返回 Bug 清单
BUG-0218project-12已修复powershell

.NET Regex.Replace 的替换串里 $ 不是字面量 —— $' 会插入匹配后的全部文本

发现:2026-10-01验证:2026-10-09置信度:98%

症状

一个「用正则替换配置文件中 marker 块」的安装脚本,每执行一次产物就变长一点(实测每次 +113 字符)。长期反复执行后,目标文件从 187 字节膨胀到 29,664,059 字符 / 703,086 行,语法错误 21,014 个。而脚本注释一直写着「幂等」;且膨胀产物里原文件的每一行都被复制了上万次。

根因

代码写的是 [regex]::Replace($content, $pattern, $hookBody)。在 .NET 中,Regex.Replace 的【替换字符串】不是字面量,其中 $ 有特殊含义:$1/$2 捕获组 · $& 整个匹配 · $` 匹配【前】的文本 · $' 匹配【后】的文本 · $$ 字面 $。而 $hookBody 里全是 PowerShell 变量($? / $LASTEXITCODE / $HOME / $__ec …),其中 $' 会把「匹配之后的全部文本」插入结果 ⇒ 每次替换都复制目标的剩余部分。叠加第二个缺陷(见 solution)后,膨胀进一步加速。

修复方案

1) 【首选】用 MatchEvaluator(脚本块)代替替换字符串,彻底绕开 $ 的替换语义: [regex]::Replace($content, $pattern, { param($m) $replacement }); 2) 若必须用字符串形式,先把 $ 转义为 $$:$escaped = $replacement -replace '\$', '$$'; 3) 同类坑也存在于其它语言的「替换串」API(Perl s///e 之外的 s///、Java Matcher.appendReplacement 的 $ 与 \ 语义、JS String.replace 的 $& $` $' $1 …)—— 凡「替换串」都先问一句:$ 是不是字面量; 4) 防御:替换后【立即比对产物大小与结构】,异常则放弃写入。

检测命令

grep -nE "Replace\(.*," <script> | grep -v 'MatchEvaluator\|param(\$m)'

修复命令

# PowerShell/.NET
$evaluator = [System.Text.RegularExpressions.MatchEvaluator] { param($m) $replacement }
$new = [regex]::Replace($content, $pattern, $evaluator)
# 或转义
$escaped = $replacement -replace '\$', '$$'

错误签名

替换后产物比预期大得多;反复执行同一替换脚本,产物逐次膨胀

技术信息

来源项目

project-12

标签

regex, replace-semantics, powershell, dotnet, over-replace...