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

Hello Monks,

I have the following sub which works as intended about 90% of the time.
# this subroutine will take path and use File::Find to decend # one directory level deeper and list the directories it finds there # if it encounters a 'home', 'shared', or 'expansions' directory it'll # descend [even] one level deeper than otherwise sub go_deeper { #arguments 1) unc style path string i.e. '\\fileshare1\froot$' #returns 1) array with paths to directories below the given path # find directories my $path_line = shift; chomp($path_line); # delete trailing backslash if its there if ( substr( $path_line, -1 ) eq '\\' ) { substr( $path_line, -1 ) = ''; } our $depth = 0; our $original_depth = $depth; our @dirs_list = (); find( { preprocess => \&preprocess, wanted => \&wanted }, $path_line ); sub wanted { my $dir = $File::Find::name; print LOG "DEBUG Entered wanted function. CWD: $dir \n"; if ( ( index( lc($dir), 'home' ) != -1 || index( lc($dir), 'expansion' ) != -1 || index( lc($dir), 'share' ) != -1 ) and $depth == $original_depth ) { print LOG "DEBUG adding to depth\n"; $depth++; } elsif ( $depth != $original_depth ) { print LOG "DEBUG subtracting from depth\n"; $depth--; } my $depth_count = $File::Find::dir =~ tr[/][]; # count slashes to get depth return if $depth_count < $depth; if ( (-d) && ( $_ ne '.' ) ) { $dir =~ s/\//\\/g; print LOG "DEBUG Adding dir to file: " . $dir . "\n"; push( @dirs_list, $dir ); } } sub preprocess { my $depth_count = $File::Find::dir =~ tr[/][]; return @_ if $depth < $depth; if ( $depth_count == $depth ) { return grep { -d } @_; } return; } return @dirs_list; }
There is a bug somewhere in there, that causes the wanted function to add a top level 'home' (or other) directory to the array instead of the 'home' directory's subdirs. This doesn't happen every time, and I have not been able to discern a pattern for why this happens, other than that it always follows a 'subtracting from depth' debug print statement.

Here's what it looks like in the log file:
DEBUG Adding dir to file: \\fileshare2\groot$\shared\EnterpriseD
DEBUG Entered wanted function. CWD: \\fileshare2\groot$/shared/MIG020907
DEBUG subtracting from depth <=== THIS WAS LAST DIR AT THIS DEPTH, SO ITS BACKING OUT
DEBUG Adding dir to file: \\fileshare2\groot$\shared\MIG020907
DEBUG Entered wanted function. CWD: \\fileshare2\groot$/shared/ITReg
DEBUG adding to depth
DEBUG Adding dir to file: \\fileshare2\groot$\shared\ITReg
DEBUG Entered wanted function. CWD: \\fileshare2\groot$/home 
DEBUG subtracting from depth
DEBUG Adding dir to file: \\fileshare2\groot$\home <=== THIS LINE SHOULD NOT BE ADDED 
DEBUG Entered wanted function. CWD: \\fileshare2\groot$/EXPANSIONS 
DEBUG adding to depth
DEBUG Entered wanted function. CWD: \\fileshare2\groot$/EXPANSIONS/old 
DEBUG subtracting from depth
DEBUG Adding dir to file: \\fileshare2\groot$\EXPANSIONS\old
My instincts are telling me this has something to do with this dir being the last one in the list or something similar, linked to the recursive nature of file::find, but nothing I've tried has yielded results. Please help me out.

Replies are listed 'Best First'.
Re: Hard to find bug in my File::Find code
by kyle (Abbot) on Aug 29, 2008 at 18:22 UTC

    There's something spishy in your preprocess sub:

    my $depth_count = $File::Find::dir =~ tr[/][]; return @_ if $depth < $depth;

    When is $depth < $depth ?

      Good catch, I got a lot of this code from an example in a Tutorial here (Beginner's Guide to File::Find), and the case there had both a maxdepth and a mindepth, which I have gotten rid of, I must have missed this line when checking the results of my search and replace.
Re: Hard to find bug in my File::Find code
by tilly (Archbishop) on Aug 29, 2008 at 18:23 UTC
    Looking at the code, as soon as you encounter "shared", you increase $depth by one. But as soon as you see any directory in home it will skip the first case of your if because the $depth is not the original depth, and will subtract from $depth. So while you're exploring "shared", $depth keeps on bouncing back and forth.

    It gets more complicated if you visit 2 special directories in a row. In your case you visit /shared/ITReg and add to $depth, but that is the last /shared/* directory. Therefore when you enter /home, $depth decreases by one and you get /home rather than subdirectories.

    The solution is that rather than trying to manipulate a global $depth as you walk the tree, within each invocation of &wanted calculate $depth from first principles.

      So I'd need another variable, lets say $curr_depth which would be calculated at the start of a &wanted call, and then I can update the $depth global with it at the end of the call? Am I getting this right?

      The reason its global now is so I can share it with &preprocess...
Re: Hard to find bug in my File::Find code
by runrig (Abbot) on Aug 29, 2008 at 18:50 UTC
    I might go with something more like this (not tested on Windows):
    #!/usr/bin/perl use strict; use warnings; use File::Basename qw(basename); # Initial directory list my @dirs = "tmp"; my @final_list; while (my $dir = shift @dirs) { push @final_list, $dir; for my $nxt_dir ( grep -d, <$dir/*> ) { if ( basename($nxt_dir) =~ /^(?:shared|home|expansions)$/i ) { unshift @dirs, $nxt_dir; } else { push @final_list, $nxt_dir; } } } print "$_\n" for @final_list;
Re: Hard to find bug in my File::Find code
by jethro (Monsignor) on Aug 29, 2008 at 18:14 UTC
    if ( (-d) && ( $_ ne '.' ) ) {

    I don't use $_ extensively so probably I have overlooked the side effect that put some value in it before this line. Otherwise it might be your bug

    UPDATE: Should have checked File::Find before posting, it seems to use some strange methods to get values into the sub