【问题标题】:How can I print a matching line, one line immediately above it and one line immediately below?如何打印匹配的行,在其上方一行,在其下方一行?
【发布时间】:2010-12-04 04:13:42
【问题描述】:

从 Bi 提出的一个相关问题中,我学会了如何打印匹配的行以及紧接其下方的行。代码看起来很简单:

#!perl
open(FH,'FILE');
while ($line = <FH>) {
    if ($line =~ /Pattern/) {
        print "$line";
        print scalar <FH>;
    }
}

然后,我在 Google 上搜索了一个不同的代码,该代码可以打印匹配行与紧接在它们上方的行。部分适合我的目的的代码是这样的:

#!perl

@array;
open(FH, "FILE");
while ( <FH> ) {
  chomp;
  $my_line = "$_";
  if ("$my_line" =~ /Pattern/) {
      foreach( @array ){
          print "$_\n";
      }
      print "$my_line\n"
  }
  push(@array,$my_line);
  if ( "$#array" > "0" ) {
    shift(@array);
  }
};

问题是我仍然不知道如何将它们放在一起。看来我的大脑正在关闭。有没有人有任何想法?

感谢您的帮助。

更新:

我想我有点感动。你们太有帮助了!也许有点跑题了,但我真的有说更多的冲动。

我需要一个能够搜索多个文件的内容并显示相关信息而无需单独打开每个文件的 Windows 程序。我尝试了谷歌搜索,两个应用程序,Agent Ransack 和 Devas,被证明是有用的,但它们只显示包含匹配查询的行,我还想查看相邻的行。然后即兴创作一个程序的想法突然出现在我的脑海中。几年前,我对一个 Perl 脚本印象深刻,它可以生成一个 Tomeraider 格式的 Wikipedia,这样我就可以在我的 Lifedrive 上轻松搜索 Wiki,我还在网上的某个地方读到 Perl 很容易学习,特别是对于像我这样的人来说没有任何编程语言的经验。然后几天前我开始自学 Perl。我的第一步是学习如何完成与“Agent Ransack”相同的工作,事实证明使用 Perl 并不难。我首先通过修改名为“Perl by Example”的书中使用的示例来学习如何搜索单个文件的内容并显示匹配的行,但我被困在那里。我对如何处理多个文件一无所知。书中没有找到类似的例子,或者可能是因为我太不耐烦了。然后我再次尝试谷歌搜索并被带到这里,我问了我的第一个问题“如何在 Perl 中搜索多个文件以查找字符串模式?”在这里,我必须说这个论坛真是太棒了;)。然后我查看了更多示例脚本,然后我昨天想出了以下代码,它很好地满足了我的初衷:

代码如下:

#!perl

