news 2026/10/22 21:44:12

php正则表达式学习笔记

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
php正则表达式学习笔记

前言


先说一件容易混淆的事:PHP 里的「正则表达式」其实分两套历史,一套是已经消失的 POSIX 扩展,一套是现在唯一在用的 PCRE。POSIX 那套函数(ereg()、eregi()、ereg_replace()、eregi_replace()、split()、spliti()、sql_regcase())在 PHP 5.3 就被废弃,在 PHP 7.0 中被彻底移除。现在网上还能搜到大量讲ereg()的教程,那些代码在 PHP 7 及以上版本里会直接报「Call to undefined function」。PHP 8 时代只认preg_*系列,底层的 PCRE 引擎负责解析和执行。


第二个常见误解是把正则当成「万能的格式校验器」。正则擅长的是在文本里找符合形状的片段,不擅长判断语义是否成立。比如用正则去完整校验一个邮箱地址是否符合 RFC 规范,是件既做不到又没必要的事——这件事应该交给filter_var($email, FILTER_VALIDATE_EMAIL)。分不清这两件事,就会写出越来越长、越来越难维护的模式。


本文是一份聚焦 PHP 侧的 PCRE 笔记:定界符与修饰符、常用元字符、捕获组与断言,以及两条必须小心的性能红线。


一、定界符与修饰符


一个 PHP 正则模式是字符串,第一个字符是定界符,最后一个字符是同一个定界符。定界符可以是任何非字母、非数字、非反斜杠、非空白的字符,常用的是斜杠、井号和波浪号:


<?php // 适用于 PHP 8.0+
$p1 = '/\d+/';
$p2 = '#\d+#';
$p3 = '~\d+~';

var_dump(preg_match($p1, 'abc123')); // int(1)
var_dump(preg_match($p2, 'abc123')); // int(1)
var_dump(preg_match($p3, 'abc123')); // int(1)

用井号做定界符的好处是,处理路径、URL 这类含大量斜杠的文本时不必转义:'#^/var/log/#'比'/^\/var\/log\//'好读得多。反过来,模式里出现的定界符字符必须转义。


定界符之后可以跟修饰符,它们控制匹配行为:




修饰符作用



i不区分大小写

m多行模式,行首行尾锚点按每行计算

s让点号也匹配换行

x扩展模式,忽略模式里的空白与注释

u按 UTF-8 解释模式与目标串

U反转量词的贪婪性(默认变懒惰)

A强制从头开始匹配

D行尾锚点只匹配字符串真正的结尾

S旧版本中用于预分析模式以加速匹配,现代 PCRE 已不再需要



关于u:不加它时,正则引擎把目标串当作字节序列,点号匹配一个字节,量词数的也是字节。中文字符在 UTF-8 下一个字占 3 个字节,所以'张三'这种两个字的中文,在没有u的模式下长度是 6。这一点造成的错误非常隐蔽:


<?php // 适用于 PHP 8.0+
var_dump(preg_match('/^.{2,4}$/', '张三')); // int(0),按字节算有 6 个「字符」
var_dump(preg_match('/^.{2,4}$/u', '张三')); // int(1),按字符算正好 2 个

处理中文时请一律加上u,并且要用 Unicode 属性转义(如\p{Han}表示汉字、\p{L}表示任意语言的字母)来描述字符类别,不要指望\w会覆盖非 ASCII 字符。


二、模式语法速查


下面这份速查表覆盖了日常九成以上的需求:


. 除换行外的任意字符(加 s 修饰符后含换行)
^ $ 行首 / 行尾(加 m 后按行计算)
\d \D 数字 / 非数字
\w \W 单词字符(字母、数字、下划线)/ 非单词字符
\s \S 空白字符 / 非空白字符
[abc] 字符类,匹配其中任意一个
[^abc] 反向字符类,匹配不在其中的任意一个
[a-z0-9] 字符区间
* + ? 0 次以上 / 1 次以上 / 0 或 1 次,贪婪
*? +? ?? 对应的懒惰版本,尽量少匹配
*+ ++ ?+ 占有版本,匹配后不回退
{n} {n,} {n,m} 精确 n 次 / 至少 n 次 / n 到 m 次
(...) 捕获组
(?:...) 非捕获组,只分组不占用编号
(?P<name>...) 命名捕获组(等价的尖括号写法现代 PCRE 也支持)
\1 \k<name> 反向引用,引用之前捕获到的内容
(?=...) 正向前瞻断言,右侧必须符合但不消耗字符
(?!...) 负向前瞻断言
(?<=...) 正向后顾断言
(?<!...) 负向后顾断言
a|b 分支,匹配 a 或 b
\b \B 单词边界 / 非单词边界
\p{Han} 汉字(需加 u 修饰符)

