我想获取指定路径中的所有文件,但我想排除使用以下定义的某些目录中的所有文件:
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;
}
答案 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更详细的“外行人”解释如果您只想要目录列表,请返回一个空列表。