以递归方式列出所有文件但排除某些目录(.git .svn)

时间:2015-01-27 15:04:10

标签: perl

我想获取指定路径中的所有文件,但我想排除使用以下定义的某些目录中的所有文件:

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

最简单的方法使用File::Find,但是对于非常大的项目(在git或svn下),find子例程仍将遍历排除目录中的所有文件:

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

我发现仅涉及核心模块的唯一解决方案是手动迭代readdir。有更好的方法吗?

修改

一个有效的解决方案是下面的代码,但对于一些应该简单的事情来说似乎有点复杂......

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

3 个答案:

答案 0 :(得分:6)

$File::Find::prune可用于避免重复进入目录。

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

这与使用命令行工具find进行的方法相同。

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

答案 1 :(得分:4)

我的搜索工具确认(http://search.cpan.org/dist/ack)正是这样做的:它忽略.svn,.git和.cvs目录。

您描述的问题,您必须遍历整个树以在使用File :: Find时返回结果,这正是我编写File :: Next(http://search.cpan.org/dist/File-Next)的原因,以封装{{1你已经正确地断定了你需要的调用,而且它只给你文件而不是目录。

在File :: Next中的方法上面描述的内容大致如下:

readdir

我意识到你只想使用核心模块,但File :: Next不依赖于非核心模块。此外,如果你想要,你可以从File :: Next窃取迭代器代码,并将其直接放入你的项目中。它非常简单,它改编自优秀书籍 Higher Order Perl http://hop.perl.plover.com/)中的代码。

答案 2 :(得分:1)

在预处理中过滤掉要排除的名称

use File::Find qw( find );

my $root = '.';

find({
   wanted   => sub {} # whatever you do with each found entry
   preprocess => sub { grep(!/\.(git|svn|cvs|\.$)/,@_) }
}, $root);

从预处理回调中返回的是随后将处理的文件/目录名称列表。由于.git,.svn和.cvs不在那里,所以不会被看到和触及。

基于Perl: How to stop File::Find entering directory recursively?

中记录的内容,File::Find更详细的“外行人”解释

如果您只想要目录列表,请返回一个空列表。