正则表达式实战:从"看天书"到"顺手就写"
正则表达式实战:从"看天书"到"顺手就写"
很多人学正则的方式是打开一张几十行的元字符总表开始背,背到"零宽断言"就放弃,之后见到正则还是像看天书。其实正则不是背出来的:记住 9 类字符 + 理解 4 个概念,就能覆盖日常 90% 的场景。剩下的部分,交给调试工具和这篇的配方库。
这篇不讲学院派理论,按"能上手写、能看懂别人的、能避开事故"三个目标来组织。
一、核心字符:一张表覆盖 90%
|
类别 |
写法 |
含义 |
|
字符组 |
[abc]、[a-z]、[^x] |
任选其一 / 范围任选 / 排除 |
|
预定义类 |
\d \w \s |
数字 / 单词字符 / 空白(大写为补集) |
|
量词 |
* + ? {n,m} |
0 次起 / 1 次起 / 0 或 1 次 / 次数区间 |
|
懒惰量词 |
*? +? ?? |
在贪婪基础上加问号,尽量少匹配 |
|
锚点 |
^ $ \b |
行首 / 行尾 / 词边界 |
|
分组 |
(…)、(?:…)、(?P<名>…) |
捕获 / 不捕获 / 命名(语法因语言而异) |
|
或 |
a|b |
左右任选 |
|
先后看 |
(?=x) (?!x) (?<=x) (?<!x) |
后面是/不是 x,前面是/不是 x |
|
转义 |
\. \* \\ |
元字符还原为字面含义 |
正则的最小可用配置就是这一张表。下面四个概念决定你是"查表拼凑"还是"流畅书写"。
二、四个概念:从查表到书写
概念 1:贪婪与懒惰
量词默认贪婪:尽可能多吃。<div>.*</div> 遇到一行里有多个 div,会从第一个 <div> 一路吃到最后一个 </div>。在量词后面加 ? 变懒惰,尽可能少吃:
文本:<div>a</div><div>b</div>
贪婪:<div>.*</div> → 匹配整个字符串(吃到最后)
懒惰:<div>.*?</div> → 分两次,各匹配一个 div
更推荐的做法是用排除法代替懒惰:<div>[^<]*</div>——语义更明确,性能也更好。
概念 2:分组与引用
圆括号除了分组还捕获:提取日志里的时间戳、把匹配的片段拿来替换,全靠它。两件小事记牢:
· 只想分组不想捕获,用 (?:…)——不占捕获编号,不浪费内存
· 命名组让长正则可读:Python 写 (?P<time>…),.NET/JS 写 (?<time>…),方言不同但思想一致
概念 3:先后看(零宽断言)
(?=x) 和 (?<=x) 匹配的是"位置"而不是字符——像站在原地往右/往左看一眼,条件满足才算命中,本身不吃任何字符。最经典的应用是千分位:
import re
re.sub(r'(?<=\d)(?=(\d{3})+$)', ',', '12345678') # → '12,345,678'
规则:每个"后面还剩 3 的倍数个数字"的位置前面插逗号。注意这个配方只适用于纯整数串,含小数时会切到小数部分。
概念 4:标志位
· i:忽略大小写
· g:全局匹配(不加只匹配第一个;findall 类 API 天然全局)
· m:多行模式——不加时 ^ $ 只认整个字符串的首尾,加了才逐行生效
· s:让 . 也能匹配换行符
三、高频配方:拿走就用
1. 手机号(中国大陆)
1[3-9]\d{9}
提取场景够用;做校验时加上锚点 ^1[3-9]\d{9}$,否则字符串里夹一段 11 位数字也会放行。
2. 邮箱(务实版)
^[^\s@]+@[^\s@]+\.[^\s@]+$
完整 RFC 规范的正则可以写到几百字符,不值得。这条只验证"没有空格、有一个 @、@ 后有点"——真正的校验靠发送确认邮件,这是行业共识。
3. 日期 YYYY-MM-DD(半严格)
\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])
月份和日期范围已校验,但 2026-02-31 仍能通过——日历合法性交给日期库,正则只挡格式错误,这是分工的正确姿势。
4. IPv4(严格版)
((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)
每段 0–255 的经典写法。用在日志提取里时两侧加 \b 或显式锚点,防止 999.1.1.1 被部分匹配。
5. 从日志行提取字段(命名组实战)
2026-09-09 22:04:31 [ERROR] 10.2.3.4 payment timeout after 3000ms
^(?P<time>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) \[(?P<level>\w+)\] (?P<ip>\d{1,3}(\.\d{1,3}){3}) (?P<msg>.+)$
一条正则把整行拆成 time / level / ip / msg 四个字段——日志解析器的基本功就是它。
6. 密码强度(多条件与先看)
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$
三个 (?=…) 各自独立检查"整串里存在小写/大写/数字",最后 .{8,} 管长度。多个先后看叠加做"与"逻辑,是这个技巧的核心用法。
四、回溯灾难:正则界的事故高发区
引擎匹配失败时会回溯重试。大多数时候无所谓,但嵌套量词会让回溯次数指数级爆炸:
^(\d+)*$ # 危险:量词套量词
^(a+)+$ # 危险:同上
拿一串 30 个字符去匹配失败,引擎要回溯数十亿次——服务直接卡死,这类攻击叫 ReDoS。而且它常出现在用户输入校验里(攻击者专门构造这种字符串),属于安全漏洞。
修复方法固定两条:消灭嵌套量词(^(\d+)*$ 改成 ^\d+$),用排除法替代点号("[^"]*" 优于 ".*?")。上线前把正则放到长反例串上压一遍,或者干脆用 RE2 这类保证线性时间的引擎。
五、五个常见坑
· 忘了转义点号:file.pdf 里的 . 匹配任意字符,fileXpdf 也会命中。字面点号写 \.,同理 + ( ) [ ] { } | * ? $ ^ \ 都要转义
· 贪婪吃太多:见概念 1,多个同类标签挤在一行时 .* 会跨越边界。用 [^x]* 排除法或 *? 懒惰
· `\b` 在中文里不靠谱:词边界依赖 \w 的定义,中文在不同引擎、不同模式下进不进 \w 都不一样。处理中文用明确的字符组([\u4e00-\u9fa5])和锚点,别赌 \b
· 多行文本忘了 `m` 标志:^ $ 默认只匹配整串首尾,逐行匹配必须开多行模式,否则整个文件只有第一行能被"行首"命中
· 跨语言照抄正则:JS 旧版不支持 lookbehind;.NET 的 \d 默认匹配全角数字;命名组语法各有写法。跨语言迁移前先查方言,用语言自带的原生测试跑一遍
六、工具与工作流:三个动作代替死记
1. regex101 调试:贴样本 → 写正则 → 右侧实时高亮和逐 token 解释,看不懂别人正则时把粘进去读解释,比任何教程都快
2. 真实样本驱动:从生产数据里抓正反例各 20 条,正例必命中、反例必不中,先有样本再写正则
3. 固化成单测:正则是"看起来对"最容易骗人的代码,正反例写成单元测试才敢改
速查卡
|
场景 |
正则 |
|
手机号校验 |
^1[3-9]\d{9}$ |
|
邮箱(务实版) |
^[^\s@]+@[^\s@]+\.[^\s@]+$ |
|
日期格式 |
\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]) |
|
千分位 |
(?<=\d)(?=(\d{3})+$) 替换为逗号 |
|
日志行提取 |
命名组,见配方 5 |
|
密码强度 |
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$ |
|
防回溯灾难 |
禁嵌套量词,点号换排除法 |
写在最后
正则的上限不是"记住多少语法",而是"能否用真实样本验证它"。9 类字符是词汇量,4 个概念是语法,配方库是肌肉记忆,样本和单测是安全网——四样配齐,正则就从天书变成了顺手的日常工具。
从打开 regex101、贴上一条你今天的真实日志开始吧。
- 点赞
- 收藏
- 关注作者
评论(0)