前一阵写批量处理资料的脚本,用 PowerShell 做胶水层——扫目录、调 Python 抽文本、生成清单。功能不复杂,但我在这上面翻的车比过去一年都多。

原因不全是我的问题。PowerShell 有几个历史遗留行为非常反直觉,而且报错信息完全不指向真正的原因。这篇把五个坑记下来。

坑一:没有 BOM 的脚本会被读错

我写了一个带中文提示信息的脚本,保存为 UTF-8。跑起来报一堆语法错误:

意外的标记"}"。

排查了很久:括号是配对的,引号也是配对的。最后发现真正的原因是脚本文件没有 BOM,而 Windows PowerShell 5.1 会把无 BOM 的 UTF-8 文件按本地 ANSI 编码读取——中文字符串被读成了几个乱码字符,把后面的引号结构冲掉了。

规则:含非 ASCII 字符的 PowerShell 脚本,保存时必须带 UTF-8 BOM。

这个坑最恶心的地方是报错信息毫不相关。它告诉你”}”有问题,而”}”完全没问题。

坑二:用管道改文本,中文永久损坏

这个坑代价最大,因为它是不可逆的。

我当时的写法很自然:

$text = Get-Content $file -Raw
$text = $text -replace '旧内容', '新内容'
Set-Content $file -Value $text -Encoding utf8

看起来没问题。但 5.1 下这套组合会:按 ANSI 把 UTF-8 文件读进来(中文已经变成乱码)→ 替换 → 用 UTF-8 写回去。

结果是文件里的中文全部变成不可恢复的乱码。原文件如果没备份,就没了。

规则:修改内容文件一律用编辑工具,不用 shell 的文本管道。 如果确实需要脚本处理,就显式指定编码,并且用能正确处理字节的工具(我后来改用 Python,明确写 encoding='utf-8')。

坑三:原生命令的正常警告被当成致命错误

脚本开头我习惯写:

$ErrorActionPreference = 'Stop'

这个设置对 cmdlet 是有意义的,但对原生命令(git、python、curl 这些外部程序)有个副作用:它们写到 stderr 的内容会被 PowerShell 当成错误流处理,在 Stop 模式下直接终止脚本。

最典型的是 git 的换行符警告。它只是个提示,但脚本会在这里退出,退出码 1。你拿到一个失败的退出码,日志里看不出任何真实问题。

规则:调用原生命令时临时把 $ErrorActionPreference 设为 Continue,并且只根据 $LASTEXITCODE 判断成败。

坑四:参数前缀匹配把参数吃掉了

我写了一个包装函数来统一处理原生命令,图省事的写法是这样:

function Invoke-Native {
param([string]$Exe, [Parameter(ValueFromRemainingArguments)]$Rest)
& $Exe @Rest
}

调用 Invoke-Native git add -A 的时候直接报错:找不到名为 A 的参数。

原因是 PowerShell 会先把 -A 当成函数自己的参数来解析——它的参数匹配是前缀式的,函数里只要有以 A 开头的参数(比如 -Arguments),这个值就会被绑过去;没有的话就报参数不存在。两种情况都会让 git 拿不到这个参数。

规则:包装函数要求显式传数组(-CommandLine @('git','add','-A')),别用”剩余参数”来接,也别指望 PowerShell 会把 -A 当字面量往下传。

坑五:5.1 和 7 不是一个东西

Windows 自带的是 Windows PowerShell 5.1,而跨平台的 PowerShell 7(命令是 pwsh)是另一套。

两者的差异包括:默认编码(7 默认 UTF-8,5.1 不是)、部分命令的可用性、以及一些行为细节。同一个脚本在两边跑出不同结果是常事。

规则:脚本开头就声明自己需要哪个版本,并且在出错提示里写清楚。团队里如果有人双击运行(默认走 5.1),而脚本是按 7 写的,这个差异会浪费很多时间。

我现在的排查顺序

踩多了之后,形成了一套固定顺序,能省不少时间:

第一步,确认脚本文件的编码。 用十六进制工具看一眼开头有没有 BOM,有疑问就用带 BOM 的方式重写一遍,至少排除一个变量。

第二步,确认跑的是 5.1 还是 7。 在脚本里打印 $PSVersionTable.PSVersion。别看到命令行长得像就以为是同一个东西。

第三步,把出问题的原生命令单独在终端里跑一遍。 终端正常、脚本报错,那问题多半在 $ErrorActionPreference 或者参数传递上。

第四步,中断文本处理环节。 只要涉及读文件改内容,先把内容读进变量打印出来,看中文是否完好。这一步能避免”改完才发现文件已经坏了”。

顺序本身不重要,重要的是别同时改多个变量。编码、版本、参数、权限一起动,出了问题就再也说不清是哪一个引起的。

我的结论

PowerShell 做 Windows 上的胶水是合适的:调外部程序、处理文件、做编排,它都很顺手。

但不要用它修改文本内容。这一条我付过学费——编码转换、正则替换这类操作,交给 Python 或者编辑器,别在 shell 管道里做。工具各有所长,硬用会付出代价。

顺带说一句:这几个坑在我换到 PowerShell 7 之后少了很多,7 的默认编码行为正常得多。但 5.1 短期内不会消失(Windows 自带的就是它),所以这些规矩还是得记着。