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

Problem: This code only works when I hard-code the size to search for in the routine. I try to pass arguments using @_, but it does not work. How do I pass $size_input so wanted sees and uses it?

thank you in advance!

use strict; use warnings; use File::Find; use vars qw/*name *dir *prune/ ; *name = *File::Find::name ; *dir = *File::Find::dir ; *prune = *File::Find::prune ; my $size_input; <snip> else { print "USING LAST ELSE\n"; $size_input = ( int 25 * ( 1024**2 ) ) ; $size_input =~ tr /\000//d ; sub wanted ; File::Find::find({wanted => \&wanted}, $fs_input ) ;

###-- Tried and does not work => File::Find::find({wanted => \&want +ed}, $fs_input, $size_input ) ; --### } sub wanted { ( $fs_input ) = shift @_ ; print "FSINPT:\t",$fs_input,"\n"; for my $key ( sort keys %mounts ) { if ( $fs_input eq $key ) { @mounts = grep {$fs_input} @{ $mounts{$key} } ; } } ###-- End For my key --### if ( scalar @mounts > 0 ) { die "To bad, your dead", join( ":", @mounts ), "\n\nI said your dead $!" ; } else { ( $size_input ) = shift @_ ; my ($dev,$ino,$mode,$nlink,$uid,$gid); (( $dev,$ino,$mode,$nlink,$uid,$gid ) = lstat($_) ) && ( $dev >= 0 ) && !( $File::Find::prune |= ($dev != $File::Find::topdev ) ) && ( int(((-s _) + 1023) / 1024 ) > 26367 ) && ###-- only works when a hard-coded value is used --###

###-- ( int(((-s _) + 1023) / 1024 ) > $size_input )--### ###-- GET ERROR if $size_input is used: Use of ###-- uninitialized value in numeric gt (>) push ((@large_files), $name); } } ###-- End sub --### print scalar @large_files; print join("\n",@large_files); exit; <snip>

Replies are listed 'Best First'.
Re: passing argument to sub wanted
by ikegami (Patriarch) on May 14, 2008 at 14:26 UTC
    You're passing the arguments to find, which has no interest in them. You want them to be passed to wanted. But since the call to wanted is made by File::Find, you can't pass it extra arguments.

    There are two solutions.

    • Use a global variable.

      my $size_input; sub wanted { ... $size_input ... } ... $size_input = ...; ... find({wanted => \&wanted}, $fs_input);

      But using globals is undesirable.

    • Or have find call a function other than wanted, and have it called wanted with the parameter.

      sub wanted { my ($size_input) = @_; ... $size_input ... } { my $size_input; ... $size_input = ...; ... find({wanted => sub { wanted($size_input) }}, $fs_input); }
Re: passing argument to sub wanted
by mirod (Canon) on May 14, 2008 at 15:06 UTC

    You need to pass an additional parameter to wanted. The way to do this is to use a closure: File::Find::find({wanted => sub { wanted( $size_input); } }, $fs_input ) ;. This way wanted is called by the anonymous sub, and gets passed $size_input.

    See Why I hate File::Find and how I (hope I) fixed it for more info.

      I read the "Why I hate File::Find..." three times and adjusted my code and it seems to retain the var $size_input yet my array it not being populated. When I hard-code the number in, my array is populated as expected. I am almost begging for help b/c I cannot understand how to solve this anymore or what add'l tools to use other than Dumper and perl -d.

      Please help on this frustrating code. Thank you in advance!

      <snip> use strict; use warnings; my ( @root_files, @large_files, %mounts, @mounts, ) ; use vars qw/*name *dir *prune/ ; *name = *File::Find::name ; *dir = *File::Find::dir ; *prune = *File::Find::prune ; <snip> } else { print "USING LAST ELSE\n"; my $size_input = ( int 25 * ( 1024**2 ) ) ; $size_input =~ tr /\000//d ; my $wanted = make_wanted ( \&wanted_1, $size_input ) ; File::Find::find( $wanted, $fs_input ) ; print "\n"; } sub wanted_1 { for my $key ( sort keys %mounts ) { if ( $fs_input eq $key ) { @mounts = grep {$fs_input} @{ $mounts{$key} } ; ###-- HoA --### } } if ( scalar @mounts > 0 ) { die "cant search...foobarbazzzzy $!" ; } else { my ( $size_input ) = shift @_ ; print $size_input,"\n"; my ($dev,$ino,$mode,$nlink,$uid,$gid) ; (( $dev,$ino,$mode,$nlink,$uid,$gid ) = lstat($_) ) && ( $dev >= 0 ) && !( $File::Find::prune |= ($dev != $File::Find::topdev ) ) && ( int(((-s _) + 1023) / 1024 ) > $size_input ) && push ((@large_files), $name ) ; } } sub make_wanted { my $wanted = shift ; # get the "real" wanted function my @args = @_ ; # "freeze" the arguments my $sub = sub { $wanted->( @args ); } ; # generate the anon sub return $sub ; # return it } print "\n",scalar @large_files,"\n"; exit; <snip>

      $size_input is being printed correctly/accurately, but nothing in the array.

      Mirod, did you have time to review my update agn reply? If you could help that would be much appreciated b/c it seems you have much experience in this! thank you!

      UPDATE

      Mirod's idea does not retain my $size_input and I am still having to hard-code the value. Any more ideas greatly appreciated!

      sub wanted; File::Find::find({wanted => sub { wanted( $size_input ); } }, $fs_inpu +t ) ;
      UPDATE AGN

      I changed the find code to include a dereference (->) and the value is being printed the line b4 the int -s, but now the array is not being populated with $name when it should contain 16 elements. Finally when I print *name with $size_input it does not print, but when I print *name with the hard-coded number in the int ((( -s line, I see *name incrementing.

      File::Find::find({wanted => sub { wanted->( $size_input ); } }, $fs_i +nput ) ; <snip> else { my ( $size_input ) = shift @_ ; ###-- Freeze the arg --# my ($dev,$ino,$mode,$nlink,$uid,$gid); (( $dev,$ino,$mode,$nlink,$uid,$gid ) = lstat($_) ) && ( $dev >= 0 ) && !( $File::Find::prune |= ($dev != $File::Find::topdev ) ) && print "MIDDLE SZINPT:\t",($size_input),"\n", && ( int(((-s _) + 1023) / 1024 ) > $size_input ) && push ((@large_files), $name); } } ###-- End sub --### print scalar @large_files;

      __OUTPUT__ MIDDLE SZINPT: 26214400 MIDDLE SZINPT: 26214400 MIDDLE SZINPT: 26214400 0
Re: passing argument to sub wanted
by starbolin (Hermit) on May 14, 2008 at 15:39 UTC

    Or... Don't try to do everything inside of your wanted(). Just have wanted() build your array of filenames then iterate over the list, outside of find(), to do the pruning. Performance is not impacted as your runtime is totally dominated by disk access.

    my @filelist; find ( \&callback, @directories ); sub callback { ... push @filelist, $_; } # Prune the list my @somefiles = grep { some_code } @filelist;


    s//----->\t/;$~="JAPH";s//\r<$~~/;{s|~$~-|-~$~|||s |-$~~|$~~-|||s,<$~~,<~$~,,s,~$~>,$~~>,, $|=1,select$,,$,,$,,1e-1;print;redo}

      And hope the script is not run on a computer where the directories contain too many files. Besides, that code would produce something somewhat usable only if the @directories contained a single directory and that directory had no subdirectories. You only stored the filenames!

      The condition in front of the push() is insanely complex, but there is no reason whatsoever not to include the pruning within the wanted() subroutine.

Re: passing argument to sub wanted
by svenXY (Deacon) on May 14, 2008 at 14:10 UTC
    Hi,
    update:checking File::Find, I now think that find(\%options, @directories); is more what you should try to do...

    you need to pass it as a parameter to the wanted() call like:
    #!/usr/bin/perl use strict; use warnings; use File::Find; use vars qw/*name *dir *prune/ ; *name = *File::Find::name ; *dir = *File::Find::dir ; *prune = *File::Find::prune ; my $size_input; my $fs_input = '/tmp'; $size_input = ( int 25 * ( 1024**2 ) ) ; $size_input =~ tr /\000//d ; sub wanted ; File::Find::find({wanted => \&wanted($size_input)}, $fs_input ) ; sub wanted { my ($size_input ) = @_ ; print "FSINPT:\t",$fs_input,"\n"; print "SizeINPT:\t",$size_input,"\n"; return; } ###-- End sub --###
    at least returns the following:
    FSINPT: /tmp SizeINPT: 26214400 Odd number of elements in anonymous hash at 686513.pl line 17. Use of uninitialized value in subroutine entry at /usr/share/perl/5.8/ +File/Find.pm line 822. Can't use string ("") as a subroutine ref while "strict refs" in use a +t /usr/share/perl/5.8/File/Find.pm line 822.
    so the size parameter has been "accepted" by wanted.
    Regards,
    svenXY
      \&wanted($size_input) doesn't work at all. It calls wanted then and there, and returns a reference to the result returned by wanted.