正则替换模板深度指南:$1 / $<name> / $& / $` / $' 全套占位符与 JS·Python·Java 跨语言迁移

· 约 4 分钟 🔍 Regex Pro

Regex Pro 的”匹配 / 解释”双面板很多人都会用,但真正吃掉日常 80% 时间的是替换——日志清洗、字段重排、变量重命名、HTML 转义、批量改文件名,每一个都依赖替换模板。这篇把替换面板里所有占位符的语义、跨语言迁移时要改的语法、以及最容易翻车的”链式替换”陷阱一次讲清。

6 种占位符的精确语义

JavaScript(也是 Regex Pro 替换面板)支持的全部占位符,记不全就翻这张表:

占位符含义例:(\d{4})-(?<m>\d{2})2026-05
$&整个匹配本身$&2026-05
$1$99第 N 个捕获组$1/$22026/05
$<name>命名捕获组$<m>月05月
$`匹配之前的全部内容整个文本如果是 日期: 2026-05$`日期:
$'匹配之后的全部内容整个文本如果是 2026-05 周一$' 周一
$$字面 $ 符号$$$1$2026(一个字面 $ + 第一组)

注意 $10 的解释顺序——引擎优先当成 $1 后跟字面 0,要强制引用第 10 组要写 ${10}(部分语言支持)或重新排列分组。

6 个高频替换模板

直接抄即可用。Pattern → Replace 都是 Regex Pro 替换面板的写法。

1. 日期重排(ISO → 中文)

Pattern:  (?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})
Replace:  $<y>年$<m>月$<d>日

2. 蛇形转驼峰

Pattern:  _([a-z])
Replace:  $1

然后对 $1toUpperCase——但替换模板不支持函数变换,这一步只能切回代码里用回调:str.replace(/_([a-z])/g, (_, c) => c.toUpperCase())

3. 把所有数字加方括号

Pattern:  \d+
Replace:  [$&]

4. CSV 字段 quote

Pattern:  ([^,\n]*)
Replace:  "$1"

注意:含逗号或引号的字段还要先把内部 " 双写转义,单条正则做不到,必须回调。

5. 给所有金额加美元前缀

Pattern:  \d+(?:\.\d{2})?
Replace:  $$$&

$$ 是字面 $$& 是匹配——输出 $100.50

6. 提取 HTML 标签名

Pattern:  <(?<tag>[a-z]+\d*)
Replace:  $<tag>

跨语言替换语法速查

调好的 pattern + 替换搬到后端要改语法。完整对照:

概念JavaScriptPython re.subJava replaceAllGo ReplaceAllString
整匹配$&\g<0>$0$0
数字组$1\1(推荐 r"\1"$1$1
命名定义(?<n>...)(?P<n>...)(?<n>...)(?P<n>...)
命名引用$<n>\g<n>${n}(花括号)$n${n}
字面 $$$不需要\\\$Matcher.quoteReplacement()$$
字面 \\\\\(字符串里 \\\\\\\\

Python 跨语言迁移最大坑:JS 写 $<name>,搬到 Python 直接报错——必须改成 \g<name>,而且分组定义也得加 P:(?P<name>...)。Regex Pro 的 ⎘ Py 按钮已经处理这两条。

Java 第二个特殊点String#replaceAll 第二参在正则替换之外还会再解一遍反斜杠和 $,传任意字符串(比如用户输入)做替换时必须先 Matcher.quoteReplacement(input) 自动转义,否则带 $ 的字符串会抛异常。

三个”看起来对、实际错”的陷阱

1. 链式替换:第二次替换吃掉第一次的输出

经典反例——HTML 实体解码:

// 错:第二步会把第一步产生的 & 当成新的 &amp; 重新解
text.replace(/&lt;/g, '<').replace(/&amp;/g, '&')
// 原文:&amp;lt;       期望:&lt;        实际:<

正确做法(一次性回调):

text.replace(/&(amp|lt|gt|quot);/g, (_, g) =>
  ({ amp: '&', lt: '<', gt: '>', quot: '"' })[g]
)

Regex Pro 替换面板是单次扫描,链式场景必须复制结果再粘回去——或者干脆在工具里调好 pattern + 单次替换、用 ⎘ JS 导出代码后改成回调。

2. .* 贪婪让替换吞掉过多内容

Pattern:  <(.*)>
Replace:  [$1]

<a><b> 上的预期是 [a][b],实际是 [a><b]——.* 贪婪吃到最后一个 >

修复:用非贪婪 <(.*?)> 或具体字符类 <([^>]*)>,后者还能避免回溯。Regex Pro 的 AST 解释面板会把 .* 标成”任意字符(贪婪,可能引发回溯)“,看到就要警惕。

3. 没匹配到的命名组替换输出空串

Pattern:  (?<protocol>https?)?://(?<host>[^/]+)
Replace:  [$<protocol>][$<host>]

://example.com 上(缺协议)输出 [][example.com]不是 [$<protocol>][example.com]——$<protocol> 没匹配到时静默输出空串。要给默认值必须切回调:

str.replace(re, (_, protocol = 'https', host) => `[${protocol}][${host}]`)

Regex Pro 在这套工作流里的定位

阶段Regex Pro 能做Regex Pro 不能做
读 patternAST 中文解释、量词/分组/断言标层级
写替换6 种占位符实时预览,前 50 条命中函数式回调(要导出代码)
跨语言⎘ JS / Py / Java 复制按钮按方言转换Go / Rust / Ruby 需手改
大文本>200KB 自动切 grep 列表模式>10MB 浏览器内存吃紧
性能兜底Worker + 2.5s 超时拦灾难性回溯生产环境的 ReDoS 防护需自己加

替换是正则的”最后一公里”——pattern 写对了不等于结果对。用 Regex Pro 把模板调到位、再导出到目标语言,能省掉大量在 Node REPL 或 Python shell 里来回粘贴的时间。

❓ 常见问题

$1、$&、$<name>、$` / $' 这些占位符到底各自是什么意思?

