Finding files with Perl

Removing the sub from sub wanted; just makes it a call to the wanted function, not a forward declaration.

However, the wanted function hasn't been designed to be called directly from your code - it's been designed to be called by File::Find. File::Find does useful stuff like populating$_ before calling it.

There's no need to forward-declare wanted here, but if you want to remove the forward declaration, remove the whole sub wanted; line - not just the word sub.

I'm not a big fan of File::Find. It just doesn't work right. The find command doesn't return a list of files, so you either have to use a non-local array variable in your find to capture your list of files you've found (not good), or place your entire program in your wanted subroutine (even worse). Plus, the separate subroutine means that your logic is separate from your find command. It's just ugly.

What I do is inline my wanted subroutine inside my find command. Subroutine stays with the find. Plus, my non-local array variable is now just part of my find command and doesn't look so bad

Here's how I handle the File::Find -- assuming I want files that have a .pl suffix:

my @file_list;
find ( sub {
    return unless -f;       #Must be a file
    return unless /\.pl$/;  #Must end with `.pl` suffix
    push @file_list, $File::Find::name;
}, $directory );

# At this point, @file_list contains all of the files I found.

This is exactly the same as:

my @file_list;

find ( \&wanted, $directory );

sub wanted {
    return unless -f;
    return unless /\.pl$/;
    push @file_list, $File::Find::name;

# At this point, @file_list contains all of the files I found.

In lining just looks nicer. And, it keep my code together. Plus, my non-local array variable doesn't look so freaky.

I also like taking advantage of the shorter syntax in this particular way. Normally, I don't like using the inferred $_, but in this case, it makes the code much easier to read. My original Wanted is the same as this:

sub wanted {
    my $file_name = $_;
    if ( -f $file_name and $file_name =~ /\.pl$/ ) {
        push @file_list, $File::Find::name;

File::Find isn't that tricky to use. You just have to remember:

  • When you find a file you don't want, you use return to go to the next file.
  • $_ contains the file name without the directory, and you can use that for testing the file.
  • The file's full name is $File::Find::name.
  • The file's directory is $File::Find::dir.

And, the easiest way is to push the files you want into an array, and then use that array later in your program.