我一直在尝试编写一个正则表达式来验证文件,以确保它遵循特定的格式.该文件应该有一个版本();行后面跟一个或多个元素();块.
以下是有效文件的示例:
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
解决方法
非贪婪的比赛一旦满足就不会停止.它试图尽快继续.如果正则表达式的其余部分无法匹配,则仍会发生回溯 – 但对于非贪婪的量词,回溯意味着匹配更多.
避免这种情况的一种可能性在于回溯控制.例如,您可能希望在版本最初匹配后禁止回溯.我们可以通过(?> …)构造来做到这一点.这与外部模式独立地匹配包含的模式.如果模式的其余部分失败,则回溯将不会继续进入包含的模式,但会跳过整个包含的模式.描述这个有点困难,详情请见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 )