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

Hello ... I am writing using Active Perl 5.12.4 and the File::DirWalk module I installed using Active State's ppm. The OS in Windows 7.

I am writing a script to run up and down a directory tree and count how many of a few types of file I find and how much space they take up. As it runs, PERL reports the following errors repeatedly:

readdir() attempted on invalid dirhandle at C:/Perl/site/lib/File/Dir +Walk.pm line 100. closedir() attempted on invalid dirhandle at C:/Perl/site/lib/File/Di +rWalk.pm line 117.
This appears to happen on empty directories. The directory I am searching starts at C:\Users\Peter Lutz\Documents, which also contains some hidden and (perhaps) system files.

The code in DirWalk that the errors point to is:

opendir my $dirh, $path || return FAILED; my @dir_contents = readdir $dirh; @dir_contents = File::Spec->no_upwards(@dir_contents); foreach my $f (@dir_contents) { # be portable. my @dirs = File::Spec->splitdir($path); my $path = File::Spec->catfile(@dirs, $f); my $r = $self->walk($path); if ($r == PRUNE) { last; } elsif ($r != SUCCESS) { return $r; } } closedir $dirh;
Finally, MY code is:
#!/usr/bin/perl use strict; use warnings; ##### # TreeWalker.pl # This script walks a directory tree starting at the directory given # on the command line (uses the current directory if none given). # It keeps track of the number and total size of: # Files # Files whose name ends in ~ # Files whose name ends in .pl # Files whose name ends in .txt # Directories # # and reports these numbers at the end of the script. ##### use Path::Class qw(file dir); use File::DirWalk; my $fileN = 0; # Counter for number of files my $fileSize = 0; # Total size of files my $backupN = 0; # Counter for number of backup files my $backupSize = 0; # Total size of backup files my $txtN = 0; # Counter for number of text files my $txtSize = 0; # Total size of text files my $plN = 0; # Counter for number of PERL scripts my $plSize = 0; # Total size of PERL scripts my $dirN = 0; # Counter for number of directories my $dirSize = 0; # Total size of directories ##### # d o F i l e # Process one file as the tree is walked ##### sub doFile { my $path = shift; my $file = file(split("/[\\\/]", $path)); # Make the path a 'fi +le' my $stat = $file->stat(); # stat the file if (not defined $stat) { return; } # wierd error ... ig +nore file $fileN++; # Incr. # of files $fileSize += $stat->size; # Incr. file size my $base = $file->basename; # Get the base to c +heck for special names if($base =~ m/~$/) { $backupN++; # backup files (end + with ~) $backupSize += $stat->size; } elsif($base =~ m/\.txt$/) { $txtN++; # text files (end with + .txt) $txtSize += $stat->size; } elsif($base =~ m/\.pl$/) { $plN++; # perl files (end w +ith .pl) $plSize += $stat->size; } return File::DirWalk::SUCCESS; # return TRUE so wal +k continues } ##### # d o D i r # Process one directory as the tree is walked ##### sub doDir { my $path = shift; my $dir = dir(split(/[\\\/]/, $path)); # Make the path a 'd +ir' my $stat = $dir->stat(); # Stat the dir if (not defined $stat) { return; } # Wierd error ... ig +nore the dir $dirN++; # Increment the # of d +irs $dirSize += $stat->size; # Increment the size o +f dirs return File::DirWalk::SUCCESS; # return TRUE so wal +k continues } ##### # p r i n t S t a t s # Print out all counters at the end of the walk ##### sub printStats { printf "%-12.12s %-15d %-20d\n", "Files", $fileN, $fileSize; printf " %-8.8s %-15d %-20d\n", "Backup", $backupN, $backupSize +; printf " %-8.8s %-15d %-20d\n", "Text", $txtN, $txtSize; printf " %-8.8s %-15d %-20d\n", "PERL", $plN, $plSize; print "\n"; printf "%-12.12s %-15d %-20d\n", "Directories", $dirN, $dirSize; } ##### M a i n P r o g r a m ##### my $currdir = "."; $currdir = $ARGV[0] unless scalar(@ARGV) < 1; $currdir = dir(split("/[\\\/]", $currdir)); die "$currdir is NOT a directory" if not $currdir->is_dir(); my $walker = File::DirWalk->new; $walker->onFile(\&doFile); $walker->onDirEnter(\&doDir); $walker->walk($currdir); printStats();
I know that was long, but I figure better more info than less. Please understand!

Does anyone have experience with this, or insight into what is going on?

Thanks ... phlpittsny

Replies are listed 'Best First'.
Re: Problem with File::DirWalk and Active Perl
by kcott (Archbishop) on Mar 15, 2012 at 22:43 UTC

    The first line of code you show (from File::DirWalk) leaped out at me as being wrong.

    opendir my $dirh, $path || return FAILED;

    To avoid operator precedence problems (see perlop), this should be written as:

    opendir my $dirh, $path or return FAILED;

    or

    opendir(my $dirh, $path) || return FAILED;

    Also note that this provides no feedback on why opendir failed.

    I thought I'd give your code a try and installed File::DirWalk. During the make test phase, I received the same errors (at the same line numbers) you're reporting but the output showed testing was successful.

    t/1.t .. 1/3 readdir() attempted on invalid dirhandle at /Users/ken/. +cpan/build/File-DirWalk-0.3-mt7jiA/blib/lib/File/DirWalk.pm line 100. closedir() attempted on invalid dirhandle at /Users/ken/.cpan/build/F +ile-DirWalk-0.3-mt7jiA/blib/lib/File/DirWalk.pm line 117. t/1.t .. ok All tests successful.

    At this point, I decided not to test your code with a module which did not install cleanly.

    I had a look at File-DirWalk reviews. These are generally negative.

    I'd suggest using File::Find. Callbacks are available through The wanted function. It's also a core module so no installation is required.

    Not directly related to your problem but I'd recommend taking a look at File::Spec. This is also a core module and provides functions which will allow you to avoid hand-crafting non-portable code such as:

    $currdir = dir(split("/[\\\/]", $currdir));

    -- Ken

      Thanks, fellas ... got it now and changed to File::Find with much more sanguine results. phlpittsny
Re: Problem with File::DirWalk and Active Perl
by Marshall (Canon) on Mar 15, 2012 at 21:34 UTC
    Odd, this first line  opendir my $dirh, $path || return FAILED;
    should be opendir (my $dirh, $path) || return FAILED; or opendir my $dirh, $path or return FAILED; for it to work right. The || has a higher precedence than "or", so the parens are needed if that operator is used.

    Update:

    I recoded your routine with File::Find to show how to use this critter. I was not sure what this "Directory Size" thing was about? The size of the directory is the sum of the sizes of the files.

    I did use the special _ (underscore, not $_) variable. When Perl does a file test, it does a stat() and if you use the _ variable for another file test, it uses the cached results from stat() from the previous file test. This speeds things up considerably if you are doing a lot of file tests. $File::Find::name is the name of the current file, $File::Find::dir is the name of the current directory.

    I did not put an explicit stat() call in the wanted subroutine. I've seen this in older code. There was a reason for this as some obscure thing could happen that could cause stat() to fail. I did some searching around for that reason but wasn't able to find it. Maybe some other Monk knows? Anyway, I don't think that is necessary anymore - so I didn't do it. Also, I think if the stat fails, the file test fails, so the way the code below is written, this would have the effect of ignoring that file.

    This Dir::Walk thing looks pretty worthless to me.
    Anyway this code runs on my machine... have fun!

    #!/usr/bin/perl use strict; use warnings; use File::Find; my $fileN = 0; # Counter for number of files my $fileSize = 0; # Total size of files my $backupN = 0; # Counter for number of backup files my $backupSize = 0; # Total size of backup files my $txtN = 0; # Counter for number of text files my $txtSize = 0; # Total size of text files my $plN = 0; # Counter for number of PERL scripts my $plSize = 0; # Total size of PERL scripts my $dirN = 0; # Counter for number of directories my $dirSize = 0; # Total size of directories ??? what ??? find (\&wanted, ('C:/temp')); #note: find takes a list of directories printStats(); sub wanted { if (-d) { $dirN++; return; } if (-f _) { $fileN++; $fileSize += -s _; if ($File::Find::name =~ m/~$/) #backup { $backupN++; $backupSize += -s _; } elsif ($File::Find::name =~ m/\.txt$/) { $txtN++; $txtSize += -s _; } elsif($File::Find::name =~ m/\.pl$/) { $plN++; $plSize += -s _; } } } ##### # p r i n t S t a t s # Print out all counters at the end of the walk ##### sub printStats { printf "%-12.12s %-15d %-20d\n", "Files", $fileN, $fileSize; printf " %-8.8s %-15d %-20d\n", "Backup", $backupN, $backupSize +; printf " %-8.8s %-15d %-20d\n", "Text", $txtN, $txtSize; printf " %-8.8s %-15d %-20d\n", "PERL", $plN, $plSize; print "\n"; printf "%-12.12s %-15d\n", "Directories", $dirN; } __END__ .....prints on my machine:..... Files 2620 671260264 Backup 0 0 Text 155 15244026 PERL 974 6015816 Directories 46