# 正则表达式速查表：常用语法、模式与测试方法

> 一份面向实践的正则参考：锚点、字符类、量词、分组与环视，配合常用模式和避免灾难性回溯的检查方法。

**快速答案:** 正则由一组记号组成：字符类匹配单个字符，量词控制重复，锚点固定位置，分组负责组合。用带示例文本的测试工具验证，区分贪婪与懒惰匹配，并警惕可能导致灾难性回溯的嵌套量词。

## 核心要点

- 需要匹配字面量时，记得转义元字符。
- 贪婪量词尽可能多匹配，懒惰量词在第一次机会处停止。
- 锚点匹配的是位置，不是字符。
- 命名分组和环视能提高可读性，但不同引擎支持程度不同。
- 用合法、非法和对抗性输入测试，嵌套量词可能拖垮正则引擎。

---

正则表达式是一种描述文本模式的紧凑语言，也很容易写的时候明白、过几天就看不懂。本文整理了最常用的记号，以及避免无声错误的测试习惯。

## 字面量与元字符

大多数字符只匹配自身。以下字符有特殊含义，需要匹配字面量时必须转义：

```text
. * + ? ^ $ { } [ ] ( ) | \
```

例如 \. 匹配字面量点号，\* 匹配字面量星号。不同语言的转义规则略有差异，尤其是在字符类和替换字符串内部。

转义问题常常发生在正则引擎之前的那一层。JavaScript 字符串里写 "\d"，\d 不是合法字符串转义，结果只剩一个 d，必须写 "\\d" 或使用正则字面量 /\d/。Python 的原始字符串 r"\d" 可以避开双反斜杠。字符类内部大多数字符失去特殊含义，但 ]、\、^ 和 - 仍需小心：[-a] 把连字符放在开头，[a\-z] 才表示字母 a、连字符和 z，而不是范围。部分引擎支持 \Q...\E 引用一段字面量，JavaScript 和 Python 不支持，只能逐个转义。

## 字符类

| 模式 | 匹配内容 |
| --- | --- |
| . | 除换行外的任意字符，除非开启 dotAll |
| \d | 数字，通常是 [0-9] |
| \w | 单词字符：字母、数字和下划线 |
| \s | 空白字符 |
| [abc] | a、b、c 中的一个 |
| [^abc] | 除 a、b、c 外的任意字符 |
| [a-z] | a 到 z 范围内的字符 |
| [A-Za-z0-9_] | ASCII 下 \w 的显式写法 |

否定字符类往往比懒惰量词更清晰。"[^"]*" 匹配从引号开始到下一个引号结束的字符串。

这些简写字符类没有看起来那么通用。JavaScript 不开 u 标志时，\d 等于 [0-9]，\w 等于 [A-Za-z0-9_]，\s 只覆盖固定的 ASCII 空白；开启 u 后，\d 和 \w 仍只认 ASCII，\s 才扩展到 Unicode 空白。Python 3 的 str 模式默认按 Unicode 解释，\w+ 能匹配西里尔字母和中文，\d 能匹配阿拉伯-印度数字。需要只认 ASCII 时，应加 re.ASCII 或改用显式字符类。浏览器和服务器上表现不一致，常常就是这里造成的。

点号也比很多人以为的窄。默认它不匹配 \n；JavaScript 不开 s 时还不匹配 \r 以及 Unicode 的行分隔符和段分隔符。确实想匹配任意 UTF-16 码元时，JavaScript 可以写 [^]，需要跨引擎可移植时写 [\s\S]。

## 量词

| 模式 | 含义 |
| --- | --- |
| * | 零次或多次 |
| + | 一次或多次 |
| ? | 零次或一次 |
| {3} | 恰好三次 |
| {2,5} | 两到五次 |
| {2,} | 两次或更多 |
| *? | 零次或多次，懒惰 |
| +? | 一次或多次，懒惰 |

贪婪量词尽可能多匹配，懒惰量词尽可能少匹配。".*" 会从第一个引号一直匹配到最后一个引号，".*?" 只匹配一个引号片段。

懒惰不等于高效。.*? 仍然是匹配、失败、前进一个字符、再匹配，面对很长的非匹配字符串可能退化成平方级。PCRE、Java 和 Perl 提供占有量词 *+、++、?+，消费后不回溯；原子分组 (?>...) 对子表达式做同样的事。确定量词不应该吐回字符时，它们才是正确工具。JavaScript 没有这两种语法，但可以用先行断言模拟一部分原子分组。

## 锚点与边界

| 模式 | 含义 |
| --- | --- |
| ^ | 字符串开头；开启 m 后也匹配行首 |
| $ | 字符串结尾；开启 m 后也匹配行尾 |
| \b | 单词边界 |
| \B | 非单词边界 |
| \A | 部分引擎中的字符串开头 |
| \z | 部分引擎中的字符串结尾 |

锚点匹配的是位置，不是字符。^$ 匹配空行，而不是包含空格的行。

