Perl打开多个文件并搜索一个字符串,并在每次匹配时打印前10行

时间:2018-04-04 13:24:11

标签: perl

我有一个正在打开文件的工作Perl脚本,搜索文本字符串。当字符串匹配时,它每次都会打印前面的10行。

我的问题是我怎样才能使其适应目录中的多个文件?

#!/usr/bin/env perl

use strict;

my $file = "myfile.txt";

open   (LOGFILE, $file);
my @cont = <LOGFILE>;
close(LOGFILE);

for(my $i = 0; $i <= $#cont; $i++) {
my $line = $cont[$i];

if ($line =~ /Voice VLAN: [0-9]/i) {
  my $st;
  ($i <= 0) ? ($st = 0) : ($st = $i - 10);
  my $ln = $i - 1;

  my $eln = $i + 1;
  my $en = $i + 0;
  ($en > $#cont) ? ($en = $#cont) : ();

  print @cont[$st..$ln];
  print $line;
  print @cont[$eln..$en];

}
}

2 个答案:

答案 0 :(得分:2)

没有必要将整个文件读入内存,保留前一行的缓冲区就足够了 将算法应用于多个文件非常简单:只需打开文件,处理并关闭即可 这是一个模仿grep -A x -B y的通用解决方案,其中$ B是前面行的计数,$ A是匹配后要打印的以下行数:

<强> grep_AB.pl

use strict; use warnings;
my $filter=qr/match/;
my ($A,$B)=(1,1);

for my $file(@ARGV) {
    open my $fh, '<', $file or die "$file:$!\n"; 
    my (@buffer,$tail);
    while(<>) {
        if (m{$filter})  {
            $tail=1+$A;
            print for @buffer;
            @buffer=();
        }
        if ($tail-->0) {
            print;
        }
        else {
            push @buffer, $_;
            shift @buffer if @buffer>$B;
        }
    }
    close $fh;
}

给出以下输入(input.txt):

1
2
3
match
match
4
match
5
6
match
7
8
9
10
match
11
12

perl grep_AB.pl input.txt的输出是:

3
match
match
4
match
5
6
match
7
10
match
11

答案 1 :(得分:1)

如果您可以在命令行上指定文件:

use warnings;
use strict;

my @buf;
while (<>) {
     push @buf, $_;
    print @buf if /Voice VLAN: [0-9]/i;
    shift @buf if @buf>10;
}

如果你想在脚本中指定文件,你可以在&#34; hack it&#34;在循环之前说local @ARGV = ('myfile.txt');。虽然更干净的解决方案,例如,如果此代码是较长脚本的一部分,则是:

use warnings;
use strict;

my @files = ('myfile.txt');

for my $file (@files) {
    open my $fh, '<', $file or die "$file: $!";
    my @buf;
    while (<$fh>) {
         push @buf, $_;
        print @buf if /Voice VLAN: [0-9]/i;
        shift @buf if @buf>10;
    }
    close $fh;
}

如果您愿意,也可以在原始代码上使用相同的循环,如评论中提到的@choroba。

更新:如果你想在输出前加上文件名,你可以修改上面第二个例子中的print,我希望这是相当不言自明的:

if ( /Voice VLAN: [0-9]/i ) {
    for my $line (@buf) {
        print "$file: $line";
    }
}

或者,如果您更喜欢较短的版本,可以将第一个示例中的print更改为:

print map {"$ARGV: $_"} @buf if /Voice VLAN: [0-9]/i;

做了非常相似的事情。我已使用map代替for来循环数组,这意味着print仅使用字符串列表调用一次。另外,我正在获取&#34; magic&#34;的文件名。 <>运营商目前正在阅读$ARGV