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

Hi,
I am trying to get a script to recurse through a directory structure. The script for the sake of simplicity will add a trailing 'z' to all files and directories. I have a problem with the recursion, is seems to go a couple of directories deep, but no further, I expect misuse of 'my ($Variable);' type statements...

first is the working non recursive script, which gets passed a directory, and then my recursion effort that doesn't work!

==WORKING EFFORT (not recursive)==
#!wperl use strict; use Win32; my ($DirItem); my ($Formatted); opendir(DIR, $ARGV[0]) || Fail; my @DirList = readdir(DIR); closedir DIR; foreach $DirItem (@DirList) { $Formatted = $DirItem . "z"; if ($DirItem ne "." && $DirItem ne "..") { rename($DirItem,$Formatted); } }
========================

==Non Working Recursive Effort==
#!wperl use strict; use Win32; sub Recurse; my ($DirItem); my ($Formatted); my ($PWD); Recurse ($ARGV[0]); sub Recurse { ($PWD) = @_; opendir(DIR, $PWD); my @DirList = readdir(DIR); closedir DIR; foreach $DirItem (@DirList) { $Formatted = $DirItem . "z"; if ( -d $DirItem &&$DirItem ne "." && $DirItem ne "..") { &Recurse ($PWD . "\\" . $DirItem); } if ($DirItem ne "." && $DirItem ne "..") { rename($DirItem,$Formatted); } } }
========================

Thanks for any help you can give! I went depth first so that only after a (sub)directory had been explorered did its name get changed, at least i believe that to be the case!

cheers
ant

Replies are listed 'Best First'.
Re: Directory recursion on win32
by Thelonius (Priest) on Aug 04, 2003 at 14:21 UTC
    Although you may want to use File::Find as other respondents suggested, you can get your subroutine to work by declaring your variables with "my" inside your subroutine Recurse.
    #!wperl use strict; use Win32; sub Recurse; Recurse ($ARGV[0]); sub Recurse { my ($DirItem); my ($Formatted); my ($PWD); ($PWD) = @_; opendir(DIR, $PWD); my @DirList = readdir(DIR); closedir DIR; foreach $DirItem (@DirList) { $Formatted = $DirItem . "z"; if ( -d $DirItem &&$DirItem ne "." && $DirItem ne "..") { &Recurse ($PWD . "\\" . $DirItem); } if ($DirItem ne "." && $DirItem ne "..") { rename("$PWD\\$DirItem","$PWD\\$Formatted"); } } }
    Updated: Changed
      rename($DirItem,$Formatted);
    
    to
      rename("$PWD\\$DirItem","$PWD\\$Formatted");
    

    That should fix it.

      Okay, moved the definitions inside the sub, and added a count, to see how many times it recursed. Extra info is that It will rename the files (and dirs) in the current directory (so the initial given path), but it won't dig deeper. So the problem is with how i pass path info to Recurse?
        Here is a script that I wrote which recurses down a win32 directory strcuture and writes a logfile which tells the directories and all the contents.. I think the recursion model might be useful for you. Although it doesn't use File::Find
        #!/Perl/bin/perl use IO::File; use strict; print "Enter the directory to map: "; my $dir = <STDIN>; chomp($dir); my $log = new IO::File; $log->open(("> c:\\log.txt")) or die "$!"; $log->write("Map of: ".$dir."\n",length("Map of: ".$dir."\n")); $dir =~ s{\\}{\\\\}g; mapMe($dir); $log->close; ##Recursive routine to print out all the files & folders under a given + root node sub mapMe { #Get the parameter my $handle = shift; #Open the directory passed to the subroutine opendir(SPROUT,$handle); #read the entries my @entries = readdir(SPROUT); #Close the directory closedir(SPROUT); my $log_entry; #Skip the . and .. entries foreach my $i (2..scalar(@entries)) { #Format the handle for the next call my $param_handle = $handle."\\".$entries[$i]; #If its a directory and its not null if(opendir(TEST,$param_handle) and $entries[$i]) { #Close the directory closedir(TEST); #Strip the handle for log writing purposes $handle =~ s{\\\\}{\\}g; #Construct and write the log $log_entry = "\n".$handle."\\".$entries[$i]."\n"; $log->write($log_entry,length($log_entry)); #recurse the directory mapMe($param_handle); } elsif($entries[$i]) { #Construct and write the log $log_entry = "*".$entries[$i]."\n"; $log->write($log_entry,length($log_entry)); } } }
Re: Directory recursion on win32
by derby (Abbot) on Aug 04, 2003 at 14:18 UTC
    File::Find is your friend.

    (note to self): Add "Recursive Directory Walker" to list of "must" implement requirements for self-respecting perl developers (along with an HTML templating system and DB abstraction layer).

    -derby

