正则的转义规则容易陷入两种极端:要么见到符号就加反斜杠(正则变得没法读),要么凭印象加(漏掉关键的那个)。
其实规则很清楚,只是它分三处,而且三处不一样。
一、模式里:12 个元字符
在正则模式中(字符类外面),这些字符有特殊含义,想匹配它们本身就要转义:
. ^ $ * + ? ( ) [ ] { } | \
| 字符 | 含义 |
|---|---|
. | 任意字符 |
^ $ | 行首 / 行尾锚点 |
* + ? | 量词 |
{ } | 次数量词 {2,5} |
( ) | 分组 |
[ ] | 字符类 |
| | 或 |
\ | 转义符本身 |
最常被漏掉的是点号。匹配 IP 地址、域名、文件扩展名时,. 不转义就成了「任意字符」:
/1.2.3.4/ 匹配 "1x2y3z4" ✗
/1\.2\.3\.4/ 只匹配 "1.2.3.4" ✓
除此之外的字符——/ , ; : ' " - _ @ # % & = < > 等等——都不是元字符,不需要转义。给它们加反斜杠不会报错,但会让正则变难读。
二、字符类里面:规则完全不同
这是最容易记混的一处:方括号里面,绝大多数元字符都失去特殊含义。
[.*+?] ← 匹配点、星号、加号、问号这四个字符本身
不需要任何转义。
类内真正需要当心的只有四个:
| 字符 | 规则 |
|---|---|
\ | 永远要转义 |
] | 要转义,或者放在第一个位置 |
^ | 只在第一个位置表示取反,其他位置是字面量 |
- | 在两个字符中间表示范围;放在首位或末位就是字面量 |
所以下面这些都是合法且常见的写法:
[a-z-] ← 小写字母,加上横杠本身(横杠在末位)
[-a-z] ← 同上(横杠在首位)
[a^b] ← 匹配 a、^、b(脱字符不在首位)
[]a] ← 匹配 ] 或 a(右括号在首位)
一个实用习惯:想匹配横杠时把它放在末位,比写 \- 更常见,也更容易一眼看懂。
三、替换字符串里:那个 $
这一处最容易出事,因为它和正则模式是两套完全不同的语法。
JS 的替换串里,$ 是特殊字符:
| 写法 | 含义 |
|---|---|
$& | 整段匹配到的内容 |
$1 $2 | 第一、第二个捕获组 |
$` | 匹配位置之前的全部文本 |
$' | 匹配位置之后的全部文本 |
$$ | 一个字面的美元符号 |
典型事故:把价格 $100 作为替换内容写进去。
'价格'.replace(/价格/, '$100')
// $1 被当成第一个捕获组,结果是「组的内容 + 00」
两种避法:
- 替换内容含
$时写成$$ - 更稳的做法是用函数形式:
str.replace(re, () => 用户提供的内容) // 返回值不做任何 $ 解释
其他语言的替换串用 \1 这类反斜杠语法,反斜杠同样要按各自的规则转义。
四、动态拼接:必须调转义函数
把变量拼进正则,是这四处里唯一会造成安全问题的。
const re = new RegExp(userInput); // ✗ 危险
三种后果:
- 语义错误 —— 用户搜
1+1,被解释成「一个或多个 1,后面跟一个 1」 - 抛异常 —— 用户输入
(,正则语法错误 - 灾难性回溯 —— 精心构造的输入能让主线程卡住几秒到几分钟
各语言的转义函数:
| 语言 | 函数 |
|---|---|
| Python | re.escape() |
| Java | Pattern.quote() |
| PHP | preg_quote() |
| Go | regexp.QuoteMeta() |
| JavaScript | 没有内置(有 RegExp.escape 提案) |
JS 通行的手写版是把这一组字符前面统一加反斜杠:
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
判断原则很简单:正则里有一段来自变量时,问一句「这段是模式还是字面量」——是字面量就必须转义。
五、两层解析:new RegExp 的双反斜杠
/\d/ // 正则字面量,一个反斜杠
new RegExp("\\d") // 字符串构造,两个
原因:new RegExp 的参数先被字符串解析器处理一遍,再交给正则引擎。源码里的 "\\d" 变成两个字符 \ 和 d,正则引擎才看到「数字」这个含义。
写成 "\d" 会怎样?字符串解析时 \d 不是已知转义序列,多数 JS 引擎会宽容地当成 d——不报错,但正则变成了匹配字母 d。
推论:
- 能用正则字面量就用字面量,少一层解析
- 匹配一个反斜杠要写四个:
new RegExp("\\\\") - 从配置文件、数据库里读的正则字符串不经过源码解析,那里写一个反斜杠就够——这一点最容易和源码里的写法搞混
速查
| 位置 | 需要转义的 |
|---|---|
| 模式中 | . ^ $ * + ? ( ) [ ] { } | \ |
| 字符类内 | \、]、首位的 ^、中间的 - |
| 替换串(JS) | $(写成 $$),或改用函数形式 |
| 字符串构造 | 反斜杠再翻一倍 |
80 条语法的完整对照和方言差异标注在 正则语法速查;要验证一条表达式实际匹配到什么,用 Regex Pro。
相关
- 各方言之间的差异(
\A、(?i)、后行断言) → PCRE / POSIX / JS / Python 的方言差异 - 灾难性回溯的成因与规避 → 正则的灾难性回溯
- 常用的十条现成模式 → 10 个最常用的正则
- 替换模板和跨语言导出 → 替换模板与跨语言导出