views:

1199

answers:

6

I'm using the File::Find module to traverse a directory tree. Once I find a specific file, I want to stop searching. How can I do that?

   find (\$processFile, $mydir);

   sub processFile() {
      if ($_ =~ /target/) {
         # How can I return from find here?
      }
   }
+1  A: 

Can you throw custom exceptions in Perl?

eduffy
I think there is a module for this but it is a hack.
Keltia
Just pass an object to die() instead of a string.
John Siracusa
+8  A: 

Seems like you will have to die:

eval {
    find (\$processFile, $mydir);
};

if ( $@ ) {
   if ( $@ =~ m/^found it/ ) {
        # be happy
    }
    else ( $@ ) {
        die $@;
    }
}
else {
   # be sad
}


sub processFile() {
   if ($_ =~ /target/) {
      die 'found it';
   }
}
innaM
This seems to work. I'm pretty new to perl - is this a typical use of eval?
MCS
Very. See `perldoc -f eval`.
Cirno de Bergerac
It's basically an exception without a class. If you want to have more control and make sure that it's not just some other error that matches "found it" you can use something like Exception::Class.
mpeters
Yes, strangely eval is the main way to catch die... and since quite a few built-ins die (such as use), it's the only way to catch them. Before you ask "Why would you want to catch a die from use?" it's the easiest way to see if a module is installed.
R. Bemrose
You don't need to use Exception::Class. You can die with something like "FIND_FILE:FOUND_MY_FILE", test the error message ($@) and be (nearly) certain that you found your file, and didn't hit some other error.
runrig
A: 

The function processFile() should return true if it finds the file, and false otherwise. So, every time that processFile calls himself should check this return value. If it is true, some recursive call has found the file, so there's no need to call himself again, and it must also return true. If it's false, the file hasn't been found yet, and it should continue the search.

Marc
That's not how File::Find works.
Cirno de Bergerac
It may not be correct for File::Find, but it's a correct way for a hand-rolled recursive search function (all recursive functions must have a way to terminate, and this is one acceptable way to do that).
Max Lybbert
A: 

I found this link:

http://www.perlmonks.org/index.pl?node_id=171367

I copied one of the scripts in that list of posts, and this seems to work:

#! /usr/bin/perl -w

use strict;
use File::Find;

my @hits = ();
my $hit_lim = shift || 20;

find(
    sub {
        if( scalar @hits >= $hit_lim ) {
            $File::Find::prune = 1;
            return;
        }
        elsif( -d $_ ) {
            return;
        }
        push @hits, $File::Find::name;
    },
    shift || '.'
);

$, = "\n";
print @hits, "\n";

It appears that is actually causing find to not traverse any more by using $File::Find::prune.

BrianH
I tried setting $File::Find::prune and it didn't work. Seems like setting prune on a directory will stop find from descending into that directory, but will not stop it from continuing to process other directories (or files).
MCS
Okay - When I run that script above it cut off at 20 results and from what I can tell it is stopping the find function as well. (adding the "return" could just prevent results from getting added to the array, but I'm pretty sure File::Find is actually stopping). Sorry it's not working for you...
BrianH
A: 

You could use named blocks and jump to it if you find your result (with next, last, it depends from what you need).

tunnuz
+3  A: 

In addition to what everyone else said, you may wish to take a look at File-Find-Object, which is both iterative (and as such capable of being interrupted in the middle) and capable of instantiation (so you can initiate and use several at once, or instantiate an F-F-O object based while performing another scan, etc.)

The downside for it is that it isn't core, but it only has Class::Accessor as a dependency, and is pure-Perl so it shouldn't be hard to install.

I should warn you that I am its maintainer, so I may be a bit biased.

Shlomi Fish