这是一项相当规范的任务,而我无法使用可能最简单的工具找到直接的答案,方便的Path::Tiny
use warnings;
use strict;
use feature 'say';
use Data::Dump; # dd
use Path::Tiny; # path
my $dir = shift // '.';
my $pattern = qr/word/;
my $ret = path($dir)->visit(
sub {
my ($entry, $state) = @_;
return if not -f;
for ($entry->lines) {
if (/$pattern/) {
print "$entry: $_";
push @{$state->{$entry}}, $_;
}
}
},
{ recurse => 1 }
);
dd $ret; # print the returned complex data structure
此处使用lines 读取文件的方式只是实现此目的的一种方式。它可能不适合非常大的文件,因为它一次读取所有行,最好逐行读取。
visit 方法基于iterator,同样干净利落地完成了这项任务
my $iter = path($dir)->iterator({ recurse => 1 });
my $info;
while (my $e = $iter->()) {
next if not -f $e;
# process the file $e as needed
#/$pattern/ and push @{$info->{$e}}, $_ and print "$e: $_"
# for $e->lines
}
这里我们必须提供一个数据结构来积累信息,但我们获得了更大的灵活性。
上面使用的-f filetest 是一个“普通”文件,仍然有些宽松;例如,它允许交换文件,某些编辑器在会话期间保留这些文件(例如 vim)。这些将导致各种匹配。要保留纯 ASCII 或 UTF-8 文件,请使用 -T 测试。
如果程序最终使用 input 作为正则表达式中的模式,那么请非常小心。
否则,还有用于递归遍历和搜索的库,例如File::Find(或File::Find::Rule)或Path::Iterator::Rule。
为了完整起见,这里是核心File::Find
use warnings;
use strict;
use feature 'say';
my $dirs_csv = shift // '.';
my @dirs = split /,/, $dirs_csv;
my $pattern = qr/word/;
my %res;
find( sub {
return if not -T; # ASCII or UTF-8 only
open my $fh, '<', $_ or do {
warn "Error opening $File::Find::name: $!";
return;
};
while (<$fh>) {
if (/$pattern/) {
chomp;
push @{$res{$File::Find::name}}, $_
}
}
}, @dirs
);
for my $k (keys %res) {
say "In file $k:";
say "\t$_" for @{$res{$k}};
}