几点使用提示:



  • 非捕获组能不用编号就别占用编号。(?:...)不产生捕获,既省内存也让$matches的下标清晰。

  • 后顾断言要求定长(或有限长度)。这是 PCRE 的硬性限制,带长度区间的后顾写法在部分场景下不被接受,写之前先确认模式的定长性。

  • x修饰符适合写长模式,它忽略模式里的空白,于是可以换行缩进、加注释,可读性大幅提升。


POSIX 时代的写法与现在的对应关系也整理一份,方便迁移旧代码:




已移除的写法现在的等价写法



ereg($p, $s)preg_match('/' . $p . '/', $s)

eregi($p, $s)preg_match('/' . $p . '/i', $s)

ereg_replace($p, $r, $s)preg_replace('/' . $p . '/', $r, $s)

split(',', $s)explode(',', $s)或preg_split('/,/', $s)

sql_regcase()无对应,需要时自行构造



注意 POSIX 的字符类写法[[:alpha:]]、[[:digit:]]在 PCRE 里仍然可用,它们和\w、\d不是一回事,迁移时不用一起改掉。另外,旧代码里常见的preg_replace()加e修饰符也在 PHP 5.5 被废弃、PHP 7.0 被移除,正确做法是改用preg_replace_callback()。


三、捕获组、断言与回溯


preg_match()一次只找第一个匹配,它的第三个参数是按引用传入的数组,用来接收捕获结果。下标 0 是完整匹配,1 开始依次是各捕获组:


<?php // 适用于 PHP 8.0+
$pattern = '/^(\d{4})-(\d{2})-(\d{2})$/';
$subject = '2026-10-06';

$result = preg_match($pattern, $subject, $matches);

var_dump($result); // int(1)
echo $matches[1], '年', $matches[2], '月', $matches[3], '日', PHP_EOL;

带名字的捕获组更好读,但要注意它在结果数组里会出现两次——一次用名字做键,一次用序号做键:


<?php // 适用于 PHP 8.0+
$pattern = '/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/';

if (preg_match($pattern, '2026-10-06', $m) === 1) {
echo $m['year'], '-', $m['month'], '-', $m['day'], PHP_EOL;
echo $m[1], '-', $m[2], '-', $m[3], PHP_EOL; // 同一批值,换个键取
}

断言(assertion)用来描述「匹配位置周围必须满足什么条件」,但它不消耗字符,所以不会出现在$matches里。它是替代「先匹配一大段再手工裁剪」的好工具:


<?php // 适用于 PHP 8.0+
$text = '价格 100 元,折扣 20 元';

// 只取后面紧跟「 元」的数字,但不把「元」匹配进来
preg_match_all('/\d+(?= 元)/', $text, $prices);

echo implode(',', $prices[0]), PHP_EOL; // 100,20

最后说性能。PCRE 使用回溯(backtracking)算法,模式写不好会导致「回溯爆炸」:引擎尝试了指数级的组合才判定失败,CPU 被吃满。核心原因是嵌套量词——形如「一个量词修饰的组里又含量词」的模式,在不匹配的长输入上代价极高。防御手段有三条:



  1. 能写精确就写精确,例如限定内容的字符类和明确的长度范围。

  2. 用占有量词(*+、++、?+)告诉引擎「匹配到就不用回头」,它对不需要回退的场景完全等价于贪婪量词。

  3. 在进程层面设防:pcre.backtrack_limit和pcre.recursion_limit这两个 ini 配置决定了回溯的上限,超出后preg_*会返回false而不是一直算下去。


第三条尤其重要,因为回溯超限返回的是false,不是0。如果代码写的是「没匹配上就报格式错误」,回溯超限会被误报成「格式不对」,把一个性能问题伪装成业务问题。


<?php // 适用于 PHP 8.0+
// 出错时用 preg_last_error_msg() 看原因(需要 PHP 8.0+;PHP 7 用 preg_last_error() 拿错误码)
$ok = preg_match('/\d+/', 'abc');

var_dump($ok); // int(0),真的没匹配上
var_dump($ok === false); // bool(false),说明这不是错误

常见坑点



  1. ❌ 用ereg()、eregi()、split()的旧代码直接上线。


✅ 这些函数在 PHP 5.3 废弃、PHP 7.0 移除;改用preg_match()、preg_match()加i修饰符、preg_split()或explode()。



  1. ❌ 用preg_replace()的e修饰符执行替换结果。


✅ 该修饰符 PHP 5.5 废弃、PHP 7.0 移除;应改用preg_replace_callback()显式回调。



  1. ❌ 处理中文时不加u修饰符。


