Regex Perl正则表达式引擎错误?

Regex Perl正则表达式引擎错误?,regex,perl,validation,debugging,pcre,Regex,Perl,Validation,Debugging,Pcre,我一直在尝试编写一个正则表达式来验证文件,以确保它遵循特定的格式。该文件应具有版本()行,然后后跟一个或多个元素()块 以下是一个有效文件的示例: version(1.0); element ( ); element ( ); element ( ); 作为测试,我创建了以下Perl示例: use strict; use warnings; my $text = <<'END_TEXT'; version(1.0); element ( ); garbage <-

我一直在尝试编写一个正则表达式来验证文件,以确保它遵循特定的格式。该文件应具有
版本()行,然后后跟一个或多个
元素()

以下是一个有效文件的示例:

version(1.0);

element
(
);

element
(
);

element
(
);
作为测试,我创建了以下Perl示例:

use strict;
use warnings;

my $text = <<'END_TEXT';
version(1.0);

element
(
);

garbage <--- THIS SHOULD NOT MATCH!

element
(
);

element
(
);

END_TEXT

my $rx_defs = qr{(?(DEFINE)
    (?<valid_text>
        \A\s*(?&version)\s*
        (?: (?&element) \s* )+
        \s*\Z
    )
    (?<version>
        version\(.+?\);
    )
    (?<element>
        element\s*
        (?&element_body);
    )
    (?<element_body>
        \( (?: [^()]++ | (?&element_body) )* \)
    )
)}xms;

if ($text =~ m/(?&valid_text)$rx_defs/) {
    print "match";
}
我花了几个小时试图找出正则表达式的错误,但我就是看不到它。我甚至使用了一个测试来测试正则表达式,根据测试,我的正则表达式应该可以正常工作!(如果希望在格式有效时查看“垃圾”行是否正确匹配,请尝试删除该行。)

这让我一整天都感到困惑,我想知道Perl正则表达式引擎本身是否存在bug。有人能告诉我为什么这是匹配的,而它不应该


我使用的是perl v5.20.1

非贪婪匹配不会在满足时立即停止。它试图尽快继续。如果正则表达式的其余部分无法匹配,那么回溯仍然会发生——但对于非贪婪量词,回溯意味着匹配更多

避免这种情况的一种可能性在于回溯控制。例如,您可能希望在
版本
初始匹配后禁止回溯。我们可以通过
(?>…)
构造来实现这一点。这与包含的模式独立于外部模式进行匹配。如果模式的其余部分失败,回溯将不会继续到包含的模式中,而是跳过整个包含的模式。描述这一点有点困难,请参阅了解详细信息

++
添加到量词(如
++
?++
*++
)具有与
(?>…)
类似的效果。在高效正则表达式中,最好使用这些无回溯量词和
(?>…)

具体来说,替换

(?<valid_text>
    \A\s*(?&version)\s*
    (?: (?&element) \s* )+
    \s*\Z
)

来自PCRE文档,网址为:

  • 直到PCRE2 10.23版,子例程调用(无论是否递归)一直被视为原子组,但从10.30版开始,这种情况发生了变化,现在支持回溯到子例程调用,就像在Perl中一样
  • regex101使用PHP运行PCRE。据介绍,PHP只支持PCRE1(8.x分支)。因此,regex101不支持回溯到子例程调用中

    。。。这就是这里发生的事情:

    • 我们进入
      (?&valid\u text>)
      并尝试匹配
      \A\s*(?&version)\s*
    • \A
      (字符串开头)和
      \s*
      (可选空格)很简单
    • (?&version)
      不执行
      version\(.+?\)
    • 这与输入的以下部分匹配:

      version();
      
      element
      (
      );
      
      版本(
      按字面匹配。下一个字符
      +?
      (至少需要一个字符匹配)使用。然后
      +?
      慢慢地消耗越来越多的字符(它不是贪婪的),直到到达
      。第一次发生这种情况是在消费
      之后;元素(
      ),所以我们现在就到此为止

    • (?&version)
      调用返回
    • 我们使用以下任何空格
    • 下一部分是
      (?:(?&element)\s*)+
      ,即一个或多个元素,每个元素后跟可选空格
    • (?&element)
      不执行
      element\s*
      ,即它必须以
      element
    • 我们当前在输入中的位置是
      垃圾…
      ,因此此操作失败
    此时正则表达式引擎尝试回溯。在PCRE<10.30中,唯一可以回溯的部分是
    \s*
    (即“可选空白”位),但匹配较少的空白字符也不会导致成功匹配,因此整个过程很快失败

    但是,在Perl中,我们可以回溯到子例程调用:我们重新输入
    (?&version)
    ,让
    +?
    匹配更多字符(直到找到下一个
    );
    ),然后重试
    (?&element)
    。这最终会让
    (?&version)
    消耗
    垃圾
    和下面的
    元素
    ,从而允许整个正则表达式成功

    有人能告诉我为什么这是匹配的,而它不应该

    我不明白你为什么认为它不匹配。:-)


    它在PHP中不匹配的唯一原因是它使用的旧PCRE版本有限制。

    这一点看起来可疑:
    version\(.+?\)–paren可以包含元素和垃圾。考虑限制内容,例如“<代码> [^ ] ] + < /代码>。但是怎么办???一个非贪婪的匹配应该在满足后立即停止!还有,为什么这在perl中匹配,而在使用“PCRE”作为regex类型的regex测试网站上不匹配?它们的功能应该相同,不是吗?一个非常酷的正则表达式,毫无疑问,正则表达式是一个伟大的工具,毫无疑问。。。但我忍不住要说:这很难,但可以通过使用处理嵌套/平衡分隔符的工具来避免。提示:这绝不是引擎/语言/编译器的bug;这总是一个用户错误。好。。。除非。。。但这是一长串可能的行为原因中的最后一件事。(我会重新考虑这样的标题;你真的确定吗?)我试图解析的真实文本在
    version()
    的括号之间有文本。我的例子似乎有点过于简单化了,但即使有其他的
    +?
    文本,它仍然匹配。我想这是正确的行为,尽管它非常令人困惑。@tjwrona1992任何时候你在正则表达式中有
    *
    +
    ,这都是一个潜在的错误
    *
    可以并且将跳过任何文本,如果这是使整个正则表达式成功的必要条件(除非我
    (?<valid_text>
        \A\s*(?>(?&version))\s*
        (?: (?&element) \s* )++
        \s*\Z
    )
    
    (?<valid_text>
        \A\s*(?&version)\s* (*PRUNE)
        (?: (?&element) \s* )+
        \s*\Z
    )
    
    version();
    
    element
    (
    );