nowox nowox - 1 year ago 83
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
, but in the case of very big projects (under git or svn), the
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));

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

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


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));

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;

Answer Source

$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;

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

   return -f;

my $root = 'foo';

my @files;
   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