PHP正则核心是写对pattern、选对preg_match(校验)或preg_match_all(批量提取)、规避回溯与redos风险,需注意定界符转义成本、贪婪匹配陷阱及preg_quote()防御用户输入。

别从“正则语法大全”开始学,那样只会卡在 ^、$、d 的含义里反复查文档。PHP 正则真正要练的是「写对 pattern、选对函数、躲开回溯陷阱」这三件事。
先搞清 preg_match 和 preg_match_all 用在哪
这两个函数不是“功能相似可互换”,而是解决完全不同的问题:
-
preg_match()只找第一个匹配,返回1(成功)、0(没找到)或-1(pattern 错了)。适合做校验:邮箱格式、手机号开头、密码强度检查。 -
preg_match_all()扫完整个字符串,把所有匹配项塞进$matches。适合做提取:HTML 中所有<a href="...">、日志里所有 IP 地址、一段文本中所有带 # 的标签。 - 常见错误是用
preg_match()去“提取多个邮箱”,结果永远只拿到第一个;或者用preg_match_all()做登录表单验证,逻辑绕弯还多占内存。
定界符选 / 还是 # 不是风格问题,是转义成本问题
定界符本质是 pattern 的“引号”,选它唯一目的就是少写反斜杠:
- 匹配 URL:
/https?://[^/s]+/里要转义/和.,看着就晕;换成#https?://[^/s]+#,/不用转了,.仍需转(除非加s修饰符)。 - 匹配 Windows 路径:
#[A-Z]:\[^\]+#比/[A-Z]:\\[^\\]+/清晰得多。 - 注意:定界符不能是字母、数字、反斜杠或空白;
{、[、(这类成对符号必须配对使用,比如{d+}合法,{d+会报错PREG_NO_DELIMITER。
量词默认贪婪,不加 ? 就可能跨段落匹配
写 /".*"/ 想提取引号内容?它会从第一个 " 一路吃到最后一个 ",中间所有引号都算进去——这就是贪婪匹配的坑:
立即学习“PHP免费学习笔记(深入)”;
- 修复很简单:
/".*?"/,末尾加?变成非贪婪,遇到第一个闭合引号就停。 - 但别滥用
.*?:它会导致大量回溯,尤其在长文本里嵌套引号时,可能触发 PCRE 的PREG_BACKTRACK_LIMIT_ERROR。 - 更稳的写法是明确字符范围:
/"[^"]*"/(匹配不含双引号的引号内内容),既快又安全。
用户输入进 pattern 前必须过 preg_quote()
直接拼接变量进正则,等于给攻击者送 shell:
- 比如搜索关键词:
$keyword = $_GET['q']; $pattern = "/$keyword/i";—— 若传入test.+,.和+就变成元字符,可能意外匹配整段 HTML。 - 正确做法:
$pattern = '/' . preg_quote($keyword, '/') . '/i';,第二个参数必须和你的定界符一致,否则preg_quote()不知道该转义哪个符号。 - 这个步骤漏掉一次,就可能让正则变成 redos 攻击入口,尤其当 pattern 里还有
.*或嵌套量词时。
真正卡住人的从来不是“怎么写 d{3}”,而是 preg_match() 返回 0 却以为 pattern 写错了,其实只是忘了加 i 修饰符;或是 $matches 是空数组,因为没传引用;又或者用 .* 匹配中文时没加 u 修饰符导致乱码截断——这些细节比语法本身更常决定正则能不能跑通。



















