正则表达式速查表 - 常用语法、模式与实战模板大全
正则真正难的地方不是元字符本身,而是同一个需求常有几种等价写法,选错一种就会在长文本上悄悄多吞或少吞字符。抓两个标签之间的内容时,.* 和 .*? 在多数样例上输出完全一样,只有当一行里出现第二个同名标签时才会分叉; 与显式空格的差别,在含下划线的标识符上同样看不出来。这张表把字符类、锚点、量词、分组与捕获、转义与标志、实战示例、常见陷阱拆成七组,每条都写明它匹配的是一个字符、一个位置还是一段结构。什么时候该翻它:模式在小样本上验证通过、上了真实数据就大面积失配;或 grep 出来的行数明显多于预期——这两种几乎都出在量词和断言上,先查贪婪还是懒惰,再补 ^/$ 或词边界。与 grep -E 的区别在于此表只讲模式语法本身,不绑定任何搜索工具;与 split 的区别在于 split 只能按固定分隔符切,模式能处理变长结构。
典型使用场景
面向经常需要写正则但总在量词与断言上翻车的开发者:把「贪婪还是懒惰」「该用前瞻还是后顾」「哪些元字符字面使用时必须转义」这三类高频翻车点集中讲清,再配上邮箱、URL、手机号、日期、密码五个可直接用的实战模式。目标是写完能自己判断对错,而不是照抄一个能跑但不知道为什么能跑的模式。
字符类 Character Classes 10
[abc][^abc][a-z]\d\D\w\W\s\S.锚点 Anchors 8
^$\b\B(?=...)(?!...)(?<=...)(?<!...)量词 Quantifiers 8
*+?{n}{n,}{n,m}*?+?分组与捕获 Groups 6
(abc)(?:abc)(?<name>abc)a|b\1\k<name>转义与标志 Flags 7
\\./i/g/m/s/u实战示例 Practical Patterns 5
[\w.+-]+@[\w-]+\.[\w.-]+https?://[\w./?=&#%-]+1[3-9]\d{9}^\d{4}-\d{2}-\d{2}$^[#\w-]{6,}$常见陷阱 FAQ 4
贪婪 vs 懒惰点号不含换行特殊字符要转义catastrophic backtracking参数矩阵
| 参数 | 作用 | 示例 |
|---|---|---|
* vs + vs ? | 0 次或多次 / 1 次或多次 / 0 或 1 次 | a*b · a+b · a?b |
{n,m} | 精确次数范围,比多次 * 更可控 | ^\d{4}-\d{2}-\d{2}$ |
*? / +? | 懒惰匹配,尽量少匹配 | <.*?> 只匹配最短标签 |
^ / $ | 锚定字符串开头 / 结尾 | ^start |
\b | 单词边界,避免匹配到词内部 | \bcat\b 不匹配 concatenate |
(?=...) | 正向前瞻:后面必须是但不消费 | \d+(?= 元) |
(?<=...) | 正向后顾:前面必须是但不消费 | (?<=\$)\d+ |
(?:...) | 非捕获分组,只组合不捕获 | (?:foo|bar)+ |
(?<name>...) | 命名捕获,结果可按名取 | (?<year>\d{4})-(?<month>\d{2}) |
/i / /g / /m / /s | 忽略大小写 / 全局 / 多行 / dotAll | str.match(/\d+/g) |
[\w.+-]+@[\w-]+ | 邮箱实战模式 | [\w.+-]+@[\w-]+\.[\w.-]+ |
1[3-9]\d{9} | 中国大陆手机号 | 1[3-9]\d{9} |
易错点与避坑指南
现象想匹配 HTML 标签内容,结果匹配到了整行文本。
原因<.*> 是贪婪匹配,会一直吃到行尾最后一个 >。
处置改成懒惰匹配 <.*?>,或用 [^>]* 排除 > 字符。
现象在长字符串上正则运行极慢甚至卡死。
原因嵌套量词(如 (.+)+)在匹配失败时产生指数级回溯(catastrophic backtracking)。
处置避免嵌套量词;改用 [^...] 排除或分两步匹配。
现象点号匹配不到多行内容,跨行的文本只匹配到第一行。
原因默认 . 不匹配换行符。
处置加 /s 标志,或用 [\s\S] 显式表示任意字符。
现象搜 "cat" 时把 concatenate 也匹配出来了。
原因模式没有单词边界约束。
处置用 \bcat\b 限定边界。
现象搜文件名里的点号时匹配到了任意字符。
原因. 是正则元字符,字面使用未转义。
处置写成 \. 转义,或用 grep -F / re.escape() 按字面匹配。
现象写好 ^ 和 $ 但多行文本里只在首尾生效。
原因缺少多行标志,^ $ 默认匹配整个字符串的首尾。
处置加 /m 标志使其匹配每一行的行首行尾。
排障路径
1确认实际匹配了什么而不是你以为的
grep -oE "你的模式" file-o 只输出匹配到的片段;没有输出说明模式没命中,逐段简化再试。
2在文本里定位边界是否存在
grep -nE "\bword\b" file边界问题最常见的解法就是加 \b,先验证再加进主模式。
3逐段验证复杂模式
grep -oE "^(\d{4})-(\d{2})-(\d{2})$" file拆成小组分别验证,比一次性调长模式更容易定位是哪段出错。
4在 JS 里测试并查看捕获组
node -e "console.log('2026-09-30'.match(/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/))"match 返回数组与 groups,命名组便于后续取值。
5排查贪婪匹配吞掉整行
grep -oE "<[^>]*>" file用 [^>] 代替 .* 是避免贪婪最直接的办法。
命令示例
用懒惰量词取标签内容,避免贪婪匹配吞掉整行
const re = /<(\w+)[^>]*>(.*?)<\/\1>/g
const line = '<b>bold</b> and <i>italic</i>'
console.log([...line.matchAll(re)].map(m => m[1] + ':' + m[2]))(.*?) 配合 *? 只取到第一个闭合尖括号;若写成 <(.*)>,贪婪量词会把 </b> and <i>italic 全部当成内容。\1 反向引用保证只匹配同名闭合标签。
输出
[ 'b:bold', 'i:italic' ]
贪婪量词在长串上的回溯爆炸
// 危险写法:对长字符串可能指数级回溯
const bad = /^(\w+\s?)+$/.test('a'.repeat(30) + '!')
// 安全写法:单层量词 + 线性匹配
const good = /^\w+(?:\s+\w+)*$/.test('a'.repeat(30) + '!')嵌套量词 (\w+\s?)+ 的匹配路径呈指数增长,30 个字符就足以让单次匹配卡住数秒;改写为不嵌套的 \w+(?:\s+\w+)* 后回到线性时间。
输出
bad: false good: false
字符类里的连字符必须放在首尾
// 错误:a-z 被当成范围 'z' 到 '8'
const bad = /^[a-z-]+$/.test('-')
// 正确:连字符放首尾或转义
const ok = /^[-a-z]+$/.test('-')[a-z-] 里的 - 跟在范围之后会被解析成 z 到 8 的反向范围,纯连字符字符串直接校验失败。放首尾或写 \- 才是想要的语义。
输出
bad: false ok: true
用前后瞻校验密码强度而不消费字符
const re = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$/
re.test('Passw0rd')
re.test('password1')三个正向前瞻分别要求「含数字/小写/大写」,(?=...) 只做检查不消耗字符,因此主量词 .{8,} 仍能校验完整长度。若改成先捕获再回溯,长密码上的性能会明显变差。
输出
true false
命名捕获分组替代数字下标
const re = /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})$/
const m = '2026-07-19'.match(re)
m.groups.y + '/' + m.groups.m + '/' + m.groups.d给分组起名后调整分组顺序不会让 m[1]、m[2] 静默错位——这类错误在日志解析里极难发现。\k<name> 可反向引用命名分组。
输出
'2026/07/19'
常见坑与注意事项
- 贪婪量词与嵌套量词组合(
(.+)+、^(a|aa)+$)在长输入上会指数级回溯,几十个字符就能让服务线程卡死,上线前必须用最长真实输入压测。 - 字符类里把
-写在中间会被解释成范围:[a-z-]实际含z到8的反向区间,纯连字符输入会校验失败;-只能放首尾或转义。 - GNU 与 BSD 正则不兼容:
grep -E "\d"在 BSD(macOS)上不成立,需写[[:digit:]];\w、\s、\b同样是 GNU 扩展。 - 未转义的元字符(
. * + ? ( ) [ ] { } | \ ^ $)在字面量场景会被当成语法,写密码/邮箱校验前不做\转义会漏匹配甚至误匹配。 .默认不匹配换行,跨行文本提取会静默少内容;需要跨行时加s标志或改用[\s\S],而^$逐行匹配必须加m。/g正则带lastIndex状态,复用同一个正则对象做多次test/exec会得到交替的结果;改用String.prototype.matchAll或每次新建正则。
提示
- 默认量词贪婪,加 ? 变懒惰(<.*?> 只匹配最短)。
- 点号 . 不含换行,跨行匹配需 /s 标志或 [\s\S]。
- 元字符 . ^ $ * + ? ( ) [ ] { } | \ / 字面使用需转义。
- 避免嵌套量词 (.+)+,长串上可能指数级回溯。
- 命名捕获组
(?<name>abc)配\k<name>替换,比按括号序号写\2更不怕后续加分组。 - 手机号只写
\d{11}会从 13 位数字里截出一段,1[3-9]\d{9}才能排除非法号段。 \b是零宽断言不消耗字符,grep -nE "\bword\b"中它只在词边界处成立,不会吃掉相邻字符。
常见问题
什么时候该把贪婪量词改成懒惰?两者差别到底在哪?
默认量词都是贪婪的,会尽可能多吞字符,这正是灾难的来源。经典对照:匹配 HTML 标签用 <.*> 会把同一行里两个标签之间的全部内容一起吃掉,而 <.*?> 只取最短的闭合,得到真正的单个标签。选懒惰的判据是「匹配内容后面还跟着别的字符约束」——比如 <(\w+?)>、>.*?</div>,一旦标签名后面必须是尖括号或结束标签,就加 ?。反过来,如果后面没有可靠的分界符,懒惰只会匹配到空串,贪婪反而正确。
正则里的 . 明明写了却匹配不到换行,该怎么改?
点号的定义就是「除换行外的任意单个字符」,所以 <div>.*</div> 在多行文本上只会匹配同一行内的内容。两种解法:其一是在模式上加 /s(dotAll)标志,让 . 也能吞换行;其二是把 . 换成 [\s\S],因为它同时包含空白与非空白字符,天然覆盖换行,且不需要额外标志。若只想匹配有限几行,用 /m 让 ^ 和 $ 锚定到每一行行首行尾,再配合惰性量词,别一上来就开 /s 把整个文件都扫进来。
嵌套量词 (.+)+ 会让程序卡死,该怎么发现并改写?
成因是回溯:外层量词尝试一种划分失败后要退回内层重试,嵌套结构让失败路径呈指数级增长。典型症状是短字符串毫秒返回,稍长的输入直接吃掉 CPU 满载几秒甚至超时。改写方向有三:把 (.+)+ 换成 (.+)——分组里本来就没有量词必要;把重复的内容收紧为 (.+?)+ 仍不安全时,改用 ([^x]+)+ 排除不可能出现的字符;或干脆绕开正则,把匹配拆成两次单次匹配(本表里的 (?=...) 正向前瞻也可用来做「校验存在但不消费」的预处理)。写完用一段明显超长的字符串做一次冒烟测试是必要的。
捕获分组、(?:...) 和命名分组该怎么选?
看你要不要从结果里取值。普通 (abc) 是捕获组,匹配结果里可以按序号取出,替换里还能用 \1 反向引用。(?:abc) 是非捕获分组,只做组合不占编号——当一组只是为了把量词或分支绑在一起(比如把两个引号字符用 | 串起来再套一层)时用它,可以省下编号、避免编号错位。命名组 (?<name>abc) 在结果多、序号难记时最清楚,取值和反向引用都能写 \k<name>。原则:只为取值而捕获,其余一律用 (?:),结果结构复杂再升级为命名组。
校验手机号、日期、邮箱这类格式,正则该怎么写才不出错?
照本表的实战示例写:邮箱用 [\w.+-]+@[\w-]+\.[\w.-]+,中国大陆手机号用 1[3-9]\d{9},日期用 ^\d{4}-\d{2}-\d{2}$,密码用 ^[#\w-]{6,}$。三个细节决定成败:一是加 ^ 和 $ 锚定整串,否则 1[3-9]\d{9} 会从长文本中截出 11 位片段误判通过;二是 \d 只覆盖 ASCII 数字,写 \d{4}-\d{2}-\d{2} 时要意识到它并不等价于「任意数字」,需要更宽松就把字符类写成 [0-9] 加 u 模式;三是字符类里 # - _ 不需转义,但 . 在字符类中已是字面量,别再写成 \.。
由 巧匠 维护
公开更新于 2026年8月2日,内容持续校对官方文档。