想象一个像下面这样的文本文件,其中 <some random text> 可以是任何东西或什么都没有,这意味着 KEYWORD 可以出现在行中的任何地方,单独或与其他文本一起出现:
1 <some random text>
2 <some random text>KEYWORD<some random text>
3 <some random text>KEYWORD<some random text>
4 <some random text>
5 <some random text>
6 <some random text>KEYWORD<some random text>
7 <some random text>
8 <some random text>KEYWORD<some random text>
9 <some random text>KEYWORD<some random text>
10 <some random text>KEYWORD<some random text>
11 <some random text>
12 <some random text>KEYWORD<some random text>
13 <some random text>KEYWORD<some random text>
14 <some random text>
15 <some random text>KEYWORD<some random text>
16 <some random text>
Run Code Online (Sandbox Code Playgroud)
如何获取包含关键字的2 个或更多连续行的最后一次出现(示例中的第 12 行和第 13 行)?明确地说,我对第 (8, 9, 10) 行不感兴趣,因为虽然它们包含关键字并且是连续的,但它们不是最后一行,也不是第 15 行,因为虽然它包含关键字并且是最后一行带有关键字,它不是 2 个或更多连续行的一部分。
记录这些带有模式的行序列,始终保留最后一组,一旦文件输出,您将拥有最后一组。
直截了当的方式
use warnings;
use strict;
use feature 'say';
die "Usage: $0 file(s)\n" if not @ARGV;
my $threshold = 2;
my (@buf, $cnt, @res);
while (<>) {
if (not /KEYWORD/) {
$cnt = 0 if $cnt;
@buf = () if @buf;
next
}
++$cnt;
push @buf, $_;
if ($cnt >= $threshold) {
@res = @buf; # excessive copying; refine if a problem
}
}
print for @res;
Run Code Online (Sandbox Code Playgroud)
(删除@ARGV
检查以允许STDIN
输入,它<>
在没有给出文件的情况下读取。)
笔记
行进入缓冲区,直到满足阈值条件(重复次数),并且计数器增加。在没有图案的线上,这些被重置
这只是一次在这里(只需要两个重复的行),所以它会更容易为以后处理的行复制到一个标量,以保存它,但使用任何阈值数组作品
一旦满足条件,就复制缓冲区。虽然需要对与阈值匹配的第一行执行此操作,但要覆盖@res
之前的行,以下重复行不需要复制整个数组 - 可以在阈值通过后添加该行。
这需要额外的踢踏舞;这是一种方法(经过最低限度的测试)
while (<>) {
if (not /KEYWORD/) {
$cnt = 0 if $cnt;
@buf = () if @buf;
next
}
++$cnt;
if ($cnt < $threshold) {
push @buf, $_;
}
elsif ($cnt == $threshold) {
@res = (@buf, $_);
}
else {
push @res, $_
}
}
Run Code Online (Sandbox Code Playgroud)
现在缓冲区在第一次添加到大于阈值的计数中时被复制,但在没有额外缓冲区副本的情况下添加了以下行。(如果这样的行序列非常少见,或者文件很小,这不会有明显的影响。)
如果您需要知道文件中的位置,这些将保存行号$.
以及行。
如果文件可能很大——这是唯一可以用它做的事情——我们可以使用相同的代码,但从文件末尾向后。一个模块是File::ReadBackwards。
为了说明增益,这里有一个程序通过向后读取文件来做同样的事情
use warnings;
use strict;
use feature 'say';
use File::ReadBackwards;
my (@buf, $cnt, @res);
my $threshold = 2;
my $bw = File::ReadBackwards->new(shift) or die $!;
#print $bw->readline until $bw->eof; exit; # test
while ( my $line = $bw->readline ) {
if (not $line =~ /KEYWORD/) {
last if @res >= $threshold;
$cnt = 0 if $cnt;
@buf = () if @buf;
next
}
++$cnt;
if ($cnt < $threshold) {
push @buf, $line;
}
elsif ($cnt == $threshold) {
@res = (@buf, $line);
}
else {
push @res, $line;
}
}
print for reverse @res;
Run Code Online (Sandbox Code Playgroud)
这将产生与从头开始读取的程序相同的输出。
我附加了 20 万次测试文件,文件大小为 111 Mb。第一个程序(按照注释进行调整)将 ~1.85 sec
放在上面(运行几次的平均值),而上面的程序则输入0.02 sec
. †
因此,对于足够大的文件,节省是甜蜜的;从后面阅读的小开销是完全看不见的。但是,在此过程中无法进行其他处理。此外,目标必须是可搜索的(文件),并且支持的操作很少;一方面,我们没有得到行号。
† 这是针对整个程序、启动和所有程序,time
在调用程序时在命令行上测量,并在几次运行中取平均值。
当我只对代码本身计时,使用Time::HiRes时,处理文件的运行时是
在在第二程序第四(第4次)的小数位,例如0.0003 sec
在第一个程序中,它当然是静止的1.8881 sec
或类似的