Regex Perl用“替换数字”;x";在字符串中,但仅在一个特定位置

Regex Perl用“替换数字”;x";在字符串中,但仅在一个特定位置,regex,perl,Regex,Perl,我正在解析一个充满各种错误的日志文件。这些都是网络错误,这意味着客户在格式化我们网站的日期时犯了错误。日志如下所示: 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: Custom

我正在解析一个充满各种错误的日志文件。这些都是网络错误,这意味着客户在格式化我们网站的日期时犯了错误。日志如下所示:

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
日期是另一个错误,尽管我以前报告过Customer#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(我的$error=){
chomp$错误;
而($error=~s/(\[.*?)\d/$1x/){
1.
}
说qq(错误:$Error);
}
但是必须有一种方法可以做到这一点,而不必在
中循环多次

是否有一种方法可以有效地将所有出现的数字替换为
x
,但只能替换为两个方括号之间的数字?

我会使用以下解决方案:

$error =~ s{(\[ [^\]]+ \])}{
  (my $date = $1) =~ tr/0-9/x/;
  $date;
}ex;
如果没有可重入的正则表达式引擎,这在旧的perl中是行不通的。显然,我错了。我用一个新的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/;
}

您不能一次完成所有操作。您需要提取要替换的部分,应用替换,然后重新生成字符串

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;

我总是喜欢将这些问题分解成更简单的部分:

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]

您可以使用:

$error =~ / \[ /gx;
$error =~ s/ \G (.*?) [0-9] /$1x/gx;
使用修饰符
/g
的搜索操作最初将锚点(即下一次搜索的起点)定位在匹配字符串的后面。然后替换操作从此点(
\g
)开始搜索并替换其后面的某个位置的第一个数字。由于
/g
,此外,锚点移动到被替换的数字后面,搜索+替换将重复,直到字符串结束(或者,使用
([^]]*?)
而不是
(.*)
,直到第一个结束括号)


在第一次尝试中,括号只找到一次;第一次替换将锚移动到被替换的数字后面,下一次搜索无法找到括号。使用
重新“调试”看到锚移动。

Perl需要有多新才能处理这个问题?我认为在此之前可以在替换表达式中使用正则表达式,只是不要在
(?{…})
(?{…})和
(?{…})
@ikegami中使用正则表达式。看起来你在这方面是对的,它在旧的Perl上运行得很好。@NathanielWaisbrot我错了,这在较旧的perl上运行良好。此外,您可以使用
tr/0-9/x/
而不是
s/[0-9]/x/g
;两者都可以工作,但是
tr//
的效率会稍微高一点。实际上我并没有忽略它。我要给客户举一个错误的例子,日期是
[21-10-2013]
,所以我也不会报告
[24-02-2013]
的日期。这些可能来自同一个错误。但是,如果还报告了日期
[12-10:]
,则客户站点中可能还有另一个错误。我想向客户报告这一点,但如果还有
[08-13://code>,则不需要报告。其思想是给出每种日期错误类型的示例。这就是为什么我需要
[…]
格式,而不是实际的数字。
$error =~ / \[ /gx;
$error =~ s/ \G (.*?) [0-9] /$1x/gx;