正则表达式反向引用:让正则记住自己匹配过的内容
像 \1 这样的反向引用,匹配的是某个分组已经捕获的那段文本。本文讲解它的原理,如何找出双写字母、重复单词和回文,以及它唯一不起作用的地方。

· 阅读约 4 分钟
/(.)\1/
抓住
- book
- letter
- coffee
放过
- cat
- dog
- lemon
正则表达式的大部分内容描述的是文本的种类:一个数字、一个字母、任意一个字符。反向引用不一样,它说的是“我刚才匹配到的那段文本”。凭这一个概念,一个很短的正则就能找出双写字母、重复单词和回文,这些都是字符类描述不了的。
下面的每个示例都可以实时编辑。修改正则,或点击 试试,在沙盒中打开它。
01先捕获,再引用
括号有两个作用:分组和捕获。分组匹配到的文本会被保存下来,并按左括号从左到右编号。\1 表示“第 1 组捕获的文本”,\2 表示第 2 组,依此类推。
关键在于相同的文本。(\w)\w 的意思是“一个字母,后面跟任意字母”,所以几乎什么都能匹配。(\w)\1 的意思是“一个字母,后面再跟一个完全相同的字母”。反向引用重复的不是分组里写的正则,而是它匹配出的结果。
02重复单词
校对时的一个经典错误是单词重复:“the the”。先捕获一个单词,再要求后面跟一个空格和同一个单词:
如果没有末尾的 \b,the then 就会匹配:the、一个空格,然后是 then 开头的 the。
03首尾字母相同
锚点和反向引用配合,可以描述一个单词的形状:
04五个字母的回文
用两个分组,就能让单词形成镜像。捕获前两个字母,中间允许任意一个字母,再要求这两个字母按相反顺序出现:
lever 很接近:它以 le 开头,但结尾是 er,不是 el。
05成对的引号
反向引用可以确保结束引号和开始引号一致。(["']) 捕获任意一种引号,\1 再用同一种引号收尾:
06命名组
在很长的正则里,数字编号很难看清。大多数引擎允许给分组命名,再按名字引用它:
/(?<ch>.)\k<ch>/不同语言的语法不同。JavaScript、.NET 和 PCRE 使用 (?<name>…) 和 \k<name>。Python 内置的 re 模块使用 (?P<name>…) 和 (?P=name)。
07替换中的反向引用
替换文本时,捕获的分组同样有用。在大多数编辑器和编程语言中,你都可以把分组按新的顺序放回去:搜索 (\w+), (\w+),替换为 $2 $1(JavaScript、VS Code)或 \2 \1(Python、sed),就能把 Lovelace, Ada 变成 Ada Lovelace。
08唯一不起作用的地方:[ ] 内部
你可能很想写 (.)[^\1] 来表示“一个字符,后面跟一个不同的字符”。这样行不通。在字符类内部,\1 不是反向引用。JavaScript 会把它当成转义的字符编码,所以这个字符类的意思是“除编码为 1 的字符以外的任意字符”:
要表示“不是同一个字符”,请使用负向先行断言:(.)(?!\1).。(详见我们的先行断言与后行断言指南。)
09试一试
在这块棋盘上,每个怪物都有两组不同的双写字母,比如 coffee 里的 ff 和 ee。每个无辜者都只有一组:
抓住含有两组双写字母的单词。
目标 · 抓住 0/5
- coffee目标
- balloon目标
- bookkeeper目标
- committee目标
- mississippi目标
无辜词 · 误抓 0
- book无辜
- letter无辜
- apple无辜
- grass无辜
- hello无辜
输入正则——棋盘会实时更新。