Given a root directory I wish to identify the most shallow parent directory of any .svn directory and pom.xml .
To achieve this I defined the following function
use File::Find;
sub firstDirWithFileUnder {
$needle=@_[0];
my $result = 0;
sub wanted {
print "\twanted->result is '$result'\n";
my $dir = "${File::Find::dir}";
if ($_ eq $needle and ((not $result) or length($dir) < length($result))) {
$result=$dir;
print "Setting result: '$result'\n";
}
}
find(\&wanted, @_[1]);
print "Result: '$result'\n";
return $result;
}
..and call it thus:
$svnDir = firstDirWithFileUnder(".svn",$projPath);
print "\tIdentified svn dir:\n\t'$svnDir'\n";
$pomDir = firstDirWithFileUnder("pom.xml",$projPath);
print "\tIdentified pom.xml dir:\n\t'$pomDir'\n";
There are two situations which arise that I cannot explain:
- When the search for a .svn is successful, the value of
$resultperceived inside the nested subroutinewantedpersists into the next call offirstDirWithFileUnder. So when the pom search begins, although the linemy $result = 0;still exists, thewantedsubroutine sees its value as the return value from the lastfirstDirWithFileUndercall. - If the
my $result = 0;line is commented out, then the function still executes properly. This means a) outer scope (firstDirWithFileUnder) can still see the$resultvariable to be able to return it, and b) print shows thatwantedstill sees$resultvalue from last time, i.e. it seems to have formed a closure that’s persisted beyond the first call offirstDirWithFileUnder.
Can somebody explain what’s happening, and suggest how I can properly reset the value of $result to zero upon entering the outer scope?
Using
warningsand thendiagnosticsyields this helpful information, including a solution:$resultis lexically scoped, meaning a brand new variable is allocated every time you call&firstDirWithFileUnder.sub wanted { ... }is a compile-time subroutine declaration, meaning it is compiled by the Perl interpreter one time and stored in your package’s symbol table. Since it contains a reference to the lexically scoped$resultvariable, the subroutine definition that Perl saves will only refer to the first instance of$result. The second time you call&firstDirWithFileUnderand declare a new$resultvariable, this will be a completely different variable than the$resultinside&wanted.You’ll want to change your
sub wanted { ... }declaration to a lexically scoped, anonymous sub:and invoke
File::Find::findasHere,
$wantedis a run-time declaration for a subroutine, and it gets redefined with the current reference to$resultin every separate call to&firstDirWithFileUnder.Update: This code snippet may prove instructive:
Typical output:
Note that the compile time
$fooalways refers to the same variableSCALAR(0xac18c0), and that this is also the run time$fooTHE FIRST TIME the function is run.The last line of
&foo,push @baz,\$foois included in this example so that$foodoesn’t get garbage collected at the end of&foo. Otherwise, the 2nd and 3rd runtime$foomight point to the same address, even though they refer to different variables (the memory is reallocated each time the variable is declared).