$hits=0;
print "INPUT YOUR QUERY:";
chop ($query = <STDIN>);
$dir = 'f:/corpus/'; 
@files = <$dir/*>;
foreach $file (@files) {
open   (txt, "$file");

while($line = <txt>) {
if ($line =~ /$query/i) {   
$hits++;
print "$file \n $line";     
print scalar <txt>;
}
}
}
close(txt);
print "$hits RESULTS FOUND FOR THIS SEARCH\n";

在“corpus”文件夹中,我有很多文本文件,包括 srt pdf doc 文件,内容如下:

然后我把尸体扔了。

J'ai mis le corps dans une décharge。

我知道你有电线。

Je sais que tu as un micro.

现在我告诉你真相。

Alors je vais te dire la vérité。

基本上我只需要搜索一个英文短语并查看对应的法语短语,所以我昨天完成的脚本非常令人满意,只是如果我的脚本可以显示以上行以防万一我想搜索一个法语短语并检查英语。所以我正在尝试改进代码。实际上我知道“打印标量”有问题,但它很整洁,至少在大多数情况下都可以打印后续行)。我什至期待打印前一行而不是后续行的另一条魔法线 :) Perl 似乎很有趣。我想我会花更多的时间试图更好地理解它。并且按照 daotoad 的建议,我会研究你们慷慨提供的代码。再次感谢你们!

【问题讨论】:

  • 您可能需要考虑建立一个博客。 “我觉得我有点感动。”嗯,你呢?
  • 你是英国人吗?你写的有点可辨认的古典抒情风格。 :)
  • 通过示例学习 a 是一件很棒的事情。这个站点和 Perlmonks (perlmonks.org) 是 Perl 的重要资源。 SO 具有涵盖广泛主题的优势。 Perlmonks 具有专注于 Perl 的优势。我不想没有任何一个;)
  • 嗨,以太,所以你已经注意到了。好吧,我不是以英语为母语的人。我来自中国。我很惭愧地承认我的专业是英语语言文学。但我的英语真的很烂,书卷气十足,而且毫无希望:(看起来我总是很难意识到社交场合的细微差别。
  • 致思南:我不写博客。我曾经有一个,但我懒得每天更新:(但也许在以后的阶段。谁知道呢。好吧,我想我会向我的朋友推荐这个论坛。

标签: perl


【解决方案1】:

使用grep 可能会更容易,因为它允许在匹配之前和之后打印行。使用-B-A 分别打印比赛前后的上下文。见http://ss64.com/bash/grep.html

【讨论】:

  • 我也是这么想的,但是 OP 没有学到任何关于 Perl 的知识,除了可能将它用于所有事情。
  • +1 为工作提供合适的工具。在这种情况下,如果grep(1)(与Perl 的grep() 函数消除歧义)可用,则Perl 不是最佳 解决方案。此外,ack(1) 是一个类似(并且更强大(并且用 Perl 编写))的工具,这是一个了不起的小程序。
  • 我发布的问题只是我希望添加到我的应用程序的几个功能的一部分。我正在学习 Perl,没有任何其他语言的经验。但是我看到 grep 看起来很棒!我已经为网址添加了书签。
【解决方案2】:

这是 Pax 出色答案的现代化版本:

use strict;
use warnings;

open( my $fh, '<', 'qq.in') 
    or die "Error opening file - $!\n";

my $this_line = "";
my $do_next = 0;

while(<$fh>) {
    my $last_line = $this_line;
    $this_line = $_;

    if ($this_line =~ /XXX/) {
        print $last_line unless $do_next;
        print $this_line;
        $do_next = 1;
    } else {
        print $this_line if $do_next;
        $last_line = "";
        $do_next = 0;
    }
}
close ($fh);

请参阅Why is three-argument open calls with lexical filehandles a Perl best practice?,了解最重要更改的原因。

重要变化:

  • 3 个参数open
  • 词法文件句柄
  • 添加了strictwarnings 编译指示。
  • 用词法范围声明的变量。

微小的变化(风格和个人品味的问题):

  • 从 post-fix if 中删除了不需要的括号
  • 将 if-not 构造转换为 unless

如果您觉得此答案有用,请务必对 Pax 的原作投票。

【讨论】:

  • 从技术上讲,这是两个参数 :-) 但是 3-arg 的主要原因在这里并不存在,因为您可以完全控制文件名。将来我会接受所有这些建议,我通常只在我的初始版本不正常时添加严格和警告:-) 但是全局文件句柄避免是一个很好的选择。对不起'关于if's,它们最初是'if(){}',我后来在压缩代码时记得后缀版本。 +1。
  • @Pax,我不敢相信我错过了那个编辑!现在真的是3。我同意这些原因并不真正适用于这个脚本。尽管如此,为了与我的其他代码保持一致,并加强良好的实践,我仍会使用 3 arg 形式编写此代码。如果有充分的理由使用这两种 arg 形式(我不知道其中一种),我会使用它,并就原因发表评论。
【解决方案3】:

给定以下输入文件:

(1:first) Yes, this one.
(2) This one as well (XXX).
(3) And this one.
Not this one.
Not this one.
Not this one.
(4) Yes, this one.
(5) This one as well (XXX).
(6) AND this one as well (XXX).
(7:last) And this one.
Not this one.

这个小sn-p:

open(FH, "<qq.in");
$this_line = "";
$do_next = 0;
while(<FH>) {
    $last_line = $this_line;
    $this_line = $_;
    if ($this_line =~ /XXX/) {
        print $last_line if (!$do_next);
        print $this_line;
        $do_next = 1;
    } else {
        print $this_line if ($do_next);
        $last_line = "";
        $do_next = 0;
    }
}
close (FH);

产生以下内容,这就是我认为您所追求的:

(1:first) Yes, this one.
(2) This one as well (XXX).
(3) And this one.
(4) Yes, this one.
(5) This one as well (XXX).
(6) AND this one as well (XXX).
(7:last) And this one.

它基本上通过记住读取的最后一行来工作,当它找到模式时,它会输出它和模式行。然后它继续输出模式行再加上一条(使用$do_next 变量)。

其中还有一些技巧可以确保没有行被打印两次。

【讨论】:

  • +1 即使我不喜欢输出格式(我认为你不应该重复,即使我的回答是这样)。
  • 是的,小错误,现已修复 :-)
  • 请使用词法文件句柄和 3 参数打开。即使在像这样的简短脚本中没有什么大的理由避免使用全局变量,IMO,最好通过实践来养成良好的习惯。
  • 欢迎你提交一个更正确的答案,@daotoad :-) 这对你来说可能看起来很古老,但那是因为我真的只将 Perl 用于快速和肮脏的脚本,所以不需要使用更现代的东西。如果我需要更复杂的应用程序,我倾向于使用 Java。但是,您的观点已被采纳。
  • 好的,我发布了您的代码的“更新”版本。但是您应该得到清晰、高效的实施的赞誉。我所做的只是添加一些小的调整。
【解决方案4】:

您总是希望存储您看到的最后一行,以防下一行有您的模式并且您需要打印它。像在第二个代码 sn-p 中那样使用数组可能有点过头了。

my $last = "";
while (my $line = <FH>) {
  if ($line =~ /Pattern/) {
    print $last;
    print $line;
    print scalar <FH>;  # next line
  }
  $last = $line;
}

【讨论】:

  • 如果模式可能出现在连续的行上,那么您可能需要稍微改变一下。
  • 我同意@mobrule,但这可以通过将最后两个打印更改为print $last = $line; print $line = &lt;FH&gt;;,然后将$last = $line; 放入else 块中来解决。
  • @Chris Lutz,修复似乎不起作用。我测试了它,但它失败了。我尝试在“print $line”下添加“$last=$ine”,但没有成功。知道为什么吗?
【解决方案5】:
grep -A 1 -B 1 "search line"

【讨论】:

    【解决方案6】:

    我将忽略您的问题的标题,而将重点放在您发布的一些代码上,因为在不解释问题所在的情况下让此代码保持不变是非常有害的。您说:

    可以打印匹配行与紧接其上方的行的代码。部分适合我的目的的代码是这样的

    我将检查该代码。首先,您应该始终包含

    use strict;
    use warnings;
    

    在您的脚本中,尤其是因为您刚刚学习 Perl。

    @array;
    

    这是一个毫无意义的陈述。使用strict,您可以使用以下命令声明@array

    my @array;
    

    首选open 的三参数形式,除非在特定情况下不使用它有特定的好处。使用词法文件句柄,因为裸词文件句柄是包全局的,并且可能是神秘错误的来源。最后,在继续之前始终检查open 是否成功。所以,而不是:

    open(FH, "FILE");
    

    写:

    my $filename = 'something';
    open my $fh, '<', $filename
        or die "Cannot open '$filename': $!";
    

    如果你使用autodie,你可以逃脱:

    open my $fh, '<', 'something';
    

    继续:

    while ( <FH> ) {
      chomp;
      $my_line = "$_";
    

    首先,阅读常见问题解答(您应该在开始编写程序之前阅读此内容)。见What's wrong with always quoting "$vars"?。其次,如果您要将刚刚阅读的行分配给$my_line,您应该在while 语句中执行此操作,这样您就不会不必要地触摸$_。最后,您无需输入更多字符即可符合strict

    while ( my $line =  <$fh> ) {
        chomp $line;
    

    再次参考之前的常见问题。

      if ("$my_line" =~ /Pattern/) {
    

    为什么要再次插入$my_line

          foreach( @array ){
              print "$_\n";
          }
    

    要么使用显式循环变量,要么将其转换为:

    print "$_\n" for @array;
    

    因此,您再次插入 $my_line 并添加之前由 chomp 删除的换行符。没有理由这样做:

          print "$my_line\n"
    

    现在我们来到了促使我首先剖析您发布的代码的那一行:

      if ( "$#array" > "0" ) {
    

    $#array 是一个数字0 是一个数字&gt; 用于检查 LHS 上的 number 是否大于 RHS 上的 number。因此,无需将两个操作数都转换为字符串。

    此外,$#array@array 的最后一个索引,其含义取决于$[ 的值。我不知道这个语句应该检查什么。

    现在,你原来的问题陈述是

    打印匹配的行和紧接在它们上面的行

    当然,自然的问题是要打印匹配的“正上方”多少行。

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    use Readonly;
    Readonly::Scalar my $KEEP_BEFORE => 4;
    
    my $filename = $ARGV[0];
    my $pattern  = qr/$ARGV[1]/;
    
    open my $input_fh, '<', $filename
        or die "Cannot open '$filename': $!";
    
    my @before;
    
    while ( my $line = <$input_fh> ) {
        $line = sprintf '%6d: %s', $., $line;
        print @before, $line, "\n" if $line =~ $pattern;
        push @before, $line;
        shift @before if @before > $KEEP_BEFORE;
    }
    
    close $input_fh;
    

    【讨论】:

    • 非常感谢您的建议和详细的解释。谢谢!
    • 我已经在我的笔记本上记下了你们cmets的要点。再次感谢!
    【解决方案7】:

    命令行 grep 是完成此任务的最快方法,但如果您的目标是学习一些 Perl,那么您需要编写一些代码。

    我不会像其他人那样提供代码,而是谈谈如何编写自己的代码。我希望这有助于大脑锁定。

    • 阅读我的previous answer on how to write a program,它提供了一些关于如何开始解决问题的提示。
    • 浏览您拥有的每个示例程序,以及此处提供的示例程序,并准确注释它们的作用。对于您不了解的每个函数和运算符,请参阅 perldoc。您的第一个示例代码有一个错误,如果一行中有 2 行匹配,则不会打印第二个匹配之后的行。错误是指代码或规范错误,需要确定在这种情况下所需的行为。
    • 写出您希望程序执行的操作。
    • 开始用代码填空。

    这是第一阶段写作的草图:

    # This program reads a file and looks for lines that match a pattern.
    
    # Open the file
    
    # Iterate over the file
    # For each line
    #    Check for a match
    #    If match print line before, line and next line.
    

    但是如何得到下一行和上一行呢?

    这就是创造性思维的用武之地,方法有很多种,您只需要一种行之有效。

    • 您可以一次读一行,但要提前读一行。
    • 您可以将整个文件读入内存并通过索引数组来选择前一行和后一行。
    • 您可以读取文件并存储每行的偏移量和长度——随时跟踪哪些匹配。然后使用您的偏移数据提取所需的行。
    • 您可以一次读一行。随时缓存上一行。使用 readline 读取下一行进行打印,但使用 seek 和 tell 倒回句柄,以便检查“下”行是否匹配。

    这些方法中的任何一种,以及更多方法都可以充实到一个正常运行的程序中。根据您的目标和约束,任何一个都可能是该问题域的最佳选择。知道如何选择使用哪一个将随着经验而来。如果您有时间,请尝试两种或三种不同的方法,看看效果如何。

    祝你好运。

    【讨论】:

    • 好吧,我真的想说,我感谢您对这篇帖子的回答背后的想法。我想说更多但是这个评论框有字符输入限制,所以我更新了我的原始帖子。无论如何,谢谢。
    【解决方案8】:

    如果您不介意失去对文件句柄进行迭代的能力,您可以直接删除文件并迭代数组:

    #!/usr/bin/perl
    
    use strict; # always do these
    use warnings;
    
    my $range = 1; # change this to print the first and last X lines
    
    open my $fh, '<', 'FILE' or die "Error: $!";
    my @file = <$fh>;
    close $fh;
    
    for (0 .. $#file) {
      if($file[$_] =~ /Pattern/) {
        my @lines = grep { $_ > 0 && $_ < $#file } $_ - $range .. $_ + $range;
        print @file[@lines];
      }
    }
    

    对于大文件,这可能会变得非常慢,但很容易理解(在我看来)。只有当你知道它是如何工作的,你才能着手尝试优化它。如果您对我使用的任何功能或操作有任何疑问,请尽管提问。

    【讨论】:

    • 效率极低,但相当容易理解。为了便于阅读,我将 grep 替换为 my $start = $_ - $range; $start = 0 unless $start &gt;= 0;my $end = $_ + range; $end = $#lines unless $end &lt;= $#lines; 之类的东西,然后执行 print @file[$start..$end];
    • @daotoad - 过多的功能炒作让我认为grep() 在某种程度上更容易/更具可读性。我同意你的肯定更容易理解。
    • 这仍然有点超出我的能力 :( 好吧,无论如何我正在熟悉非常基础的知识,因此我认为我的问题最好留到后面阶段:) 非常感谢您的回答。
    • @Mike - 这不一定是最好的方法,但大部分是人们编写现代 Perl 的方式。例如,如果您从一本书或更早的教程中学习 Perl,您可能会看到 open FILE, "filename";,但 Perl 允许您使用变量(如 $fh )而不是文件句柄(如 FILE )——这是一种改进,因为变量可以限定范围,而文件句柄是全局的。除了grep().. 运算符之外,这里没有太多非常先进的东西。 (就像@daotoad 所说,grep() 不是这里的最佳答案。)
    • @Chris - 如果我错了,请纠正我。 (从 "for (0 .. $#file)" 行开始) # 读取从第一行(索引 0)到最后一行($#file)的内容。 #如果特定行号($_)上的特定行的内容与模式匹配,并且此行号($_)大于0且小于最后一个行号,则搜索此数字之和之间的数字减去 $range 和这个数字的总和加上 $range,然后将它们存储在名为“@lines”的容器中。显示在“@lines”中存储相应编号的每一行的内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-23
    • 2010-11-07
    • 1970-01-01
    • 1970-01-01
    • 2021-11-26
    相关资源
    最近更新 更多