PHP preg_match正则匹配bug:带something_happened前缀时失效
问题根源
- 你的正则未限定类型名的边界,导致PCRE引擎会在开头的
something_happened这类连续单词序列的每一个字符位置,都尝试匹配类型名的规则,产生大量无意义回溯。 - 当回溯次数超过PHP配置的
pcre.backtrack_limit(部分环境默认值较小)时,引擎会直接终止匹配流程,返回空结果,不会继续扫描后面的真正匹配位置。 - 替换为短单词后回溯次数大幅减少,未超过限制,因此引擎可以正常扫描到后面的
(branch|leaf)完成匹配,也就是你观察到的“替换该词后匹配即可正常进行”的原因。
修复方案
方案1:添加类型名边界限定(推荐)
在捕获组前加入负向后瞻(?<!\w),确保类型名不会出现在连续单词序列中间,直接消除无意义回溯:
preg_match('/(?:\( ?)?(?<!\w)((?:(?:\**\[(?:!?\d+)?\])*\**[A-Za-z_]\w*)+(?: ?\| ?(?:\**\[(?:!?\d+)?\])*\**[A-Za-z_]\w*)+)(?: ?\))?/', $two, $matches2, PREG_OFFSET_CAPTURE);
方案2:惰性匹配跳过无关前缀
在正则开头添加惰性匹配的.*?,让引擎直接跳转到第一个可能匹配的位置:
preg_match('/.*?(?:\( ?)?((?:(?:\**\[(?:!?\d+)?\])*\**[A-Za-z_]\w*)+(?: ?\| ?(?:\**\[(?:!?\d+)?\])*\**[A-Za-z_]\w*)+)(?: ?\))?/s', $two, $matches2, PREG_OFFSET_CAPTURE);
注:
s修饰符用于让.匹配换行符,如不需要可移除。
方案3:临时提高回溯限制
若不想修改正则,可临时调高PHP的回溯限制,但此方案仅为兜底,不推荐长期使用:
ini_set('pcre.backtrack_limit', 10000000);
额外优化
原正则的左、右括号为独立可选状态,可能匹配到只有左括号或只有右括号的无效内容,可调整为成对匹配规则:
preg_match('/(?:\( ?(?<!\w)|^(?<!\w))((?:(?:\**\[(?:!?\d+)?\])*\**[A-Za-z_]\w*)+(?: ?\| ?(?:\**\[(?:!?\d+)?\])*\**[A-Za-z_]\w*)+)(?: ?\)|$)/', $two, $matches2, PREG_OFFSET_CAPTURE);
内容的提问来源于stack exchange,提问作者Alasdair
相关产品推荐
相关产品推荐

