muizelaar has asked for the wisdom of the Perl Monks concerning the following question:

I have been a away from Perl for a few months and I'm still learning but finding it frustrating to pick it up again. I have two problems, the first is I would like to send a list of directories to an out put file and the second is i would like not to include the . and .. directories.
#!/usr/bin/perl -w use Cwd; my $dir = getcwd; my $dirs = "/opt/DSPKG"; opendir(BIN, $dirs) || die "Can't open $dirs: $!"; my @array = grep { -d "$dirs/$_" } readdir BIN; foreach my $file (@array) { open(MYFILE, ">/tmp/pkgtest") || die "cant open pkgtest: $!"; print "$file\n"; }

Replies are listed 'Best First'.
Re: directory listing
by ikegami (Patriarch) on Sep 10, 2007 at 18:14 UTC
    Just remove them.
    my $dir = "/opt/DSPKG"; opendir(my $bin_dh, $dirs) or die "Can't open directory $dir: $!\n"; my @subdirs = grep { !/^\.\.?\z/ && -d "$dirs/$_" } readdir $bin_dh; my $pkg_file = '/tmp/pkgtest'; open(my $pkg_fh, '>', $pkg_file) or die "cant open pkgtest $pkg_file: $!\n"; foreach my $subdir (@subdirs) { print $pkg_fh "$subdir\n"; }

    Other fixes:

    • Added omitted file handle in print statement.
    • Eliminated the use of global variables (BIN and MYFILE).
    • Adjusted the number (singular/plural) of variable names. (For example, $dirs held the name of one directory, so I renamed it to $dir.)
    • Tried to use more meaningful names. You'd be able to pick even better ones since you know what this code is doing.
    • Used safer 3-arg open.
    • Added \n to die messages. The user does not need to know the line number at which the error occured for those errors.
      Thanks You were a great help. :)
Re: directory listing
by moritz (Cardinal) on Sep 10, 2007 at 18:04 UTC
    You could take a look at glob.

    Also, you don't need to open the file each time in the loop, do something like that:

    open MYFILE, '>', '/tmp/pkgtest' or die "Can't open pkgtest: $!"; for (@array){ print MYFILE, $_, "\n"; } close MYFILE;
Re: directory listing
by mr_mischief (Monsignor) on Sep 10, 2007 at 20:29 UTC
    #!/usr/bin/perl use strict; use warnings; my $dir = '/opt/DSPKG'; opendir my $dh, $dir || die "Can't open $dir: $!\n"; open my $out, '>', '/tmp/pkgtest' || die "Can't open pkgtest: $!\n"; foreach ( readdir $dh ) { print $out $_ . "\n" if -d "$dir/$_" && $_ !~ /^\.{1,2}$/; } close $out || die "Error closing pkgtest: $!\n";

    Notice the following, which will help you in the long run:

    • scalar directory handles and file handles instead of barewords
    • three-argument open rather than two-argument open
    • die() with a newline -- leave it out if you want the "died at line ##" message, or put it in to get nice, clean custom error messages. This one's a preference, but it's good to know your options.
    • There's no particular need to make an array just to hand it to foreach. The foreach loop will take results in list context directly.
    • It's good to know you can specify your loop variables, but in some cases using $_ is nice and clear. I'm using that here.
    • The negative regex match takes care of '.' and '..'. The -d makes sure you're only getting directories in your output. This saves you the separate loop of the grep.
    • Although strictly not necessary in this small of an isolated program, you might like to get accustomed to closing your file handles. The success test on closing helps you deal with things such as out-of-disk-space conditions.

    If building the array with grep and handing it to foreach is clearer for you, it's fine to do things that way. I wouldn't want to promote doing away with that simply for the sake of doing so. I, personally, find it clearer when the test is made inside the main loop rather than in a secondary looping construct.

    I like doing away with the array as well, as it lets me focus on what's being done to the data rather than where it's being stored.

    Scalar filehandles and directory handles are how things tend to be done these days. The language is moving away from barewords for such things, and for scoping reasons it's smart to use scalars. Bareword handles are package-scoped. Scalar ones can be declared lexical with my.

Re: directory listing
by jrsimmon (Hermit) on Sep 10, 2007 at 18:16 UTC
    Try this:
    #!/usr/bin/perl -w use Cwd; my $dir = getcwd; my $dirs = "/opt/DSPKG"; opendir(BIN, $dirs) or die $!; open(MYFILE, ">/tmp/pkgtest") || die $!; while(my $checkDir = readdir(BIN)){ #test for the directory -- better than grep if(-d "$dirs"){ #print it to file unless it's the current or #previous directory entry, as you specified unless($dirs eq "." or $dirs eq ".."){ print MYFILE "$dirs\n"; } } } closedir(BIN); close(MYFILE);
Re: directory listing
by jlk (Hermit) on Sep 11, 2007 at 19:31 UTC

    You could always just use the File::Util module from cpan. It contains a list_dirs function with an option ( -no-fsdots ) to exclude the dot directories.


    Regards,

    jlk

    "Perl isn't a toolbox, but a small machine shop where you can special-order certain sorts of tools at low cost and in short order. -- Larry Wall"