2009-10-06 68 views
4

从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); 
    } 
}; 

问题是我仍然无法弄清楚如何在一起做他们。似乎我的大脑正在关闭。有没有人有任何想法?

感谢您的任何帮助。

UPDATE:

我想我有点感动。你们很有帮助!也许有点偏离主题,但我真的感到有更多的冲动。

我需要一个Windows程序能够搜索多个文件的内容和显示相关信息,而无需单独打开每个文件。我尝试了谷歌搜索和两个应用程序,代理Ransack和Devas,已被证明是有用的,但他们只显示包含匹配查询的行,我也希望偷看相邻的行。然后,即兴创作一个程序的想法突然出现在我的脑海中。几年前,我对Perl脚本印象深刻,它可以生成维基百科的Tomeraider格式,这样我就可以在我的Lifedrive上轻松地搜索Wiki,并且我还在网上的某个地方阅读过Perl很容易学习,特别是对于像我这样的人在任何编程语言中都没有经验。几天前,我开始自学Perl。我的第一步是学习如何完成“代理Ransack”的工作,并证明使用Perl并不困难。我首先学习了如何搜索单个文件的内容,并通过修改书中标题为“Perl by Example”的示例来显示匹配的行,但我被困在那里。对于如何处理多个文件,我变得完全无能为力。书中没有发现类似的例子,或者因为我太急躁了。然后我再次尝试使用Google搜索,并在这里被引导,并且问了我的第一个问题:“如何在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"; 

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

然后我倾倒了尸体。

J'ai mis le le corps dans unedécharge。

我知道你有电线。

Je sais que tu as un micro。

现在我会告诉你实情。

Alors je vais te dire lavérité。

基本上我只需要搜索一个英文短语并查看法语等价物,所以我昨天完成的脚本非常令人满意,只是如果我的脚本可以显示上面的行以防万一我想搜索一个法语短语并检查英语。所以我正在尝试改进代码。其实我知道“印刷标量”是越野车,但它很整洁,并且至少在大多数时间印刷下一行)。我甚至期待打印上一行而不是随后的其他单行魔术线:) Perl似乎很有趣。我想我会花更多的时间试图更好地理解它。正如daotoad所建议的那样,我会研究你们慷慨提供的代码。再次感谢你们!

+1

您可能想考虑获取博客。 *“我觉得我有点感动。”*好吧,是吗? – 2009-10-06 13:07:53

+0

你是英国人吗?你写在一个有点可识别的古典抒情风格。 :) – Ether 2009-10-06 15:02:31

+2

通过实例学习是一件了不起的事情。这个站点和Perlmonks(http://perlmonks.org)是Perl的很好的资源。 SO具有涉及广泛主题的优势。 Perlmonks的优点是专注于Perl。我不希望没有任何一方;) – daotoad 2009-10-06 18:09:19

回答

5

鉴于以下输入文件:

(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. 

这个小片段:

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变量)。

这里还有一点小小的诡计,以确保没有行打印两次。

+0

+1,尽管我不喜欢输出格式(即使我的答案确实如此,我认为你不应该重复)。 – 2009-10-06 06:27:16

+0

是的,轻微的错误,现在修复:-) – paxdiablo 2009-10-06 06:32:39

+4

请使用词法文件句柄和3参数打开。尽管在这样一个简短的脚本中,没有很大的理由来避免全局变量,IMO最好通过练习来培养良好的习惯。 – daotoad 2009-10-06 06:49:02

5

您总是希望存储您看到的最后一行,以防下一行有您的模式,并且您需要打印它。使用像你在第二个代码片段中做的数组可能是矫枉过正。

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

如果图案可能出现在连续的线条上,那么您可能需要稍微改变一点。 – mob 2009-10-06 06:13:47

+0

太棒了!代码像魔术一样工作!谢谢谢谢谢谢! – Mike 2009-10-06 06:19:11

+0

我同意@mobrule,但可以通过将最后两个打印改为'print $ last = $ line; print $ line = ;'然后把'$ last = $ line;'放在'else'块中。 – 2009-10-06 06:20:21

10

只要使用grep就可以了,因为它允许在匹配之前和之后打印行。使用-B-A分别在比赛前后打印上下文。见http://ss64.com/bash/grep.html

+5

我也这么认为,但是OP没有学习任何有关Perl的知识,除非**不**可以将它用于一切。 – pavium 2009-10-06 06:17:59

+4

+1为工作的正确工具。在这种情况下,如果'grep(1)'(从Perl的'grep()'函数中消除歧义)是可用的,则Perl不是_best_解决方案。另外,一个类似的(也是更强大的(用Perl编写的))工具就是'ack(1)',这是一个非常棒的小程序。 – 2009-10-06 06:18:59

+1

