杂记 › 技术笔记 › 替换规则的顺序与 $ 符号:为什么第二条规则吃掉了第一条的结果

替换规则的顺序与 $ 符号:为什么第二条规则吃掉了第一条的结果

一、两条规则丢进去,满屏都是 Class

给一份拖了几年的文档做术语统一,我在替换页里排了两条规则:第一条把 Type 换成 Kind,第二条把 Kind 换成 Class。预期是两个词各归各位,实际跑完输出 "Class Class"、替换次数 3——第二条规则连第一条刚造出来的 Kind 也没放过。

问题不在规则本身,而在一个没人写进交接文档的前提:规则不是各自独立作用于原文的,它们排队串行,第 2 条看到的是第 1 条的输出。勾上区分大小写也救不了这条链,依旧 3 次。

二、顺序既决定输出,也决定计数

把链条缩到最短,差额就看得清了。输入 "a1 b2",规则按 [1→2, 2→3] 跑完得 "a3 b3"、替换次数 3;同一批规则反序 [2→3, 1→2] 得 "a2 b3"、替换次数 2。两条规则各自单跑是基线:[1→2] 得 "a2 b2"、1 次,[2→3] 得 "a1 b3"、1 次,独立相加本该只有 2 次。多出来的那 1 次,是 b2 本来不含 1、被第一条规则造出一个 2、又被第二条抓走。

链条再长一位就更夸张。输入 "abc" 配 [a→b, b→c, c→d],输出是 "ddd"、替换次数 6;这三条规则单独跑只能得到 "bbc"、"acc"、"abd",各 1 次。同一批规则、同一份输入,只是让它们在页面上排了个队,计数就从 1+1+1 变成 6。

三条单跑 各 1 次 两条串跑 3 次 三条串跑 6 次 "abc" 配 [a→b, b→c, c→d] 串跑得 "ddd" 页面没有上下移按钮,改顺序只能删掉重加

图 1:同一批规则,单跑与串跑的命中差

页面上只有添加规则、清空规则和复制三个按钮,规则行不提供上移下移。要纠正顺序只能删掉重排,或者分两遍跑:把第一遍的输出复制出来,当第二遍的输入。

三、替换串里的 $,不认「勾了正则」这个前提

替换那一侧的坑更容易踩,因为它和正则那个勾选框无关。规则 [Hello→$&] 在纯字面模式下跑完,输出和输入一字不差,替换次数却照样显示 1——$& 是命中内容本身,于是这一趟什么都没改成,计数却不认账。

想给命中的词包一层标记:[Hello→<b>$&</b>] 出来就是 <b>Hello</b>,勾没勾正则都一样。