✅ 不加u时点号和量词都按字节算,一个汉字占 3 字节,长度判断会整体错位。



  1. ❌ 用\w去匹配中文,以为它会包含所有语言的字母。


✅\w默认面向 ASCII;匹配汉字请用\p{Han},匹配任意字母用\p{L},并且都要加u。



  1. ❌ 把模式写进单引号字符串时忘了处理反斜杠。


✅ 单引号里'\d'就是\d本身,问题不大;但'\\d'会变成字面量反斜杠加 d,写模式时统一用单引号并只写一次反斜杠最不容易错。



  1. ❌ 用正则去完整校验邮箱或日期的语义合法性。


✅ 正则只适合做形状粗筛;邮箱用filter_var()加FILTER_VALIDATE_EMAIL,日期合法性用DateTime::createFromFormat()校验。



  1. ❌ 写出嵌套量词的模式,并用它去处理用户提交的长文本。


✅ 嵌套量词容易引发回溯爆炸,长输入下 CPU 飙高;改用精确字符类、占有量词,并给pcre.backtrack_limit设一个合理上限。



  1. ❌ 把回溯超限返回的false当成「没匹配上」。


✅false表示出错,0才表示没匹配;判断命中请写=== 1,并在失败分支里检查preg_last_error()/preg_last_error_msg()。


总结




关注点结论



现行扩展只有 PCRE 系的preg_*;POSIX 的ereg_*在 PHP 7.0 已移除

定界符任意非字母数字非反斜杠非空白的字符,斜杠、井号、波浪号最常用

必加修饰符处理中文一定要加u

捕获组(...)捕获并占编号,(?:...)只分组

断言前瞻断言与后顾断言,各自都有否定形式,都不消耗字符

错误与未命中出错返回false,未命中返回0,必须用===区分

性能红线嵌套量词会引发回溯爆炸,靠精确模式、占有量词、回溯上限三重设防

该交给别人的活邮箱、日期的语义校验不要用正则硬扛



学 PHP 正则最省事的路径是:先把「定界符 + 修饰符 +u」这三件事形成肌肉记忆,再记牢「false是错误、0是没匹配」这条判断规则,剩下的语法细节随用随查即可。真正会让人栽跟头的从来不是记不住元字符,而是把回溯爆炸和语义校验这两类问题错误地交给了正则。




版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 23:32:38

项目资源管理实战:人力设备资金时间四维动态调度

1. 项目资源管理到底管什么&#xff1f;——别再把“人财物”当万能答案了很多人一听到“项目资源管理”&#xff0c;脑子里立刻蹦出三个字&#xff1a;人、财、物。这没错&#xff0c;但太浅了。就像说“做饭就是把米下锅”&#xff0c;听起来对&#xff0c;可真让你做一桌宴席…

作者头像 李华
网站建设 2026/10/17 23:57:35

零基础3天上线微信小程序:原生开发极简实战指南

1. 项目概述&#xff1a;为什么现在还要从零做微信小程序&#xff1f; “从0开始&#xff0c;手把手教你制作一个微信小程序”——这句话在2024年听起来有点复古&#xff0c;甚至带点“怀旧感”。但恰恰是这种“复古”&#xff0c;戳中了大量真实需求&#xff1a;某高校教培团…

作者头像 李华
网站建设 2026/10/15 16:33:11

从npm权限到WSL2:Claude Code环境配置与多模型接入完整指南

我接手这个项目的时候&#xff0c;最先接触到的其实是团队里不断冒出来的安装报错截图。有人卡在npm权限、有人在Windows上折腾半天跑不起来、还有人问能不能把Claude Code接到别的模型上。后来我把这些零散需求汇总成一个可复用的配置集合&#xff0c;顺手起了个名字叫pstack-…

作者头像 李华
网站建设 2026/10/15 13:35:04

t3code终端编码工作流:Homebrew、winget与Electron打包实战

1. 从"t3code"这个关键词说起&#xff1a;一个被低估的终端编码工作流第一次看到"t3code"这个词&#xff0c;很多人会以为是某个新出的代码编辑器或者某个小众编程语言。实际上&#xff0c;结合它周边的热搜词——Electron、CLI、Homebrew、winget、codex …

作者头像 李华
网站建设 2026/10/15 16:33:12

朋友圈功能测试全攻略:从核心链路到弱网兼容与数据一致性

这道经典面试题&#xff0c;几乎每个测过微信或类似社交产品的同学都遇到过&#xff0c;也是我在面试候选人时必问的一道题。它表面问的是“朋友圈怎么测”&#xff0c;实际上考的是一个测试工程师面对一个庞杂的真实业务系统时&#xff0c;能不能快速拆解出功能模块、识别核心…

作者头像 李华