末尾锚点在不同引擎里并不等价。Python 3.14 及以后，\A 和 \z 始终表示整个字符串的开头和结尾，\Z 允许末尾多一个换行，$ 即使不开多行模式也会匹配结尾换行之前的位置。JavaScript 不开 m 时，$ 只匹配真正的末尾，"abc\n" 无法通过 c$，除非先去掉换行。\b 是单词字符与非单词字符之间的位置，不是字符本身，所以单独一个 \b 匹配零个字符，只高亮边界。输入含中文时，\b 是否把汉字当作单词字符，取决于引擎和 u 标志的定义。

## 分组与分支

- 捕获分组：(abc) 会捕获匹配文本。
- 非捕获分组：(?:abc) 只分组，不捕获。
- 命名分组：(?<name>abc) 按名称捕获。
- 反向引用：\1 或 \k<name> 引用之前的捕获。
- 分支：cat|dog 匹配任意一个。

分支的优先级很低。^cat|dog$ 等价于 ^cat 或 dog$。如果锚点应同时作用于两个分支，应写成 ^(cat|dog)$。

分支是有顺序的，不是对称的。cat|category 会在 category 中匹配到 cat，第一支成功后引擎不会再尝试更长的分支。一个分支是另一个的前缀时，把长的放在前面，或者加锚点限定。捕获组编号按左括号出现的顺序分配，而不是按实际匹配顺序，所以在中间插入一个分组会让后面所有反向引用重新编号。命名分组能避免这种脆弱性，但写法不同：JavaScript 和 PCRE 用 (?<name>...)，Python 用 (?P<name>...)，反向引用分别是 \k<name> 和 (?P=name)。替换语法又是另一套，这也是为什么测试工具里正常的正则，写进代码后会失败。

## 环视

| 模式 | 含义 |
| --- | --- |
| (?=abc) | 后面跟着 abc |
| (?!abc) | 后面不是 abc |
| (?<=abc) | 前面是 abc |
| (?<!abc) | 前面不是 abc |

环视只断言上下文，不消费字符。\d+(?= dollars) 会匹配“25 dollars”中的数字，但不会匹配“25 euros”。不同引擎对环视的支持和长度限制不同，部分旧引擎不支持后行断言。

后行断言是最容易破坏可移植性的特性。Python 要求定宽：(?<=abc) 可以，(?<=a|bc) 不行，(?<=\d+) 因为长度无界会直接报错。JavaScript 的后行断言更宽松，允许变长，.NET 和许多情况下的 PCRE2 也允许。于是 Chrome 里测试通过的正则，到 Python 里可能连编译都过不了。必须同时运行时，应把后行断言换成捕获组并在替换时调整，或者在代码里先过滤候选。

## 标志

| 标志 | 作用 |
| --- | --- |
| i | 忽略大小写 |
| m | 多行模式，^ 和 $ 匹配行边界 |
| s | 点号匹配换行 |
| g | 全局查找所有匹配 |
| u | Unicode 模式 |
| y | 粘性匹配，从当前位置开始 |

标志会改变语义，不只是方便程度。没开 m 时测试通过的模式，开启 m 后可能表现不同。

有两个标志会制造状态 bug。JavaScript 的 g 正则会维护 lastIndex，对同一个正则对象连续调用两次 test()，同一个字符串可能先返回 true 再返回 false，因为第二次从上次停下的位置继续。要么每次重置 lastIndex，要么把正则创建在循环内部。开启 u 后，引擎按 Unicode 码点解释模式，点号会把一个 emoji 当作一个码点而不是两个代理项，也能使用 \p{Letter}。较新的 v 标志在 u 之上增加集合运算，例如 [\p{Letter}--[a-z]]。但 u 和 v 都不会让 JavaScript 的 \d 匹配非 ASCII 数字，那仍然需要显式的 Unicode 属性类。

## 常用模式

- 纯数字：^\d+$
- 带符号整数：^[+-]?\d+$
- 十六进制颜色：^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
- ISO 日期形状：^\d{4}-\d{2}-\d{2}$
- URL slug：^[a-z0-9]+(?:-[a-z0-9]+)*$
- 简单邮箱形状：^[^\s@]+@[^\s@]+\.[^\s@]+$
- 带协议的 URL：^https?://[^\s/$.?#].[^\s]*$

这些模式校验的是形状，不是语义。2026-99-99 可以通过日期形状校验。涉及真实有效性时，应配合日期解析器使用。简单邮箱模式也无法确认地址是否存在。

上面的 URL 模式故意写得很浅：它接受 http:// 后面几乎任何内容，却拒绝带端口、IPv6 字面量、用户信息或国际化域名的合法地址。要验证 URL，应使用平台的 URL 解析器并检查协议和主机，正则处理不了这套语法。邮箱模式一旦被加上嵌套量词，还很容易变成拒绝服务风险；保持简短，并用发信验证。日期应先用正则检查形状，再构造 Date 或交给日期库，因为 2026-02-30 和 2026-13-01 都能通过形状检查。颜色模式里 #abc 和 #aabbcc 合法，#abcd 不合法，上面的表达式已经覆盖这一点。

## 贪婪、懒惰与灾难性回溯

嵌套量词可能导致指数级爆炸。模式 (a+)+$ 面对一长串 a 后面跟一个 b 的输入时，可能尝试指数多种切分方式。

