PHP正则默认贪婪匹配,.会匹配至最后一个闭合位置;加?变为.?才停在第一个闭合处,这关系到HTML、日志等解析是否跨段误捕。

PHP 中的正则默认是贪婪匹配,.* 会吞掉中间所有内容直到最后一个匹配项;加个 ? 变成 .*? 才能停在第一个闭合位置——这不是风格偏好,而是解析 HTML、日志、JSON 片段时是否“跨段误捕”的分水岭。
为什么 preg_match() 只返回第一个贪婪匹配结果
因为 preg_match() 本身只找一次完整匹配,不负责遍历所有可能。哪怕你写的是 <div>.* 这种明显会覆盖多段的模式,它也只从左到右扫一遍,找到最长合法匹配就停。
<ul>
<li>输入:<code>"<div>A</div>
<div>B</div>"
preg_match('/<div>(.*)/', $str, $m) → <code>$m[1] 是 "A
B",不是
"A"
这不是 bug,是引擎按贪婪规则执行完就收手了
想拿全部匹配要用 preg_match_all(),但前提是模式本身得支持“断开”
非贪婪模式不是万能解药,要防 .*? 匹配空字符串
.*? 在“可匹配零次”的前提下,极容易在边界处匹配到空串,尤其当后续模式可选或存在重叠时。
- 错误写法:
/(.*?)(\d+)?/ —— .*? 会优先匹配空,把 \d+ 全让给后面
- 正确思路:明确限定左侧范围,比如用
[^ 替代 <code>.*?,避免无约束吞吃
- 调试技巧:加
PREG_OFFSET_CAPTURE 看匹配起始位置,确认是不是卡在开头就结束了
- PHP 8.0+ 支持
(*PRUNE) 等动词控制回溯,但日常建议先收紧字符集
preg_match_all() 配合非贪婪才能真正提取多个片段
单靠 .*? 不足以让 preg_match_all() 拿到所有目标——必须确保每次匹配后引擎能“跳过已匹配部分”,否则会原地重复匹配空或重叠内容。
PHP
编写健壮的PHP代码,规避类型转换陷阱、数组怪癖及常见安全漏洞。
下载
立即学习“PHP免费学习笔记(深入)”;
- 安全写法:
preg_match_all('/<div>([^/', $str, $m),用 <code>[^ 显式排除干扰符
<li>对比:<code>preg_match_all('/<div>(.*?)/', $str, $m) 在含换行或嵌套标签时可能漏匹配或崩溃
<li>注意 <code>.*? 默认不匹配换行符,需加 s 修饰符:/s,否则遇到 \n 就中断
- 性能提示:嵌套使用
.*?(如 a.*?b.*?c)仍可能触发深度回溯,比固定字符类慢一个数量级
最常被忽略的一点:PHP 的 PCRE 引擎对非贪婪的支持依赖 NFA 实现,而某些极端嵌套结构(如未闭合的引号内再套标签)会让 .*? 和 .* 同样卡住——这时候该放弃正则,改用 DOM 或状态机解析。