正则里哪些字符要转义:字符类里外是两套规则

· 约 4 分钟 🔣 正则语法速查

正则的转义规则容易陷入两种极端:要么见到符号就加反斜杠(正则变得没法读),要么凭印象加(漏掉关键的那个)。

其实规则很清楚,只是它分三处,而且三处不一样。

一、模式里: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」

两种避法

  1. 替换内容含 $ 时写成 $$
  2. 更稳的做法是用函数形式
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

相关

❓ 常见问题

字符类里面到底要不要转义点号?

不要,[.] 里的点就是普通的点字符类内部的规则完全不同. * + ? ( ) { } | ^ $ 这些在外面是元字符,放进方括号里绝大多数都变成字面量,加反斜杠虽然不报错,但会让正则变得难读。类内真正需要当心的只有四个:(1) \ —— 永远要转义;(2) ] —— 要转义,或者放在类的第一个位置;(3) ^ —— 只有在第一个位置才表示取反,其他位置是字面量;(4) - —— 在两个字符中间表示范围,放在首位或末位就是字面量。所以 [a-z-][-a-z] 都合法,末尾和开头那个横杠就是横杠本身。一个实用写法:想匹配横杠时把它放末位,比写 \- 更常见也更易读。

替换字符串里为什么会冒出奇怪的内容?

因为替换串里的 $ 是特殊字符,和正则模式是两套语法JS 里的几个:(1) $& —— 整段匹配到的内容;(2) $1 $2 —— 第一、第二个捕获组;(3) ` $ ` —— 匹配位置之前的全部文本;(4) $' —— 匹配位置之后的全部文本;(5) $$ —— 一个字面的美元符号。典型事故:把价格 $100 作为替换内容写进去,$1 被当成第一个捕获组,结果变成了组的内容加上 00怎么避免:(1) 替换内容含 $写成 $$;(2) 更稳的做法是用函数形式的替换 —— str.replace(re, () => 用户内容),函数返回值不做任何 $ 解释;(3) 其他语言的替换串用 \\1` 这类反斜杠语法,反斜杠同样要转义

把用户输入拼进正则要做什么处理?

必须先转义,否则轻则匹配错误,重则页面卡死问题:用户搜索 1+1,直接拼成 new RegExp("1+1") 会被解释成「一个或多个 1,后面跟一个 1」,匹配结果完全不是用户要的;输入 ( 更直接——正则语法错误抛异常。更严重的是:精心构造的输入可以造成灾难性回溯,让主线程卡住几秒到几分钟。各语言的转义函数:Python re.escape()、Java Pattern.quote()、PHP preg_quote()、Go regexp.QuoteMeta()JavaScript 目前没有内置(有 RegExp.escape 提案),通行的手写版是把 [.*+?^${}()|[\]\\] 这一组字符前面统一加反斜杠。原则:凡是正则模式里有一段来自变量,就问一句这段是模式还是字面量——是字面量就必须转义。

new RegExp("\\d") 为什么要写两个反斜杠?

因为它经过了两层解析:先是字符串字面量,再是正则过程:源码里的 "\\d" 被字符串解析器处理成两个字符 \d,这两个字符再交给正则引擎,才得到「数字」这个含义。写成 "\d" 会怎样:字符串解析时 \d 不是已知的转义序列,多数 JS 引擎会宽容地当成 d,于是正则变成匹配字母 d——不报错,但行为完全错了推论:(1) 用正则字面量 /\d/ 就没有这一层,只要写一个反斜杠,能用字面量时优先用;(2) 反斜杠本身在这种场景下要写四个 —— new RegExp("\\\\") 才是匹配一个反斜杠;(3) 从配置文件、数据库里读正则字符串时不经过源码解析,那里写一个反斜杠就够,容易和源码里的写法搞混。

🔣 打开 正则语法速查 字符类/量词/断言/标志全表·JS 与 PCRE 差异标注·手机号身份证等常用模式可直接复制

🔗 相关阅读

全部教程 →