Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation

Its not supposed to!

by BBQ (Deacon)
on Apr 17, 2000 at 05:57 UTC ( #7796=perlquestion: print w/replies, xml ) Need Help??
BBQ has asked for the wisdom of the Perl Monks concerning the following question:

I thought I had done something right because it was working, then I looked at the code and realized it wasn't supposed to be working. I'm actually glad, but a bit bewildered!
# all my tmp dirs @dirs = GetDirs('/tmp'); print "@dirs\n"; sub GetDirs { my $startdir = $_[0]; foreach $leaf (glob("$startdir/*")) { if (-d $leaf) { @dirs = GetDirs($leaf); push(@dirs,$leaf); } } return (@dirs); }

If at every $leaf I am re-defining @dirs (since it isn't my @dirs), how can it return all of my sub-dirs? Shouldn't it return only the last directories in my last leaf?
I'm happy, but a bit confused...

Anyone care to enlighten?

Replies are listed 'Best First'.
Re: Its not supposed to!
by chromatic (Archbishop) on Apr 17, 2000 at 07:50 UTC
    It would except that @dirs is not local to any one instance of the GetDirs function. That is, the push command modifies the same @dirs that is being redefined.

    For example, suppose /tmp contains:

    /tmp/chromatic /tmp/isotope
    On the first recursion, the parent directory is /tmp. The glob picks up chromatic/ and isotope/. @dirs is empty.

    It recurses to chromatic/ and picks up a bunch of files there, but none are directories. It returns the empty @dirs. That brings us to the push, and we put chromatic/ into @dirs.

    Next, it moves on to isotope/. @dirs contains only chromatic/. As there are only files in isotope/, it doesn't push anything. It returns @dirs (still containing chromatic/) and then reaches the point where it pushes isotope/ onto the array.

    As there is nothing left to check from the glob, it returns @dirs, now containing chromatic/ and isotope/. Recursion and global variables are subtle this way.

    It's the push statement that makes it right. If you only did an assignment to @dirs, it would be wrong.

      I *think* I get it...

      What you're saying is that I could as well do this
      foreach $leaf (glob("$startdir/*")) { if (-d $leaf) { $trash = GetDirs($leaf); push(@dirs,$leaf); } }
      because it would still recurse, and it really doesn't matter what I'm assigning the return of the recursion (like $trash) because I'm pushing every $leaf to @dirs right after anyway? What really matters is the return of the 1st call and not the $trash.

      Or was that totally off track?

      BTW: You're explanations are always fun to read... Thanx!
        You don't even need the assignment to $trash in there, as the function might as well be declared void (in C or C++, if you're familiar). What you're concerned about is the side-effect of the subroutine.

        I tried to come up with a better way to do that, without the global variable, but I think you've hit on the clearest method with that recursion.

        (BTW, there is an account for isotope on this machine, though not under that nickname. Thanks for the kind words.)

Re: Its not supposed to!
by btrott (Parson) on Apr 17, 2000 at 21:29 UTC
    I'm not sure exactly what you're doing w/ this code, but you might investigate using File::Find, since it does a similar thing.
    use File::Find; my @dirs; sub wanted { push @dirs, $File::Find::name if -d $_; } find(\&wanted, 'foo');
      While there ARE modules out there that will do this, its such a small snip of code I didn't want to bother logging onto CPAN and looking for something that would do the job for me.

      Appart from the fact that the name Find::File::etc doesn't suggest what I was trying to do...

      The result of this snippet is now being used on my cron to pickup the directories in my /tmp and delete *.htm, *.txt, *.pdf, and files of the sort.

      Last time I checked, my /tmp had like 250 megs of sh1t I wasn't using. This keeps me tracking the stuff I really need.

      But thanks anyway!
        I understand. No problem with writing your own code.

        Although File::Find (not Find::File) is actually in the core distribution, so you wouldn't have to go to CPAN to get it.

RE: Its not supposed to!
by Anonymous Monk on Apr 17, 2000 at 13:10 UTC

    I was also puzzled by this till I realized that, for any empty case (either an empty dir or one without subdirs), the "recursive" call isnt going to return (); its going to return whatever its already in @dirs

    So, in fact this "recursive" function is using @dirs as an stack. But in any case I dont think this is a good way to solve the problem, cause it really depends on something that is not evident at first sight, and it goes against the intuitive definition of your function; that is, you would expect it to return the subdirs of a dir you pass to it, not that and whatever you already have in @dirs.

      I agree! While obfuscation is fun, I try to be as readable as possible. I'm commenting that in the code right now.

take a look at these scripts
by Anonymous Monk on Apr 18, 2000 at 00:26 UTC
    Look at, and, those are two scripts I used to fix a recursive 'chmod 000 *' on a development websevrer I have. :( save_perm has a very nice recursive subroutine, it's not leaky with global vars, it calls itself and returns to itself. Written with the help of 4 cans of mountain dew. :)
      um. that was supposed to be me. :)
      Hummm! I might just do that! I have a staging site on which all of my permissions are set to 666 and 777 because of the user/group that runs the httpd daemon.

      Every once in a while, I copy everything on to the deployment site and forget to reset the permissions. It would be nice to have something in the crontab that would keep me from having to do this.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://7796]
Approved by root
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (10)
As of 2018-05-21 15:34 GMT
Find Nodes?
    Voting Booth?