实用的防御措施：

- 避免对同一批字符使用嵌套量词。
- 用明确的字符类代替点号加重复。
- 引擎支持时，使用占有量词或原子分组。
- 引擎允许时，设置匹配超时。
- 不只测试能匹配的输入，也要测试对抗性输入。

“指数级”具体是什么样：对 (a+)+$ 来说，30 个 a 后面跟一个 b，可以切成 2^29 种分组，约 5.36 亿条路径。JavaScript 或 PCRE 这类回溯引擎可能要跑几秒到几分钟才报告失败；到 40 个 a，同样的输入能拖过任何请求超时。^(a|aa)+$ 和 ^(\w+\s?)*$ 在同样短的输入上表现相同。曲线非常陡，10 个字符的测试看起来没事，生产环境 50 个字符就会挂住。

引擎选择很关键。RE2、Go 的 regexp 和 Rust 的 regex 不回溯，时间复杂度是线性的，但也不支持反向引用和部分环视。模式确实需要这些特性时，应留在回溯引擎上，同时限制输入长度并设置超时。在 Node.js 里，灾难性回溯会阻塞整个事件循环，而不是只卡住一个请求，最终会拖垮无关流量。

## 替换字符串

JavaScript 用 $1 和 $<name> 插入捕获，Python 用 \1 和 \g<name>，部分工具用 $& 表示整个匹配。替换语法因语言和工具而异，使用前应确认目标方言。

还有几个替换记号值得记住。JavaScript 支持 $$ 表示字面量美元符号，$& 表示整个匹配，$` 表示匹配之前的文本，$' 表示匹配之后的文本。Python 的 \g<0> 表示整个匹配；\1 后面紧跟数字时会产生歧义，写 \g<1>0 更安全。两种语言里，未参与匹配的可选分组在替换时都会插入空字符串，而不是保留原文，这会在不知不觉中删掉内容。替换需要逻辑时，应传函数：JavaScript 支持 replacer 函数，Python 的 re.sub 第二个参数也可以传函数，两者拿到的都是匹配对象，而不是模板字符串。

[正则测试工具](/tools/regex-tester)可以在示例文本上高亮匹配和捕获分组，[正则生成器](/tools/regex-generator)可以根据自然语言需求生成模式，[查找替换工具](/tools/find-replace)可以把模式应用到真实文本。生成的正则应先经过测试用例验证，再写入代码。

## 测试清单

1. 至少测试一个合法输入。
2. 测试一个不应匹配的非法输入。
3. 测试空输入。
4. 测试包含换行和 Unicode 字符的输入。
5. 检查有锚点和无锚点的差异。
6. 确认生产环境使用的引擎和标志。
7. 用很长的非匹配字符串测试性能。
8. 为复杂的正则保存注释和示例。
9. 同样的用例要在生产所用引擎上再跑一遍，而不只是在测试工具里。
10. 正则执行前先限制输入长度，即使模式看起来安全。

正则也是代码。保持可读，用真实示例测试，并把任何带嵌套量词的模式当作需要重点审查的对象。

有些任务根本不该用正则。用正则解析 HTML 会在嵌套标签、注释和含 > 的属性上出错，应使用 HTML 解析器。用正则校验 JSON、CSV 或 URL，等于重新实现一套已有解析器的语法，边界情况几乎必错。判断字符串是否包含固定子串、是否以某个前缀开头、按分隔符切分，用 includes、startsWith 或 split 更快也更清楚。只有当模式确实不规则、但仍然属于正则语言时才用正则；输入存在嵌套或正式语法时，交给解析器。

## 常见问题

### 点号匹配什么？

默认情况下，点号匹配除换行符以外的任意字符。开启 s 或 dotAll 标志后，它也会匹配换行。需要精确控制时，可以用 [^\n] 这样的否定字符类。

### * 和 + 有什么区别？

星号匹配零次或多次，因此模式可以匹配空字符串。加号匹配一次或多次，至少需要出现一次。

### 为什么正则匹配了太多内容？

贪婪量词会尽可能多地消费文本，然后回溯。如果只想要最短匹配，可以使用 .*? 这样的懒惰量词，或让前后模式更具体。

### 用正则校邮箱靠谱吗？

简单正则可以拦截明显错误，但完整的邮箱校验非常复杂，而且规则会变化。可以用正则检查基本形状，再通过发送验证邮件确认地址真实存在。

### 什么是灾难性回溯？

某些模式，尤其是 (a+)+ 这样的嵌套量词，在不匹配的长输入上可能需要指数级时间。应减少嵌套，让分支彼此区分，并用对抗性字符串测试。

## 参考资料

- [MDN - 正则表达式](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_expressions)
- [MDN - RegExp 语法参考](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Regular_expressions)
- [RE2 语法](https://github.com/google/re2/wiki/Syntax)
- [OWASP - 正则表达式拒绝服务](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS)

---

- Canonical: https://www.jsonmind.com/zh-CN/guides/regex-cheat-sheet
- Locale: zh-CN
- Published: 2026-11-10
- Updated: 2026-11-10
- Tools: regex-tester, regex-generator, find-replace