2009-10-06 40 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