我发布的问题只是我希望添加到我的应用程序中的几个功能的一部分。我正在学习Perl,没有任何其他语言的经验。但是我看到grep看起来很棒!我已经为网址添加了书签。 – Mike 2009-10-08 04:08:21

4
grep -A 1 -B 1 "search line" 
1

如果你不介意输给遍历文件句柄的能力,你可以只发出声音文件,并遍历数组:

#!/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]; 
    } 
} 

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

+0

非常低效,但相当容易理解。为了便于阅读,我将grep替换为'my $ start = $ _ - $ range; $ start = 0,除非$ start> = 0;'和'my $ end = $ _ + range; $ end = $#行除非$ end <= $#行;'然后执行'print @file [$ start .. $ end];' – daotoad 2009-10-06 07:05:07

+0

@daotoad - 太多的功能炒作让我觉得'grep() '在某种程度上更容易/更具可读性。我同意你的确很容易理解。 – 2009-10-06 22:13:57

+0

这仍然有点超出我:(嗯,无论如何,我正在熟悉非常非常基础的过程中,因此我想我的问题会更好保留为后期:)我真的很感谢您的答案。 – Mike 2009-10-08 04:18:45

2

命令行grep是实现此目的的最快方法,但如果您的目标是学习一些Perl,那么您需要生成一些代码。

与其他人已经完成的代码不同,我会谈谈如何编写自己的代码。我希望这可以帮助大脑锁定。

  • 请阅读我的previous answer on how to write a program,它提供了一些关于如何开始解决问题的提示。
  • 仔细阅读您所有的示例程序,以及这里提供的示例程序,并准确评论它们的功能。请参阅perldoc了解您不了解的每个功能和操作员。您的第一个示例代码有错误,如果一行中的两行匹配,第二个匹配后的行不会打印。由于错误,我的意思是代码或规范是错误的,在这种情况下需要确定所需的行为。
  • 写出你想让你的程序做什么。
  • 用代码开始填充空白。

这里是一个阶段一个写了一个小品:

# 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来倒回句柄,以便可以检查“下一行”是否匹配。

任何这些方法,还有更多可以充实到功能程序。根据您的目标和约束条件,任何人都可能是该问题域的最佳选择。知道如何选择使用哪一种将带有经验。如果你有时间,尝试两种或三种不同的方式,看看他们如何解决问题。

祝你好运。

+0

嗯,我真的很想说,我很欣赏你对这篇文章的回答背后的想法。我想说更多,但这个评论框有字符输入限制,所以我更新了我的原始发布。无论如何,谢谢。 – Mike 2009-10-06 12:39:11

7

这里是大同的出色答卷的现代化版本:

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编译指示。
  • 用词法范围声明的变量。

的微小变化(的风格和个人喜好的问题):

  • 去除不需要从括号后的修复if
  • 转换的,如果,不contstruct为unless

如果你觉得这个答案很有用,一定要赞成票大同的原件。

+1

从技术上讲,这是两个参数:-)但是,3-arg的主要原因并不存在,因为你完全可以控制文件名。我将在将来采用所有这些建议,严格和警告我通常只在我的初始版本不行为时才会添加:-)但全局文件句柄避免是一个好办法。对不起,'如果',他们最初是'if(){}',我记得在压缩代码后的后缀版本。 +1。 – paxdiablo 2009-10-06 07:51:51

+1

@Pax,我不敢相信我错过了编辑!现在真的是3。我同意这个原理不适用于这个脚本。尽管如此,为了与我的其他代码保持一致,我仍然会使用3 arg表单编写此代码,并强化一个良好的做法。如果有充分的理由使用这两个arg表单(不是我所知道的),我会使用它,并留下评论为什么。 – daotoad 2009-10-06 09:17:27

2

我将忽略您的问题的标题,并专注于您发布的某些代码,因为让代码处于无法解释其错误的状态是有害的。你说:

代码,可以打印匹配行与他们上面的直线。代码将部分适合我的目的是这样的

我要通过该代码。首先,您应该始终在脚本中包含

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 = "$_"; 

第一,阅读FAQ(你应该这样做开始编写程序)。见What's wrong with always quoting "$vars"?。其次,如果您要将刚刚阅读的行分配给$my_line,则应该在while声明中执行此操作,以免您不必要地触摸$_。最后,你可以strict兼容,而无需输入任何更多的字符:

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

请参阅前一个FAQ一次。

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号码>用于检查在LHS比在RHS越大。因此,不需要将两个操作数转换为字符串。

此外,$#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; 
+0

非常感谢您的建议和详细的解释。谢谢! – Mike 2009-10-09 10:53:55

+1

我在笔记本上写下了您评论的要点。再次感谢! – Mike 2009-10-09 10:59:55

+0

@Mike:不客气。 – 2009-10-09 11:01:11