我一直在尝试编写一个正则表达式来验证文件,以确保它遵循特定的格式。该文件应该有version();
行,然后是一个或多个element();
块。
以下是有效文件的示例:
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似乎仍然认为这个文本是有效的!当我运行此代码时,它会生成输出:
match
我花了好几个小时试图找出我的正则表达式出了什么问题,但我只是没有看到它。我甚至使用online regular expression tester测试了正确的正则表达式,根据测试我的正则表达式应该正常工作! (如果要在格式有效时看到它确实匹配,请尝试删除“垃圾”行。)
这让我整天彻底难过,让我想知道Perl正则表达式引擎本身是否存在错误。有人可以告诉我为什么这不匹配,不应该吗?
我使用的是perl v5.20.1
答案 0 :(得分:4)
来自http://www.pcre.org/current/doc/html/pcre2compat.html的PCRE文档:
- 子程序调用(无论是否递归)被视为直到PCRE2版本10.23的原子组,但是从版本10.30开始,这已经改变,现在支持回溯到子程序调用,如Perl。
醇>
regex101使用PHP运行PCRE。根据{{3}},PHP仅支持PCRE1(8.x分支)。因此,regex101不支持回溯到子程序调用。
......这正是这里发生的事情:
(?&valid_text>)
并尝试匹配\A\s*(?&version)\s*
\A
(字符串开头)和\s*
(可选空格)很简单(?&version)
执行version\(.+?\);
这匹配输入的以下部分:
version();
element
(
);
version(
字面匹配。下一个字符)
由.+?
使用(需要至少一个字符匹配)。然后.+?
慢慢消耗越来越多的字符(它不贪婪),直到它达到);
。第一次发生这种情况是在消费; element (
之后,这就是我们现在停止的地方。
(?&version)
调用返回(?: (?&element) \s* )+
,即一个或多个元素,每个元素后跟可选的空格(?&element)
执行element\s*
,即必须以element
garbage ...
,因此无法此时正则表达式引擎试图回溯。在PCRE中&lt; 10.30,唯一可以回溯的部分是\s*
(即&#34;可选的空格&#34;位),但匹配较少的空白字符也不会导致成功匹配,所以整个事情失败很快。
然而,在Perl中我们可以回溯到子例程调用:我们重新输入(?&version)
并让.+?
匹配更多字符(直到找到);
的下一个出现),然后重试(?&element)
。这最终会让(?&version)
消耗garbage
和以下element
,从而使整个正则表达式成功。
有人可以告诉我为什么这不匹配的时候不应该?
我不明白为什么你认为它不应该匹配。 : - )
它在PHP中不匹配的唯一原因是它使用的旧PCRE版本的限制。
答案 1 :(得分:3)
非贪婪的比赛一旦满足就不会停止。它试图尽快继续。如果正则表达式的其余部分无法匹配,则仍会发生回溯 - 但对于非贪婪的量词,回溯意味着匹配更多。
避免这种情况的一种可能性在于回溯控制。例如,您可能希望在version
最初匹配后禁止回溯。我们可以通过(?> ...)
构造做到这一点。这与外部模式独立地匹配包含的模式。如果模式的其余部分失败,则回溯将不会继续进入包含的模式,但会跳过整个包含的模式。描述这个有点困难,请参阅perldoc perlre
了解详细信息。
将+
添加到量词(例如++
,?+
,*+
)与(?> ...)
具有类似的效果。在高效的正则表达式中,首选这些无回溯量词和(?>...)
组是非常可取的。
具体来说,替换
(?<valid_text>
\A\s*(?&version)\s*
(?: (?&element) \s* )+
\s*\Z
)
与
(?<valid_text>
\A\s*(?>(?&version))\s*
(?: (?&element) \s* )++
\s*\Z
)
作为另一种选择,您可以使用(*PRUNE)
回溯控制动词。遇到PRUNE命令后,不会发生超过该点的回溯。这使得匹配到目前为止选择的替代方案。
(?<valid_text>
\A\s*(?&version)\s* (*PRUNE)
(?: (?&element) \s* )+
\s*\Z
)