nowox nowox - 7 months ago 35
Perl Question

List all files recursively but exclude some directories (.git .svn)

I would like to get all the files in a specified path but I want to exclude all the files inside some directories defined with:

my $exclude = qw/.git .svn .cvs/;


The simplest method uses
File::Find
, but in the case of very big projects (under git or svn), the
find
subroutine will still iterates over all the files inside the excluded directories:

my $root = 'foo/';
my @files = do {
my @f;
find(sub {
state $excluded = do {
my $qr = join('|', map(quotemeta($_ =~ s/\/+$//r), @exclude));
qr/$qr$/;
};

local $_ = $File::Find::name;
next unless -f;
next unless /$excluded/;
push @f, $_;
}, $root);
@f;
}


The only solution I've found involving only core modules is to manually iterate with
readdir
. Is there a better method?

EDIT

A solution that works is this code below but It seems it is a bit complex for something that should be simple...

use 5.014;
my @exclude = qw/.git .svn .cvs/;
my @files = parse_dir('.');
say join("\n", @files);

sub parse_dir {
state $re = do {
my $qr = join('|', map(quotemeta($_ =~ s/\/+$//r =~ s/^(\.\/)?/.\//r) , @exclude));
qr/$qr/;
};

my @files;
my $dir = shift;
return unless -d $dir;
opendir my $dh, $dir;

while(my $file = readdir($dh))
{
$file = "$dir/$file";
next if $file =~ /\/[.]{1,2}$/;
next if $file =~ /$re/;
if (-f $file) {
push @files, $file;
} elsif (-d $file) {
@files = (@files, parse_dir($file));
}
}
closedir $dh;
@files;
}

Answer

$File::Find::prune can be used to avoid recursing into a directory.

use File::Find qw( find );

sub wanted {
   state $excluded_re = do {
      my @excluded = qw( .git .svn .cvs );
      my $pat = join '|', map quotemeta, @excluded;
      qr{(?:^|/)$pat\z/
   }

   if (/$excluded_re/) {
      $File::Find::prune = 1;
      return 0;
   }

   return -f;
}

my $root = 'foo';

my @files;
find({
   wanted   => sub { push @files, $_ if wanted() },
   no_chdir => 1,
}, $root);

This is the same approach one would take using the command line tool find.

find foo \( -name .git -o -name .svn -o -name .cvs \) -prune -o -print