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

I'm trying to recurse through a bunch of subdirectories:

foo {
chdir ($_[0]);
opendir (CURRENT_DIR, ".");
while ($name = readdir(CURRENT_DIR)) {
do_stuff();
if (-d $name && $name ne "." && $name ne "..") {
foo($name);
} } }

But that doesn't work because the directory handle gets overwritten in each recursion. Is there any way of declaring directory handles private, as you would with my($var). I am humbled before the majesty of perl.

  • Comment on Using directory handles in recursive functions

Replies are listed 'Best First'.
RE: Using directory handles in recursive functions
by mikfire (Deacon) on Jun 09, 2000 at 17:55 UTC
    If you are playing chdir games, I would first suggest trying File::Find. Understanding that such an answer is not always the best, and there is frequently knowledge to be gleaned from reinventing wheels, I would focus your attention to the local() keyword.

    I believe if you try this:

    sub foo { my $dir = shift; chdir $dir; local *CURRENT_DIR; # Everything else follows }
    you will have better success. Basically, the local() protects the calling function's version of CURRENT_DIR from any changes made locally.

    In a broader issue, let me give you a few hints. If your directory structure is very deep, you will run out of file handles soon. Those of us with (way too much) experience writing this kind of code usually suck the contents into an array and then close the directory handle.

    opendir FOO, "$dir" or die "Couldn't open $dir: $!"; @contents = readdir FOO; closedir FOO;
    This will also obviate your problem. A common variant on this is to read the contents like
    @contents = grep !/^\.\.?$/, readdir FOO;
    but I do not much care for this. If you are parsing the contents of the directory later, you have just caused perl to walk the array twice when a simple next() could have done the job.

    If the directory is too big to suck in at once and you are still running out of file handles, you can do it this way

    sub recurse_me { my $dir = shift; opendir FOO, "$dir" or die "Couldn't open $dir: $!; while( readdir FOO ) { if ( -d $_ ) { push @dirs, $_; } else { # do something interesting } } closedir FOO; recurse_me( $_ ) for ( sort @dirs ); }
    Probably more than you asked, but I thought I may help you avoid some later pain.

    Hth,
    mikfire

(jcwren) Re: Using directory handles in recursive functions
by jcwren (Prior) on Jun 09, 2000 at 17:46 UTC
    You probably want to look at the File::Recurse module. Recurse calls a function you provide for each file name encountered. Unless you're trying to stat directories, I image this should do it for you. It's also portable across Linux and NT. Here's some sample code that deleted the word 'The' from the front of all the MP3 files found:
    use strict; use File::Recurse; use File::Basename; my $allcount = 0; my $mp3count = 0; my $rencount = 0; { recurse (\&myfunc, "."); printf "\n"; printf "Total Files Found = %d\n", $allcount; printf " MP3 Files Found = %d\n", $mp3count; printf "MP3 Files Renamed = %d\n", $rencount; } sub myfunc { my $newname = ""; ++$allcount; my ($name, $path, $suffix) = fileparse ($_[0], '\.mp3'); if (length ($suffix)) { ++$mp3count; $_ = $name; if ($name =~ m/^The /) { ++$rencount; s/(^The )//ge; $newname = $path . $_ . $suffix; rename $_[0], $newname; } } }

      File::Recurse seems to be very similar to File::Find which is part of the standard distribution. What advantages do you get from using File::Recurse?


      --
      <a href="http://www.dave.org.uk><http://www.dave.org.uk>

      European Perl Conference - Sept 22/24 2000
      <http://www.yapc.org/Europe/>
        Most likely it's because I found File::Recurse first...

        The real answer is File::Recurse allows one to set the maximum depth, and has the ability to pass a parameter with that permits easy 'statefulness'. If you were building a directory of files, and wanted to assign a unique identifier to each and every entry, then File::Recurse would be the tool.

        For your purposes, File::Find would work fine, and might be more appropriate. Since I cut'n'paste a good bit, and File::Recurse is installed, I just tend to gravitate for it.

        But being a good Perl monk, you'll explore the documentation for both, make an informed judgement, and use the best tool. Right?

        --Chris
Re: Using directory handles in recursive functions
by KM (Priest) on Jun 09, 2000 at 17:45 UTC
    Remove the files from the filehandle and into a data structure so you can reopen the filehandle:

    opendir(DIR, $dir); my (@filenames) = grep(!/^\.\.?$/, readdir(DIR)); closedir(DIR); for (@filenames) { ... something ... }

    You may also want to look at File::Find. Or File::Recurse, or one of the File::* modules :)

    Cheers,
    KM

Re: Using directory handles in recursive functions
by marcos (Scribe) on Jun 09, 2000 at 22:51 UTC
    Once I wrote a script which had this procedure (it receive a directory):
    sub clean { my ($dir) = @_; opendir (DIR, $dir) or {warn "cannot opendir $dir" and return}; my (@subdirs, @files); map { push @subdirs, $_ if (-d "$dir/$_" && $_ !~ /\.|\.\./); push @files, $_ if (-f "$dir/$_"); } readdir(DIR); closedir(DIR); map {&clean("$dir/$_")} @subdirs; print "DIR - $dir\n"; foreach my $file (@files) { # do thing on each file } }
    Then I realized that File::Find did everything I needed: have a look ad it.
    marcos
RE: Using directory handles in recursive functions
by ahunter (Monk) on Jun 10, 2000 at 22:40 UTC
    In the cases where I've wanted to make file handles local (without using File::Handle :-), I've done the following:
    { local(*FH); open(FH, "<whatever") }
    I think my works with file globs as well, but I'm not sure, as this is one instance where local() seems more intuitive (!) to me.

    This is also useful for opening an arbitary number of files. Take a reference to the glob before exiting the local block:

    { local(*FH); open(FH, "<whatever") $fh = \*FH; } print $fh "Foo\n";
    As an added bonus, the file will be closed automagically when $fh goes out of scope :-)

    I would guess that opendir() and so on will work fine with this. I haven't tried it, of course, but there's no reason it shouldn't. Note that the asterix in local(*FOO) is important. local(FOO) has a different meaning...

    Andrew.

Re: Using directory handles in recursive functions
by miked (Novice) on Jun 09, 2000 at 18:52 UTC
    Thanks for the tips everyone, you're all wonderful.
RE: Using directory handles in recursive functions
by Dalvross (Acolyte) on Jun 09, 2000 at 21:42 UTC
    I wrote a program that does this a while ago while I was just learning the language, it is large but it uses a mathematical offset to travel down and up arrays. Starting in the root directory you enter and traveling down all subdirectories. It works well although the code is not the cleanest in the world. If you would like to see the code E-mail me at chagen@sei-it.com
Re: Using directory handles in recursive functions
by t0mas (Priest) on Jun 10, 2000 at 10:22 UTC
    Corion and I had a rather lengthy discussion of this subject at this node. Maybe can be of some help.

    /brother t0mas