http://qs321.pair.com?node_id=1031085

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

Dear Monks,

this is most certainly a misunderstanding of a scope issue but I cannot solve this puzzle myself.

The following code works:

#!/perl use strict; use warnings; use File::Find; my $dir = qw(N:/TMP); find( \&wanted, $dir ); sub wanted { print "[$dir] $File::Find::dir $File::Find::name\n"; }

However if I try to call the same in the loop:

my @dir = ( qw( N:/TMP N:/TMP_2 )); for my $dir ( @dir) { find( \&wanted, $dir ); }

it dies with the message:

Global symbol "$dir" requires explicit package name at ...

If I call the same as an anonyme subroutine, it works:

for my $dir ( @dir) { find( sub {print "[$dir] $File::Find::dir $File::Find::name\n";}, $dir ); }

Could you please explain this behavior?

Thanks in advance.

VE

Replies are listed 'Best First'.
Re: File::Find in a loop
by fishmonger (Chaplain) on Apr 28, 2013 at 16:35 UTC
    Others have answered you base question, so I'll bring up something else. Why would you execute the find(..) call in a for loop like that, when the find(..) function accepts a list of directories?
    my @dir = qw( N:/TMP N:/TMP_2 ); find( \&wanted, @dir ); sub wanted { print "$File::Find::dir $_\n"; }

      I have almost forgotten why (sorry I am getting old :-) ) in fact I used the anonyme subroutine in the working script already.

      The problem: I use File::Find in a script to backup my files to the floppy disk. I have a hash with keys as the source directories and values as the target directories.

      So I use:

      for my $source ( keys %dir ) { my $destination = $dir{$source}; find( sub { my $relativeName = $File::Find::name; $relativeName =~ s/$source//; my $sourceName = File::Spec->catdir( $source, $relativeName ); my $destinationName = File::Spec->catdir( $destination, $relativeName ); (...compare and backup if new or newer ...) }

      That's why I found no other way as to feed both $source and $destination to &find and that's why I do it in a loop. Well, another option would be to call &find anew for each location in a script:

      my $source = "N:/TEMP"; my $destination = $dir{$source}; find ( \&wanted, $source ); my $source = "N:/TEMP_2"; etc.

      Is there another way to do it?

      Thanks again!
Re: File::Find in a loop
by NetWallah (Canon) on Apr 28, 2013 at 16:07 UTC
    The "wanted" sub uses a GLOBAL $dir in your code. (Before I get flamed - what I mean is GLOBAL to the sub. The example below uses a lexical variable that is global to the sub)

    In the "for my $dir" loop, $dir is LOCAL to the block enclosed by the "for" loop, so the "wanted" sub cannot see it.

    To fix, keep $dir global:

    my $dir; for $dir (@...)...

                 "I'm fairly sure if they took porn off the Internet, there'd only be one website left, and it'd be called 'Bring Back the Porn!'"
            -- Dr. Cox, Scrubs

      Before I get flamed

      You're not using that word correctly :)

        From The Urban Dictionary:
        flamed
        being attacked online/on a forum.

        I think it is appropriate. Do you have an alternative suggestion ?

                     "I'm fairly sure if they took porn off the Internet, there'd only be one website left, and it'd be called 'Bring Back the Porn!'"
                -- Dr. Cox, Scrubs

Re: File::Find in a loop
by RichardK (Parson) on Apr 28, 2013 at 16:08 UTC

    But you've missed out a very important part of the error message, where the error is occurring!

    It will say at line xxx. So, if you look at that line then it will be obvious where your scoping issue is coming from ;)

Re: File::Find in a loop
by vagabonding electron (Curate) on Apr 28, 2013 at 16:59 UTC

    Thank you very much all!

    @ NetWallah This works, however with a warning "Use of a uninitialized value $dir ..." on the first run of the loop. After your explanation I understand now why.

    @ RichardK The line number corresponds to the subroutine wanted in the script. That is what I meant :-)

    @ fishmonger This was a aha moment for me :-) Thanks again! Update: See Re^2: File::Find in a loop

Re: File::Find in a loop
by tobyink (Canon) on Apr 29, 2013 at 00:40 UTC

    I have no idea why so many people actually go to the trouble of creating an actual sub in their code called "wanted". Pass a coderef...

    use strict; use warnings; use File::Find; my @dir = qw( N:/TMP N:/TMP_2 ); for my $dir (@dir) { find(sub { print "[$dir] $File::Find::dir $File::Find::name\n"; }, $dir); }
    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
Re: File::Find in a loop
by Laurent_R (Canon) on Apr 28, 2013 at 21:05 UTC

    I haven't tested it, but I guess you could simply pass a parameter to your wanted call-back function:

    my @dir = ( qw( N:/TMP N:/TMP_2 )); for my $dir ( @dir) { find( \&wanted ($dir), $dir ); }

    And modify the wanted function as follows:

    sub wanted { my $dir = shift; print "[$dir] $File::Find::dir $File::Find::name\n"; }

      find( \&wanted ($dir), $dir );
      No! I don't think so.

      The File::Find documentation makes this clear saying thus:

      wanted function takes no arguments but rather does its work through a collection of variables.
      $File::Find::dir is the current directory name,
      $_ is the current filename within that directory
      $File::Find::name is the complete pathname to the file.
      These variables have all been localized and may be changed without affecting data outside of the wanted function.

        Yes, you are right, what I suggested does not work. It has been a long time since I last looked at the File::Find documentation, I did not remember at all about that.

        Sorry about my mistake and thank you, 2teez, for pointing out this error.

        Having said that, this is (as far as I know) the first time that one of my posts had a negative reputation (-1 as of this reading). OK, well, my mistake, someone down voted me, I should know better, fair enough. I should add that I am not particularly concerned about that (and I only partly understand the reputation thing). But I did not think that an error in a post where I said that this just *might* be a solution and in which I very specifically said that this was untested deserved a down voting. But, of course, someone out there thinks that none should talk unless she or he knows everything about every single module. Oh, well...