在Perl中使用*修饰符进行否定前瞻性断言

时间:2012-04-27 11:45:44

标签: regex perl negative-lookahead regex-lookarounds

我有(我相信的)否定前瞻断言 <@> *(?!QQQ)如果测试的字符串是<@>后跟任意数量的空格我希望匹配(零包括)然后后跟QQQ

然而,如果测试的字符串是<@> QQQ,则正则表达式匹配。

我不明白为什么会出现这种情况,并希望对此事有任何帮助。

这是一个测试脚本

use warnings;
use strict;

my @strings = ('something <@> QQQ',
               'something <@> RRR',
               'something <@>QQQ' ,
               'something <@>RRR' );


print "$_\n" for map {$_ . " --> " . rep($_) } (@strings);



sub rep {

  my $string = shift;

  $string  =~ s,<@> *(?!QQQ),at w/o ,;
  $string  =~ s,<@> *QQQ,at w/  QQQ,;

  return $string;
}

打印

something <@> QQQ --> something at w/o  QQQ
something <@> RRR --> something at w/o RRR
something <@>QQQ --> something at w/  QQQ
something <@>RRR --> something at w/o RRR

我希望第一行是something <@> QQQ --> something at w/ QQQ

3 个答案:

答案 0 :(得分:10)

匹配,因为零包含在“任何数字”中。因此,没有空格,后跟空格,匹配“任意数量的空格后面没有Q”。

你应该添加另一个先行断言,即你的空格之后的第一件事本身就不是空间。试试这个(未经测试):

 <@> *(?!QQQ)(?! )

ETA 旁注:将量词更改为+只有在只有一个空格时才会有所帮助;在一般情况下,正则表达式总是可以占用更少的空间,因此成功。正则表达式想要匹配,并且会以任何可能的方式向后弯曲。所有其他考虑因素(最左边,最长等)都会退居二线 - 如果它可以匹配多种方式,它们会决定选择哪种方式。但匹配总是胜过不匹配。

答案 1 :(得分:7)

$string  =~ s,<@> *(?!QQQ),at w/o ,;
$string  =~ s,<@> *QQQ,at w/  QQQ,;

你的一个问题是你正在分别查看两个正则表达式。首先要求在没有QQQ的情况下替换字符串,然后用QQQ替换字符串。从某种意义上说,这实际上是两次检查相同的东西。例如:if (X==0) { ... } elsif (X!=0) { ... }。换句话说,代码可能写得更好:

unless ($string =~ s,<@> *QQQ,at w/  QQQ,) {
    $string =~ s,<@> *,at w/o,;
}

你总是要小心*量词。由于它匹配零次或多次,它也可以匹配空字符串,这基本上意味着:它可以匹配任何字符串中的任何位置。

负面环视断言具有相似的质量,因为它只需要找到一个不同的东西才能匹配。在这种情况下,它会将"<@> "部分与<@> +空格+空格匹配,其中空格当然是“不是”QQQ。你或多或少处于逻辑僵局,因为*量词和负前瞻相互反击。

我认为解决这个问题的正确方法是将正则表达式分开,就像我上面所说的那样。允许两个正则表达式都被执行的可能性是没有意义的。

然而,出于理论目的,需要锚定一个允许任意数量的空格,负面前瞻的工作正则表达式。很像Mark Reed所示。这可能是最简单的。

<@>(?! *QQQ)        # Add the spaces to the look-ahead

不同之处在于,现在空格和Qs相互锚定,而之前它们可以分别匹配。要将*量词的重点放在家中,并解决删除其他空格的小问题,您可以使用:

<@> *(?! *QQQ)

这将起作用,因为任何一个量词都可以匹配空字符串。从理论上讲,您可以根据需要添加任意数量的这些内容,但它们没有区别(性能除外):/ * * * * * * */在功能上等同于/ */。这里的区别在于可能不存在与Qs结合的空格。

答案 2 :(得分:4)

正则表达式引擎将回溯直到找到匹配,或直到找不到匹配为止。在这种情况下,它找到以下匹配:

                         +--------------- Matches "<@>".
                         |   +----------- Matches "" (empty string).
                         |   |       +--- Doesn't match " QQQ".
                         |   |       |
                        --- ----    ---
'something <@> QQQ' =~ /<@> [ ]* (?!QQQ)/x

你需要做的就是洗牌。取代

/<@>[ ]*(?!QQQ)/

/<@>(?![ ]*QQQ)/

或者你可以使正则表达式只匹配所有空格:

/<@>[ ]*+(?!QQQ)/
/<@>[ ]*(?![ ]|QQQ)/
/<@>[ ]*(?![ ])(?!QQQ)/

PS - 很难看到空格,因此我使用[ ]使它们更加明显。无论如何它都会被优化掉。