都是 JavaScript String.prototype.replace 的标准替换占位符,Regex Pro 的"替换"面板用的就是这套。逐个解释:(1) $& —— 整个匹配本身;用例:给所有数字加方括号 pattern: \d+ / replace: [$&]abc123abc[123]。(2) $1 / $2 / … / $99 —— 第 N 个捕获分组;用例:交换日期顺序 pattern: (\d{4})-(\d{2})-(\d{2}) / replace: $3/$2/$12026-05-2626/05/2026。(3) $<name> —— 命名捕获组;用例:pattern: (?<y>\d{4})-(?<m>\d{2}) / replace: $<y>年$<m>月。(4) $(dollar + 反引号)—— 匹配之前的全部内容;很少用,但适合"把匹配前移到匹配后"。(5) $'(dollar + 单引号)—— 匹配之后的全部内容;同样冷门。(6) $$ —— 字面美元符号;想替换成 $100 必须写 $$100,否则 $1 会被当占位符。容易踩的坑:(a) $10 在引擎里优先解释成 $1 后跟字面 0,没有第 10 个分组时才回退;想强制第 10 组要写 ${10}(部分语言)或重排分组顺序;(b) 没匹配到的命名组替换会输出空字符串而不是字面 $<name>`。

替换字符串里想插入一个真的 $ 符号怎么写?

$$ 表示一个字面 $——这是 JS / Java / Go 的统一约定。例:pattern: (\d+) / replace: $$$1100 上输出 $100$$ 转成一个 $$1 是捕获组。常踩:想输出"打折后 $99"写成 replace: 打折后 $$99 才对;写 $99 会先尝试匹配第 99 个分组(不存在 → 不同引擎行为不同:Java 抛 IndexOutOfBoundsException、Python re.sub 抛 error、JS 保留字面)。Python 不一样re.sub\1 \2 \g<name> 反向引用 + \\ 转义反斜杠,不用 $——这是跨语言迁移最大坑(见下条)。Java 还要小心 \$——String#replaceAll 第二个参数额外解析反斜杠,字面 $ 需要写 \\\$(Java 源码里实际四个反斜杠);建议用 Matcher#quoteReplacement(str) 自动转义。