体量也会跳。默认那三行文本是 43 字符,只加一条 [World→$`|$'] 就膨胀成 77 字符,命中只有 1 处:$` 取命中之前的全部文本,$' 取之后的全部文本,替换串里塞进去的是这两段拼接。

正则档里的 $1 更薄。[(Hello)|(World)→$1] 把 11 字符的 "Hello World" 削成 6 字符——命中来自第二个分支时,$1 对应的那个组没参与匹配,替换结果成空串。要输出字面的 $1,得写 $$1。

四、「替换次数」量的不是改了多少处

默认档按处累加:同一份默认文本计 3 次。勾上「仅替换第一个匹配」,尺子换成每条规则最多 +1,同一份输入只剩 1 次。两档都没有错,但把它当「改动量」的凭证就会误判:[Hello→Hello] 这种一个字都没动的替换,默认档同样计 3 次。

「原始字符 / 结果字符」两格也不能当字节看:数的是 UTF-16 码元,不是字节也不是字数——"中文测试" 页面显示 4,UTF-8 编码是 12 字节。

五、正则写错的时候,页面一声不响

这一页交给 JS 引擎,只拼 g 和 i 两个 flag,不认 (?i)、(?m) 这类别的方言里的内联写法。1000 行、4889 字符的文本,想用正则 (?m)^ 给每行加个前缀,跑完是 0 命中、4889 字符原样,而「规则数量」那一格照样显示 1。只写 ^ 而不带 (?m) 时只算 1 处,那份输入实际只给第一行加上了前缀,4889 变 4891。

模板里的 catch 是空的,坏规则被跳过却不报错,唯一的线索是命中数比预期少。「查找」里只写一个反斜杠、又勾着正则,同样会被当坏规则丢掉,规则数量照报。想确认写法本身,先在正则测试那篇里单验一遍。

六、大文本很稳,嵌套量词会拖死整页

规模分两头看。字面替换很稳:100000 字符的输入配一条规则实测 8.8 ms;可规则一链起来,5 条 [a→b, b→c, c→d, d→e, e→f] 就把替换次数推到 500000。另一头是加规则这个过程本身,500 行规则实测 1068.7 ms——页面每加一行就全量重跑一遍。

真正会卡死的是回溯。正则 (a+)+b:20 个 a 是 52.1 ms,24 个 a 是 758.2 ms,26 个 a 到 3211.7 ms,每多 2 个字符约 4.2 倍。它同步跑在主线程上,这几秒整页不动,也不会有任何提示。

20 个 a 52.1 ms 24 个 a 758.2 ms 26 个 a 3211.7 ms 正则 (a+)+b 的三档耗时 每多 2 字符约 4.2 倍,全程零提示

图 2:嵌套量词下输入长度与耗时

七、我现在动手前会核对的五件事

现象先看什么实测依据
结果比预期多改了一遍规则行顺序,后一条的查找是否撞上前一条的输出"abc" 三条链式 6 次,单跑各 1 次
输出与输入一字不差替换串里的 $&,它等于命中内容本身字面 [Hello→$&] 计 1 次
文本突然变长或变短$` 与 $' 各抄一遍前后文,$1 可能是空串43 字符撑成 77,11 字符削成 6
命中 0 也不报错(?m)、(?i) 与裸反斜杠4889 字符原样,规则数量仍显示 1
整页几秒不动正则里的嵌套量词(a+)+b 在 26 个字符上 3211.7 ms

说到底是一句话:把规则当流水线看它的中间态,而不是当对照表。我先只放一条规则跑一遍、记下替换次数,再加第二条看数字怎么变;跑完把原始输入与最终输出各粘一份到两段文本对比里看差额落在哪几行,确认互不吃掉的那几条再分两批丢进文本批量替换的规则框。

Q:为什么第二条规则把第一条的结果又改了一遍?

因为规则是串行覆盖执行的,第 2 条作用在第 1 条的输出上。"a1 b2" 配 [1→2, 2→3] 得 "a3 b3"、3 次,反序 [2→3, 1→2] 得 "a2 b3"、2 次。避开的方法是让后一条的查找内容不出现在前一条的替换结果里,或者干脆分两遍跑。

Q:替换串里的 $ 要怎么打才算字面?

要字面的 $1 就写 $$1。$&、$`、$' 在不勾正则时同样被解释:[Hello→$&] 输出与输入一字不差却计 1 次,[World→$`|$'] 把 43 字符撑成 77 字符。

Q:页面卡住不动,是跑崩了吗?

不一定是崩,也可能是回溯。100000 字符的纯字面替换只要 8.8 ms,而 (a+)+b 在 26 个字符上要 3211.7 ms,且同步跑在主线程、零提示。先查正则里有重量词的嵌套,再谈文本规模。

📎 相关文章

文本合并后行数对不上:缺尾换行、被吃掉的 BOM 和不可靠的顺序 2026-10-26 Excel 粘贴转 JSON:日期、长数字和精度在「粘贴那一刻」就已经变了 2026-10-26 条形码解码只吐原文:EAN-13 校验位、UPC-A 的前导零与 CODE128 的模 103 2026-10-26 二维码解码实测:纠错等级救不了遮挡,每模块 2 像素才是生死线 2026-10-26 字频还是词频:同一句中文,一份 23 项榜单和一份空表 2026-10-26