正则表达式工作,但收到警告:在正则表达式错误中多次匹配空字符串

时间:2016-06-03 23:55:28

标签: regex perl regex-lookarounds

我有一个字符串,其中包含我需要提取的许多组件。它们形成良好且可预测,但它们出现的顺序各不相同。下面是一个片段,说明字符串可能是什么样子以及我用来提取我需要的信息的正则表达式。这段代码有效,我得到了预期的输出。

my $str1 = '(test1=cat)(test2=dog)(test3=mouse)';         # prints cat\ndog\mouse
$str1 = '(test1=cat)(test3=mouse)(test2=dog)(test1=cat)'; # prints cat\ndog\nmouse
$str1 = '(test3=mouse)(test1=cat)';                       # prints cat\nempty\nmouse
$str1 = '(test3=mouse)(test2=dog)';                       # prints empty\ndog\nmouse
my $pattern1 = '(?=.*\(test1=(.*?)\))*(?=.*\(test2=(.*?)\))*(?=.*\(test3=(.*?)\))*';

if (my @map = $str1 =~ /$pattern1/) {
    foreach my $match (@map) {
        say $match if $match;
        say "empty" if !$match;
    }
}

上述最后一个字符串的预期和收到结果如下:

empty
dog
mouse

但是,除了预期的响应之外还有以下警告:

(?=.*\(test1=(.*?)\))* matches null string many times in regex; marked by <-- HERE in m/(?=.*\(test1=(.*?)\))* <-- HERE (?=.*\(test2=(.*?)\))*(?=.*\(test3=(.*?)\))*/ at /path/to/scratch1.pl line 32.
(?=.*\(test2=(.*?)\))* matches null string many times in regex; marked by <-- HERE in m/(?=.*\(test1=(.*?)\))*(?=.*\(test2=(.*?)\))* <-- HERE (?=.*\(test3=(.*?)\))*/ at /path/to/scratch1.pl line 32.
(?=.*\(test3=(.*?)\))* matches null string many times in regex; marked by <-- HERE in m/(?=.*\(test1=(.*?)\))*(?=.*\(test2=(.*?)\))*(?=.*\(test3=(.*?)\))* <-- HERE / at /path/to/scratch1.pl line 32.

这告诉我,虽然我的正则表达式有效但可能会有一些问题。

如何在消除警告的同时调整上述正则表达式以继续按预期工作?

以下是我必须处理的一些限制因素:

  • 必须保持结果的顺序(例如,“test1”将始终是数组的第一个元素)
  • 字段名称实际上不是“testN”,我必须使用许多独特的名称,这些是静态值
  • 重复没问题,但应该使用最后一个(上面的脚本执行此操作)

我通常不会使用外观,因此我的错误可能很简陋(希望如此)。任何建议或反馈都非常感谢。谢谢!

编辑 - 运行Perl 5.20

1 个答案:

答案 0 :(得分:2)

多次匹配前瞻(?=...)没有意义。它不会消耗对象字符串中的任何数据,因此如果它匹配一次,它将无限期地匹配

您需要做的主要更改是将(?=.*\(test1=(.*?)\))*等替换为(?=.*\(test1=(.*?)\))?。这只是使你的前瞻“可选”,并将摆脱你的警告

use strict;
use warnings 'all';

use Data::Dump;

my $pattern = qr/
    (?= .* \( test1= (.*?) \) )?
    (?= .* \( test2= (.*?) \) )?
    (?= .* \( test3= (.*?) \) )?
/x;

my @strings = qw/
    (test1=cat)(test2=dog)(test3=mouse)
    (test1=cat)(test3=mouse)(test2=dog)(test1=cat)
    (test3=mouse)(test1=cat)
    (test3=mouse)(test2=dog)
/;

for my $str ( @strings ) {

    next unless my @map = $str =~ /$pattern/;

    $_ //= 'empty' for @map;

    dd \@map;
}

输出

["cat", "dog", "mouse"]
["cat", "dog", "mouse"]
["cat", "empty", "mouse"]
["empty", "dog", "mouse"]

然而,这听起来像是另一个让单个正则表达式模式做太多工作的情况。你是用Perl写的,为什么不用呢?

以下代码假定与上面的完整程序相同的标题,包括@strings的定义。 for循环就是我所有改变的

for my $str ( @strings ) {
    my @map = map {  $str =~ / \( test$_= ( [^()]* ) \)/x ? $1 : 'empty' } 1 .. 3;
    dd \@map;
}

输出

["cat", "dog", "mouse"]
["cat", "dog", "mouse"]
["cat", "empty", "mouse"]
["empty", "dog", "mouse"]

或者可能是不同的东西是合适的。哈希对于这类事情很有用

for my $str ( @strings ) {
    my %map = $str =~ / \( ( test\d+ ) = ( [^()]* ) \) /gx; 
    dd \%map;
}

输出

{ test1 => "cat", test2 => "dog", test3 => "mouse" }
{ test1 => "cat", test2 => "dog", test3 => "mouse" }
{ test1 => "cat", test3 => "mouse" }
{ test2 => "dog", test3 => "mouse" }