把 JS 调好的替换模板搬到 Python / Java 要改哪些地方?

整张速查表:(1) 整匹配 —— JS $& / Python \g<0> / Java $0 / Go $0。(2) 数字捕获组 —— JS / Java / Go 都用 $1$2Python 用 \1\2(在 re.sub 字符串里),原始字符串里推荐写 r"\1" 避免被 Python 字符串先吃掉反斜杠。(3) 命名捕获组定义 —— JS / Java / Go 用 (?<name>...)Python 用 (?P<name>...)(注意 P)。(4) 命名捕获组引用 —— JS $<name> / Python \g<name> / Java ${name}(注意是花括号)/ Go $name${name}。(5) 字面 $ —— JS / Java / Go $$;Python 没有 $ 占位符所以不需要转义。(6) 特殊场景 —— Java String.replaceAll 的第二个参数会解析反斜杠和 $,传任意字符串建议先 Matcher.quoteReplacement();JS 的 String.replace 第二个参数传函数时占位符全失效,转而拿 (match, p1, p2, ..., offset, string, groups) 参数。Regex Pro 的 ⎘ JS / ⎘ Py / ⎘ Java 复制按钮已经按这套规则自动改写命名分组语法和反斜杠转义,复制即可用,不必手抠。

为什么我"链式替换"两次就出错了?比如先把 A 替换成 B,再把 B 替换回 C 时结果不对

因为替换是"贪心一次性扫描"——第二次替换不区分"原文本里就有的 B"和"上一次替换产生的 B"。经典踩坑:(1) 想把 HTML &amp; 解码为 & 再把 &lt; 解码为 <,先做第二步会把 &amp;lt; 错误解码成 <(原本应该输出 &lt;)—— 解码必须按"长串先于短串"的顺序,或用一次性映射。(2) Markdown 渲染时先把 bold 改成 <strong>,再把 *italic* 改成 <em>——如果用户写了 *x*(三星号粗斜),两步走会得到错的嵌套。正确做法:(a) 一次性 callback 替换——JS str.replace(/&(amp|lt|gt|quot);/g, (m, g) => ({amp:'&', lt:'<', gt:'>', quot:'"'}[g])) 一次解析所有命中,无中间态。(b) 用占位符隔离——先把 &amp; 临时换成 \u0000 这种不会出现在文本里的字符,再做其他替换,最后把占位符换回 &。(c) 正则合并——能写成一个交替分支 (amp|lt|gt|quot) 就别拆两次。Regex Pro 当前替换面板是"单次扫描"——一次 pattern + 一次 replace,链式场景需要复制结果再粘回去做第二次。

替换里能引用"匹配位置 / 整个原串 / 没匹配到的组"这类元信息吗?

字符串模板里只能用上面那 6 个占位符;要拿位置 / 原串 / 命名组对象,必须切到回调函数模式(仅代码里能用,Regex Pro 替换面板本身只跑字符串模板)。JS 回调签名str.replace(pattern, (match, ...groups, offset, string, namedGroups) => ...)——倒数第二个是 offset(匹配在原串里的下标),最后一个是 string(整个原串),命名分组打包在最后的 namedGroups 对象里。典型用途:(1) 给每个匹配编号 —— let i = 0; str.replace(/<h\d>/g, m => \${m}[#${++i}]\);(2) 按上下文做条件替换 —— str.replace(/\d+/g, (m, off, s) => s[off-1] === '$' ? m : '['+m+']')(前一字符是 $ 时不加方括号);(3) 没匹配到的命名组在 namedGroups 里是 undefined,可以判断后给默认值。Python 等价re.sub(pattern, lambda m: m.group(0) + str(m.start()), text)m.group("name") 取命名组、m.start() / m.end() 取位置。Regex Pro 工作流:在工具里先把 pattern 和基础替换调通 → 用 ⎘ JS / ⎘ Py 导出代码 → 在代码里改成回调形式补元信息逻辑。

🔍 打开 Regex Pro 对标 regex101·pattern 语法着色·AST 中文解释·命名组捕获/回引·替换预览·大文本 grep 模式·Web Worker 超时保护·本地运行

📖 同一工具的其他教程