Re: Directory recursion on win32
by broquaint (Abbot) on Aug 04, 2003 at 14:48 UTC
    Further to what others have said you might want to check that you're able to open the directories e.g
    opendir(DIR, $PWD) or return warn("$0: $! [$PWD]"); ...
    So you'll get a warning and the sub will return. You may also want to check rename for success.

    Of course, you could always do this with File::Find::Rule, the spiritual successor to File::Find, like so

    use File::Find::Rule; rename $_ => "${_}z" or warn "$0: $! [$_]" for find(file => in => $ARGV[0]);
    See. the File::Find::Rule docs for more info.
    HTH

    _________
    broquaint

      Okay, had something similar but ripped out for the query, as it happened, also took it out of my version ;) so cheers!
Re: Directory recursion on win32
by Abigail-II (Bishop) on Aug 04, 2003 at 14:15 UTC
    Is there a reason you aren't using File::Find?

    Abigail

      Hi,
      Really REALLY swift responses, so for now i'll assume you are boths scripts ;)
      The test tree is about 4 directories deep, and contains about 50 files, some directories merely contain subdirs, and some nothing at all.

      As far as a reason why I am not using File::Find the reason would be ignorance. I have constructed something similar to this script which worked fine in unix, but am having probs, in win32. I just stuck with the same method. The unix scripts can be cut and pasted if you think it would be of any help.

      cheers again
      ant
        The script for the sake of simplicity will add a trailing 'z' to all files and directories... As far as a reason why I am not using File::Find the reason would be ignorance

        Then perhaps this will help to rectify that:

        use File::Find; sub rename_file { my $fullpath = $File::Find::name; rename ($fullpath, $fullpath.'z') || warn "Failed to rename $fullpath\n"; } find (\&rename_file, $path);
        What could be simpler?

        As others have pointed out, modules are there to help you. Not using them out of ignorance is probably not what you want to do for too long: you miss out on all the fun!

Re: Directory recursion on win32
by fourmi (Scribe) on Aug 04, 2003 at 16:27 UTC
    SOLVED!

    Special thanks to Grygonos, and thanks to everyone else who responded so quickly!! I adapted Grygonos' code, and managed to make it work, things that may have made a difference were the initial pwd chomp, and the use of \\\\ in places. for interest the working code follows, will probably adapt further but this works.
    #!wperl use IO::File; use strict; my $dir = $ARGV[0]; chomp($dir); $dir =~ s{\\}{\\\\}g; mapMe($dir); sub mapMe { my $handle = shift; opendir(SPROUT,$handle); my @entries = readdir(SPROUT); closedir(SPROUT); foreach my $i (2..scalar(@entries)) { my $param_handle = $handle."\\".$entries[$i]; if(opendir(TEST,$param_handle) and $entries[$i]) { closedir(TEST); $handle =~ s{\\\\}{\\}g; my ($newdirname)= $handle."\\".$entries[$i]. "z"; rename ($handle."\\".$entries[$i], $newdirname); mapMe($newdirname); } elsif($entries[$i]) { my ($newname) = $handle."\\".$entries[$i] . "z"; rename ($handle."\\".$entries[$i], $newname); } } }
    cheers!!
    ant
Re: Directory recursion on win32
by fglock (Vicar) on Aug 04, 2003 at 15:11 UTC

    Your recursion code is ok - you have to specify the path for "rename":

    rename( $PWD . "\\" . $DirItem, $PWD . "\\" . $Formatted );
      Ahh, okay that has gained me an extra level of recursion. Can i get confirmation that $PWD will take on the new pwd on each recursion not, maintain the previous value? (i'm not fully understanding the practical differences between $def, my $def, and my ($def) )
        sub Recurse { my ($PWD) = @_;

        This makes $PWD exist only during this call to this block. Recurrent calls will create new versions of $PWD.

        The parenthesis make a list, and $PWD is the first value. This list gets aliased to the values in the @_ list, such that $PWD = $_[0].

Re: Directory recursion on win32
by talexb (Chancellor) on Aug 04, 2003 at 14:15 UTC

    I haven't tried your code but it looks OK. There are memory limits to how big an array can grow, but unless you've got a directory structure millions of layers deep, that shouldn't be a problem. Your my ($Variable); declarations suggest that you wrap lists in brackets without knowing why. That will become useful when you write things like

    my ( $foo, $bar ) = @_;
    since @_ is a list context kind of animal.

    How deep did your recursion go? How did it die? What happened? Why do you think it died?

    --t. alex
    Life is short: get busy!
Re: Directory recursion on win32
by CountZero (Bishop) on Aug 04, 2003 at 18:50 UTC

    As Mark Jason Dominus so eloquently explained at YAPC::EU in Paris, there is a good case to make to favour non-recursive solutions over recursive solutions for this (type of) problem. He als pointed out some deficiencies in File::Find (which are closely linked to its "recursiveness").

    Have a look at his Iterators and constructors article.

    CountZero

    Update: Oops, The link only points to the table of contents and not to the slides of this talk itself. I could have sworn that MJD said he would put the slides on his web-site. Wishful thinking on my side probably (or more likely my memory finally giving way).

    "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law