preg_match只匹配一次因其设计目标是查找首个匹配项并返回结果,需全量匹配应使用preg_match_all;注意分隔符转义、Unicode修饰符u、捕获组索引规则及回溯安全。

preg_match 为什么只匹配一次就停了
因为 preg_match 的设计目标就是「找第一个匹配项并返回」,不是遍历全部。它返回 1(找到)、0(没找到)或 false(正则写错了),不提供后续匹配位置。
- 需要全量匹配用
preg_match_all,别硬套preg_match加循环 - 常见错误:写
while (preg_match(...)) { ... },结果死循环或逻辑错乱 - 如果只是校验格式(比如邮箱、手机号),
preg_match正好够用;但要提取多个链接、标签、参数值,必须换preg_match_all
模式分隔符和转义容易漏掉的细节
PHP 的 PCRE 要求正则两边有分隔符,最常用的是 /,但它本身在模式里出现就得转义——而很多人忘了这点,尤其匹配路径、URL 或 HTML 标签时。
- 匹配
https://example.com写成/https:\/\/example\.com/,不能漏掉\/和\. - 用其他分隔符可减少反斜杠,比如
#https://example\.com#或@<a>]*>@</a> - 中文字符、Unicode 要加
u修饰符:/[\x{4e00}-\x{9fa5}]+/u,否则可能空匹配或 Warning - 忽略大小写加
i,多行模式加m,但别滥用——im一起用可能让^和$行为变复杂
第三个参数 $matches 怎么安全取值
preg_match 把结果塞进传入的变量(通常是 $matches),但这个数组结构容易误读:索引 0 是完整匹配,1 开始才是捕获组,而且只有括号才产生新索引。
- 写
if (preg_match('/(\d{4})-(\d{2})/', $str, $m)) { echo $m[1]; }才能拿到年份;$m[0]是整个日期串 - 没加括号的子模式(比如
(?:...))不会进$m,别指望它有索引 - 使用前务必用
isset($m[1])判断,否则未匹配时访问$m[1]会触发 Notice - 想直接取命名捕获组?可以,但得用
'/(?P<year>\d{4})-/'</year>,然后用$m['year'],不过要注意 PHP 版本兼容性(5.2.2+)
性能和安全边界:别在用户输入上无脑跑复杂正则
正则引擎对某些病态模式(比如嵌套量词、回溯爆炸)响应极慢,用户提交的任意字符串 + 不设限的正则 = 拒绝服务风险。
立即学习“PHP免费学习笔记(深入)”;
- 避免类似
/a+.*b/这类高回溯模式处理长文本;用更具体的字符类替代.,比如[^\r\n\t]+ - 对不可信输入(如表单、URL 参数),先做长度限制:
if (strlen($input) > 1000) die('too long'); - 启用 PCRE JIT(PHP 7.3+ 默认开启)能提速,但无法拯救设计不良的正则;真正关键的是精简逻辑,而不是堆修饰符
- 调试时加
PREG_UNMATCHED_AS_NULL(PHP 7.2+)能让空捕获组返回null而非未定义,减少判空负担
正则写得越短、锚点越明确(^、$、\b)、分支越少,就越不容易在奇怪的地方卡住或漏匹配。











