跳到正文

正则表达式 ​

正则用一小段模式描述一组字符串。编辑器搜索、grep、测试框架的用例筛选、日志解析都靠它。

语法骨架 ​

写法含义
abc字面量 abc
.任意单个字符
[abc]括号内任一字符
[^abc]不在括号内的字符
[a-f]范围内任一字符
`ab`
\d数字,等价 [0-9]
\w单词字符
\s空白字符
\b单词边界,宽度为零
X?零个或一个
X*零个或多个
X+一个或多个
X{N}恰好 N 个
(...)分组并捕获
^ $行首、行尾
\.转义,匹配字面量点号

., *, +, ?, (, ), [, ], {, }, |, ^, $, \ 都有特殊含义,要匹配本身就得加反斜杠。\d{4}-\d{2}-\d{2} 只保证四位数字、横线、两位、横线、两位,2026-01-99 照样通过,格式校验和取值校验是两件事。

写模式时还有两个易错点:

  • Python 里统一加 r 前缀。普通字符串中的 \d 属于无效转义,Python 3.12 起会给出 SyntaxWarning;\b 更糟,会被解释成退格符
  • ^ 和 $ 的语义取决于引擎与选项。Python 的 re 默认按整串,加 re.MULTILINE 才逐行;grep 与多数编辑器默认逐行

分组与替换 ​

python
import re

m = re.match(r"\d{4}-(\d{2})-\d{2}", "2026-01-14")
print(m.group(1))                              # 01

print(re.sub(r"(\w+)@(\w+)", r"\2:\1", "a@b")) # b:a

替换串里的反向引用,Python、sed、Vim 写 \1,VS Code 的替换框写 $1。抄别人的正则时先确认方言,再确认分组编号是从 0 还是 1 开始。

re.match 从字符串开头匹配,re.search 扫描整个字符串,re.fullmatch 要求整串都匹配,校验逻辑该用后者。命名分组写 (?P<year>\d{4}),取值用 m.group("year"),比数字下标耐改。

前后查找不消耗字符,可以用来做「后面跟着」的条件:

写法含义
(?=x)后面是 x,不含 x 本身
(?!x)后面不是 x
(?<=x)前面是 x
(?<!x)前面不是 x

re.compile 预编译模式,循环里反复匹配时省掉解析开销;模式只用一个正则字符串、匹配上千行日志时差别明显。

贪婪与懒惰 ​

.* 是贪婪的,会尽量多吃,然后回溯。匹配 JSON 里的名字:

python
re.search(r'"name": "(.*)"', '{"name": "Alyssa", "college": "MIT"}').group(1)

结果是 Alyssa", "college": "MIT——它吃到了这一行最后一个引号。

加个问号变成懒惰:".*?",匹配到第一个可行位置就停。更稳的写法是把转义考虑进去:

python
r'"name": "((?:[^"\\]|\\.)*)"'

[^"\\] 吃掉普通字符,\\. 吃掉转义序列,整个分组不会越过未转义的引号。

. 默认不匹配换行。要跨行用 re.DOTALL;要让 ^ 和 $ 按行生效用 re.MULTILINE。两个标志可以按位或:re.I | re.M。

常用片段 ​

目标模式备注
日期\d{4}-\d{2}-\d{2}不校验月份与日
邮箱粗筛.+@.+只排除明显不是的
IPv4 粗筛\d{1,3}(\.\d{1,3}){3}不校验 0 到 255
重命名导入import .* as .*配合 ag、rg 搜代码
测试筛选go test -run 'TestUser'按测试名子串匹配
4xx 与 5xx`" (4\d5\d{2}) `

这些模式用于粗筛,不用于校验。真要做校验,先把输入解析成结构,再检查取值范围。

在编辑器里替换 ​

bash
# 只替换行首的列表标记,不动日期里的横线
sed -E 's/^([[:space:]]*)- /\1* /' notes.md
vim
" Vim 基本正则,分组要转义
:%s/^\(\s*\)- /\1* /g
环境引用分组说明
VS Code$1替换框里也支持 \n
Vim\1整个匹配是 &
sed\1基本正则要写 \( \)
PCRE\1-P 或 pcre2grep

直接全局替换 - 会改坏日期、复合词和 --flag。正确做法是把匹配锚定到行首,再要求后面跟一个空格:^(\s*)- 。

拼接用户输入进模式时用 re.escape,否则输入里的 . 或 ( 会被当成语法,轻则匹配错误,重则抛异常。

方言与边界 ​

场景用法
POSIX 工具grep -E,扩展正则
GNU grepgrep -P,PCRE 方言
Pythonre 模块
测试筛选`go test -run 'TestFoo
代码搜索ag、rg 默认支持正则
调试regex101 可选方言逐步解释

不同引擎在命名分组、后顾、懒惰量词上的细节不一致,复杂模式先到 regex101 选好 flavor 再试。POSIX 字符类写作 [[:digit:]],在 grep -E 里比 \d 可靠。

正则的表达力有上限,几类东西写不出来或写不好:

  • {a^n b^n} 这种「a 的个数等于 b 的个数」
  • HTML、JSON 这类嵌套结构
  • 嵌套量词引发的灾难性回溯,比如 (a+)+$ 遇到一长串 a 就崩了

硬写会让模式又长又脆;回溯那类还会在 CI 里跑大文件时直接挂住,用时先想想能不能换成解析器。

解析 JSON 就该用 JSON 解析器:

bash
python -c 'import json,sys; print(json.load(sys.stdin)["name"])'

一行代码,处理转义、嵌套、Unicode 全部正确。正则留给日志行、配置项、批量重命名这类扁平文本。