tags:

views:

127

answers:

3

I'm a bit confused from File::Find documentation... what is the equivalent to $ find my_dir -maxdepth 2 -name "*.txt"?

+5  A: 

You should give find a preprocess function, as described in this comment on perlmonks.org.

use File::Find;

my $max_depth = 2;

find( { preprocess => \&limit_depth,
        wanted => \&textfiles_only
      }, "my_dir");

sub limit_depth {

Count number of slashes to find out depth. Exercise for the reader: substract the number of slashes in the original path. That's zero for "my_dir", of course.

    my $depth = $File::Find::dir =~ tr[/][];

The minus one is necessary because GNU find starts counting at one, the Perl module at zero.

    if ($depth < $max_depth - 1) {
        return @_;
    } else {

Do not process directory children if we're at $max_depth.

        return grep { not -d } @_;
    }
}

This is the wanted subroutine, which is called on each file at depth <= 2.

sub textfiles_only {
    print "$File::Find::name\n" if $_ =~ /\.txt$/;
}
larsmans
+1 Thanks for demystifying `File::Find` for me.
David B
+3  A: 

I think I'd just use a glob since you really don't need all the directory traversal stuff:

 my @files = glob( '*.txt */*.txt' );

I made File::Find::Closures to make it easy for you to create the callbacks that you pass to find:

 use File::Find::Closures qw( find_by_regex );
 use File::Find qw( find );

 my( $wanted, $reporter ) = File::Find::Closures::find_by_regex( qr/\.txt\z/ );

 find( $wanted, @dirs );

 my @files = $reporter->();

Normally, you can turn a find(1) command into a Perl program with find2perl:

% find2perl my_dir -d 2  -name "*.txt"

But apparently find2perl doesn't understand -maxdepth, so you could leave that off:

% find2perl my_dir -name "*.txt"
#! /usr/local/perls/perl-5.13.5/bin/perl5.13.5 -w
    eval 'exec /usr/local/perls/perl-5.13.5/bin/perl5.13.5 -S $0 ${1+"$@"}'
        if 0; #$running_under_some_shell

use strict;
use File::Find ();

# Set the variable $File::Find::dont_use_nlink if you're using AFS,
# since AFS cheats.

# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;

sub wanted;



# Traverse desired filesystems
File::Find::find({wanted => \&wanted}, 'my_dir');
exit;


sub wanted {
    /^.*\.txt\z/s
    && print("$name\n");
}

Now that you have the starting programming, you can plug in whatever else you need, including a preprocess step to prune the tree.

brian d foy
+1 Thanks! good to find about `File::Find::Closures`
David B
+3  A: 

Personally I prefer File::Find::Rule as this doesn't need you to create callback routines.

    use strict ;
    use Data::Dumper ;
    use File::Find::Rule ;

    my $dir = shift ;
    my $level = shift // 2 ;

    my @files = File::Find::Rule->file()
                                  ->name( "*.txt" )
                                  ->maxdepth( $level )
                                  ->in( $dir );

    print Dumper( \@files ) ;                            

Or alternatively create an iterator :

    my $ffr_obj = File::Find::Rule->file()
                                  ->name( "*.txt" )
                                  ->maxdepth( $level )
                                  ->start ( $dir );

    while (my $file = $ffr_obj->match() )
    {
       print "$file\n"
    }
justintime
+1 I think this is the simplest, most `$ find` -like solution suggested.
David B