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

Greetings All -

I am having some difficulty with a module that is using File::Find. The method is below.

The idea is to feed FindPath a file name and directory so that all occasions of $file_name are pushed into @a_files. This works fine until I need to use FindPath again during the same session. What I'm finding is that even though @a_files looses scope within FindPath itself, it does not in ProcessFile. In other words, if FindPath returns and it is called a second time within the same session, @a_files is an uninitiated array in FindPath. However when ProcessFile is called, @a_files has retained the values it had from the last call to FindPath.

Am I making sense? Well here is what it looks like having been stripped down to the bare essentials.

From a .cgi file -

use FileMan; use Cwd; my $fileman = new FileMan; my $first_path = $fileman->FindPath('file1.txt', cwd); my $second_path = $fileman->FindPath('file2.txt', cwd);
In FileMan.pm
sub FindPath { #- Var Declaration And Initialization my ($hr_self, $file_name, $file_path) = @_; # Array to fill with file paths my @a_files = (); # Search file_path for the file find(\&ProcessFile, $file_path); #- The Subroutine To Process Files And Directories sub ProcessFile {if ($_ eq $file_name){push (@a_files, $File::Find::name);}} # Return the paths found return @a_files; } # end FindPath

Replies are listed 'Best First'.
Re: Confused scoping while using File::Find
by pg (Canon) on Oct 11, 2004 at 05:21 UTC

    The following code clearly shows what is going on. When you call foo for the first time, @a was shared by bar. Second time when you enter foo, bar does not share the new @a, instead it retains the old one.

    use strict; use warnings; use Data::Dumper; foo(1); bar(); foo(2); bar(); sub foo { my @a; push @a, shift; print \@a; print Dumper(\@a); sub bar { print \@a; print Dumper(\@a); } }

    Here is the output:

    Variable "@a" will not stay shared at c.pl line 17.
    ARRAY(0x1890e78)$VAR1 = [ 1 ]; ARRAY(0x1890e78)$VAR1 = [ 1 ]; ARRAY(0x18c2194)$VAR1 = [ 2 ]; ARRAY(0x1890e78)$VAR1 = [ 1 ];

    Make @a_files a property of the class, and define those two methods at the same level. This shall resolve your problem, and the code is much clearer.

      This makes sense. Thank you for taking the time to examine my issue. Thank you also for the test code sample. I can see that I have much to learn regarding debugging my own problems.
Re: Confused scoping while using File::Find
by ysth (Canon) on Oct 11, 2004 at 05:38 UTC
    Turning on warnings with "use warnings;" or -w would have given you a Variable will not stay shared warning; saying "use diagnostics" would have produced:
    Variable "$file_name" will not stay shared at FileMan.pm line 15 (#1)
    (W closure) An inner (nested) named subroutine is referencing a lexical variable defined in an outer subroutine.

    When the inner subroutine is called, it will probably see the value of the outer subroutine's variable as it was before and during the *first* call to the outer subroutine; in this case, after the first call to the outer subroutine is complete, the inner and outer subroutines will no longer share a common value for the variable. In other words, the variable will no longer be shared.

    Furthermore, if the outer subroutine is anonymous and references a lexical variable outside itself, then the outer and inner subroutines will never share the given variable.

    This problem can usually be solved by making the inner subroutine anonymous, using the sub {} syntax. When inner anonymous subs that reference variables in outer subroutines are called or referenced, they are automatically rebound to the current values of such variables.

    Variable "@a_files" will not stay shared at FileMan.pm line 15 (#1)

    Following that recommendation, make your code something like (untested):
    #- The Subroutine To Process Files And Directories my $process_file = sub { if ($_ eq $file_name){push (@a_files, $File::Find::name);}} # Search file_path for the file find($process_file, $file_path);
      THANK YOU!!!! This is the answer. Holy Cow!! =) This has been several days of endless frustration. You are indeed a wise one and I bow to your superior knowledge. Thank You.

        It is quite usual for a new comer to the Perl world being confused by closure, so don't be too frustrated ;-)

        Closure is something nice to know, so you can recognize it next time. But in general, you should avoid it, as it breaks your lexical scope. You want to code in a clear way, not a confusing way ;-) I guess you learned more than just closure, if you think about it more.

Re: Confused scoping while using File::Find
by Zaxo (Archbishop) on Oct 11, 2004 at 05:30 UTC

    Your sub ProcessFile is defined to form a closure on the lexical @a_files. It will bind to the first instance, and ignore others.

    One simple way to fix this is to make @a_files global and localize it instead where the my declaration occurs.

    After Compline,
    Zaxo

      Thanks for looking at my issue. I do not want to make this a global variable. I want to keep any class memebers within the $hr_self object ref.