2014-01-27 18 views
6

我正在解析充滿各種錯誤的日誌文件。這些都是網絡錯誤,這意味着客戶在爲我們網站的日期格式化方面做了一些混亂。日誌看起來像這樣:Perl用字符串中的「x」替換數字,但僅在一個特定位置

Error 123: Customer 2: Bad Date [17/12/2014] 
Error 123: Customer 2: Bad Date [19/12/2014] 
Error 123: Customer 1: Bad Date [123/23/222] 
Error 123: Customer 2: Bad Date [null] 
Error 123: Customer 6: Bad Date [12/14:] 
Error 123: Customer 6: Bad Date [12/16:] 

現在,前兩個對於同一個客戶來說確實是相同的錯誤。兩行都將日期報告爲DD/MM/YYYY而不是YYYY/MM/DD,因此我不需要兩次報告此錯誤。最後兩行對同一個客戶也是同樣的錯誤。使用的MM/DD和離開年。 null日期是另一個錯誤,即使我報告客戶#2的錯誤日期錯誤之前。某處,他們傳遞了一個空日期。

我希望做的是比較線路是這樣的:現在

Error 123: Customer 2: Bad Date [xx/xx/xxxx] 
Error 123: Customer 2: Bad Date [xx/xx/xxxx] 
Error 123: Customer 1: Bad Date [xxx/xx/xxx] 
Error 123: Customer 2: Bad Date [null] 
Error 123: Customer 6: Bad Date [xx/xx:] 
Error 123: Customer 6: Bad Date [xx/xx:] 

,可以很容易地看到,前兩個和最後兩行實際上是一樣的錯誤。問題是如何用正則表達式來做到這一點。我想將[]之間的所有數字更改爲x,但我不想觸摸字符串的其餘部分,因此我不想將錯誤或客戶號碼轉換爲x

我第一次嘗試:

$error =~ s/(\[.*?)\d/$1x/g; 

但這僅觸及括號中的第一位。我已經嘗試過沒有非貪婪的限定符,但只觸及最後一個字符。

我可以簡單地這樣做:

$error =~ s/\d/x/g; 

但是,這取代了兩位數的所有出現的x毀了我的錯誤號和客戶號。

我可以反覆傳遞錯誤行再次,直到有沒有更多的替換:

while (my $error = <DATA>) { 
    chomp $error; 
    while ($error =~ s/(\[.*?)\d/$1x/) { 
     1; 
    } 
    say qq(Error: "$error"); 
} 

但是,必須有一種方法,我可以做到這一點,而無需通過while循環多次不必循環。

有沒有一種方法可以有效地用x代替所有出現的數字,但只能在兩個方括號之間?

+0

因爲無論如何你都忽略了信息,難道你不能只是從第一個'[? 's/\ [\ d。* //;' – toolic

+0

其實我並沒有忽視它。我要給客戶一個錯誤示例,其中日期是'[21-10-2013]',所以我也不會報告'[24-02-2013]'的日期。這些可能來自同一個錯誤。但是,如果還報告了日期「[12-10:]」,那麼客戶網站中可能存在另一個錯誤。我想向客戶報告一個,但如果還有'[08-13:]'',則不會。這個想法是提出每個日期錯誤類型的例子。這就是爲什麼我需要''格式,但不是實際的數字。 –

回答

5

我會使用此解決方案:

$error =~ s{(\[ [^\]]+ \])}{ 
    (my $date = $1) =~ tr/0-9/x/; 
    $date; 
}ex; 

沒有再入正則表達式引擎這是行不通的老年皮爾斯。顯然,我錯了。我試着用新鮮的代碼 - brewed perl 5.10.1,它工作得很好。

或者,你可能濫用左值substr

if ($error =~ /\[/gc) { 
    my $start = pos $error; 
    my $length = index($error, ']', $start) - $start; 
    substr($error, $start, $length) =~ tr/0-9/x/; 
} 
+0

Perl需要怎樣處理這個問題? –

+0

我認爲你可以在替換表達式中使用正則表達式,而不是在'(?{...})和'(?? {...})裏面' – ikegami

+0

@ikegami看起來你是對的這個,它在老版本的perls上工作得很好。 – amon

1

你不能做到這一切在一通。您需要提取您要替換的部分,應用替換,然後重新創建字符串。

if (
    my ($pre, $date, $post) = 
     /^ ([^\[\]]* \[)([^\[\]]*)(\] .*)/x 
) { 
    $date =~ s/[0-9]/x/g; 
    $_ = "$pre$date$post"; 
} 

這可以做得更簡潔。

s{ (\[ [^\[\]]* \]) } 
{ (my $x = $1) =~ s{[0-9]}{x}g; $x }xeg; 

或者,如果你有5.14,

s{ (\[ [^\[\]]* \]) } 
{ $1 =~ s{[0-9]}{x}rg }xeg; 
1

我總是喜歡打破這些問題成更簡單的部分:

sub xdigit 
{ 
    my $str= shift ; 
    $str =~ tr/[0-9]/xxxxxxxxxx/ ; 
    "[$str]" 
} 

my $x= 'Error 123: Customer 2: Bad Date [17/12/2014]' ; 
$x =~ s/\[(.*?)\]/xdigit($1)/e ; 

輸出:

錯誤123 :客戶2:錯誤日期[xx/xx/xxxx]

0
while($error =~ s/([\[\/x])\d/$1x/){};print 
+1

這對我不產生任何輸出,並且它使用'使用警告'產生警告消息。你有沒有嘗試運行你的Perl代碼? – toolic

+0

對不起,我忘了使用你的變量$錯誤,我用$ _。將其更改爲 while($ error =〜s /([\ [\/x])\ d/$ 1x /){};打印 –

+2

您應該更新您的答案並附上此說明。 – toolic

1

你可以使用:

$error =~/\[ /gx; 
$error =~ s/ \G (.*?) [0-9] /$1x/gx; 

與修改/g搜索操作最初定位錨(即匹配字符串後面的下一個搜索的起始點)。然後替換操作從這個點(\G)開始搜索並替換它後面的第一個數字。由於/g,此外,錨點被移動到替換數字後面,重複搜索+替換直到字符串結束(或者用([^]]*?)而不是(.*?),直到第一個右括號)。

在第一次嘗試中,括號只被找到一次;第一個替換將錨點移動到替換後的數字後面,下一次搜索無法找到括號。用use re 'debug';看到錨點移動。

相關問題