正则表达式速查表
正则表达式(Regex)语法速查:元字符、字符类、锚点、量词、分组与前后行断言,附中文说明,调试正则时快速查阅。
原理解析
正则表达式是描述「字符串模式」的迷你语言:普通字符逐字匹配,元字符赋予特殊含义——. 匹配任意单字符,* 重复前项 0 次以上,+ 1 次以上,? 0 或 1 次,^/$ 锚定首尾,\d \w \s 匹配数字/词/空白。
贪婪量词(* +)会尽可能多匹配,加 ? 变懒惰(*?);括号用于分组与捕获,反向引用 \1 复用已捕获内容。危险点是「灾难性回溯」——某些模式在坏输入下复杂度指数爆炸,须避免在用户输入上套用嵌套量词。
什么时候该查这张表
这张表在你要查「正则里这个符号是什么意思」时有用。它按字符类、锚点、分组、量词、断言分类,把 PCRE/JavaScript 风格的写法列清楚。忘记某个符号时查这里比翻文档快。
不该查的情况:你在设计复杂匹配或调性能。正则表达式的可读性、回溯控制、灾难性回溯的规避属于工程判断,本表只回答符号语义,不回答「这个正则该怎么写」。
判断标准:先看引擎与方言。同一个 d 在 JavaScript、Python、PCRE 下有细微差别,A 与 ^ 在多行模式下的行为不同。跨语言复用正则前,必须确认目标引擎的语法支持。
特别注意贪婪与惰性:* 和 + 默认贪婪(尽可能多匹配),? 后缀使其惰性(尽可能少匹配)。这一个差异经常决定匹配结果对不对,尤其是配合锚点时——(.*) 匹配到最后一个分隔符,(.*?) 匹配到第一个。
边界与常见陷阱
贪婪量词加锚点导致匹配到行尾而非预期位置
日志解析里典型症状:用 「^(\S+)\s+(\S+)」 提取前两列,结果第二组吃掉了整行剩余内容。原因是 「\S+」 贪婪。修法是加惰性或明确排除:改 「^(\S+?)\s+」 或 「^(\S+)\s+(\S+)\s*$」。另一个常见变体是 「.*」 在多行文本里跨行匹配,需要用 「[^\n]*」 或开启单行模式修饰。
捕获组编号会随嵌套改变,替换时容易错位
替换串里的 「$1」、「\1」 引用的是捕获组序号,一旦在模式里插入新的括号,所有后续编号都会顺移。「(a)(b)」 改成 「(a)(x)(b)」 后,原来 「$2」 指向 b,现在指向 x。命名捕获组(「(?<name>...)」 配 「\k<name>」 或 「$<name>」)在多数现代引擎都支持,可读性远好于数字,且不受插入影响。
灾难性回溯:嵌套量词会让匹配耗时指数增长
「(a+)+$」 匹配一长串 a 后跟一个非 a 的输入时,会产生指数级回溯,几万字符就可能卡死页面。同类写法还有 「(\S+)*」、「(.*)*」。在 JavaScript 里这会冻结主线程,在 Node 服务端则阻塞事件循环。规避方法:避免嵌套量词、把量词改为惰性或具体次数、或用「先整体匹配再判断」的写法替代回溯。
零宽断言的匹配位置容易被误读
「(?=...)」 是正向预查、「(?!...)」 是负向预查,它们不消耗字符,所以相邻的两个预查可以同时匹配同一个位置。常见困惑:「\b\w+@\w+.\w+\b」 中两个 「\b」 看似多余,实际是必要的边界断言。另一个坑是预查内的分组会消耗编号,「(?=(a))b」 的组 1 是参与的,虽不消耗字符但仍占编号。
与相邻内容的分工
要查某个符号的完整语法(含命名组、递归、Unicode 属性等),以目标语言的官方文档为准;本表覆盖常用子集。
要检查正则是否匹配目标字符串,用站内正则测试工具实测;本表只给语义,不给验证。
要处理正则与替换的多行编辑场景,涉及语言特定标志(如 Python 的 re.M、JS 的 m/s/u/y),需查对应语言文档。
若目的是写日志解析器或数据清洗流程,模式设计往往还需考虑流式处理与部分匹配,不在本表范围。
实战片段
要点:① 默认量词贪婪,(.*) 会吃到最后一个分隔符,提取列要用惰性或 [^\s]+ 这类排除类;② 需要多组捕获时优先用命名捕获组 (?<name>…),插入新组不会让后续编号错位;③ 预查/边界断言不消耗字符,但预查内的分组仍占编号;④ 禁止嵌套量词((a+)+$、(.*)*),它会指数级回溯并冻结主线程,长串务必用线性写法。
// ① 贪婪 vs 惰性:日志解析的经典坑
const line = 'GET /api/users 200 12ms'
console.log(line.match(/^(S+)s+(S+)/)[2]) // '/api/users' ✅
console.log(line.match(/^(S+)s+(.*)/)[2]) // '/api/users 200 12ms' ← 贪婪吃掉整行
console.log(line.match(/^(S+?)s+(S+)/)[2]) // '/api/users' ✅ 惰性
// ② 行内只取第一段要用排除类,不要用 .*
const safe = /^([^s]+)s+([^s]+)/
console.log(safe.exec(line).slice(1)) // ['GET', '/api/users']
// ③ 命名捕获组:插入新组不会打乱编号
const re = /(?<method>w+) (?<path>S+) (?<status>d{3})/
const m = re.exec(line)
console.log(m.groups) // { method: 'GET', path: '/api/users', status: '200' }
// ④ 零宽断言不消耗字符
console.log(/cat/.test('a cat sat')) // true 单词边界
console.log(/cat(?= )/.test('concatenate')) // false 预查要求后面是空格
// ⑤ 防灾难性回溯:避免嵌套量词
const BAD = /^(a+)+$/ // ❌ 指数级回溯
const OK = /^a+$/ // ✅ 线性
// 确认不会卡死(仅测短串,长串请勿在主线程直接跑)
console.log(OK.test('a'.repeat(1000)))实践注解
- . 默认不匹配换行,且不同语言默认值不一样多数引擎里 . 不匹配 \n,需要显式开 dotAll(JS 的 s 标志、Python 的 re.S)。但 POSIX 工具(grep、sed)的默认行为又不同,跨语言搬运正则时这是最常见的不一致来源。
- 灾难性回溯来自嵌套量词(a+)+b 这类模式在长串不匹配时会有指数级回溯,几百字符就能让进程 CPU 打满。改用原子组/占有量词,或把嵌套量词拆成更明确的字符类,能从根本上避免。
- 贪婪与懒惰只影响匹配长度,不影响能否匹配把 .* 改成 .*? 不会让「本来匹配不上」的变成能匹配(只有当后面还有可匹配内容时才有区别)。误以为加问号能「修好」不匹配的正则,是最常见的排查弯路。
数据来源
- ECMAScript Language Specification (regular expression semantics)
- PCRE2 documentation
- POSIX.1-2017 (BRE/ERE)
上述编号、名称与定义均来自公开规范,本站不主张对规范数据本身的独创性;本页的编排方式、中文释义与实践注解由本站撰写。
| 符号 | 含义 |
|---|---|
| . | 任意单个字符(默认不含换行) |
| \d | 数字 [0-9] |
| \D | 非数字 |
| \w | 单词字符 [A-Za-z0-9_] |
| \W | 非单词字符 |
| \s | 空白字符(空格/制表/换行) |
| \S | 非空白字符 |
| ^ | 字符串或行开头 |
| $ | 字符串或行结尾 |
| \b | 单词边界 |
| \B | 非单词边界 |
| * | 前一项 0 次或多次(贪婪) |
| + | 前一项 1 次或多次(贪婪) |
| ? | 前一项 0 或 1 次 |
| {n} | 恰好 n 次 |
| {n,} | 至少 n 次 |
| {n,m} | n 到 m 次 |
| (...) | 捕获组 |
| (?:...) | 非捕获组 |
| [...] | 字符类(匹配其中任一) |
| [^...] | 取反字符类 |
| | | 或(分支) |
| \1 | 反向引用第 1 个捕获组 |
| (?=...) | 正向先行断言 |
| (?!...) | 负向先行断言 |
| (?<=...) | 正向后行断言 |
| (?<!...) | 负向后行断言 |
常见问题
贪婪量词与非贪婪量词有什么区别?
贪婪(*、+)会尽可能多匹配;在其后加 ?(*?、+?)变为惰性,尽可能少匹配。例如对 "<a>1</a><a>2</a>",<.*> 会贪心匹配到最后一个 </a>,而 <.*?> 只匹配到第一个。
如何让 . 也能匹配换行符?
默认 . 不匹配换行。可启用 dotall 模式(JS 用 s 标志 /[sS]/,或 PHP/Python 的 DOTALL),或用 [sS] 这类写法兼容所有引擎。
字符类和分组有什么不同?
[abc] 只匹配单个字符(a、b 或 c);(abc) 是一个整体序列,匹配字面子串 "abc",并可作为捕获组整体量化(如 (abc)+)。
写正则要注意什么性能问题?
警惕灾难性回溯:避免嵌套量词如 (a+)+ 匹配失败时会指数级回溯。尽量用明确字符类、锚点 ^ $ 缩小范围,必要时用占有量词或原子组。