正则的转义规则容易陷入两种极端:要么见到符号就加反斜杠(正则变得没法读),要么凭印象加(漏掉关键的那个)。
其实规则很清楚,只是它分三处,而且三处不一样。
一、模式里: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」
抛异常 —— 用户输入 (,正则语法错误
灾难性回溯 —— 精心构造的输入能让主线程卡住几秒到几分钟
各语言的转义函数:
语言函数Pythonre.escape()JavaPattern.quote()PHPpreg_quote()Goregexp.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 个最常用的正则
替换模板和跨语言导出 → 替换模板与跨语言导出