你只想从 Git Bash 里快速调一段 PowerShell,于是写了 powershell -Command "... 用 $_ 处理当前对象 ..."。命令发出去了,PowerShell 却报一堆「无法识别为 cmdlet」。你盯着自己写的脚本,$_ 明明是对的,PowerShell 怎么会不认识?
现象
现象通常是这样的:
- PowerShell 报
... 无法将“...”项识别为 cmdlet、函数、脚本文件或可运行程序的名称; - 或者报一个和
$_完全无关的变量名错误; - 你把同样的内容写进
.ps1文件再执行,一切正常。
关键线索是:同样的代码,内联执行失败,存成文件执行成功。 差别就在「内联」这两个字上。
根因
根因是变量展开的优先级:外层 shell 先动手,轮到 PowerShell 时脚本已经不是原来那个了。
$_ 在 PowerShell 里是「当前管道对象」,很有用。但它在 bash 里也是一个变量——bash 的 $_ 表示「上一条命令的最后一个参数」。当你在 bash 的双引号里写:
powershell -Command "... $_ ..."
bash 会先处理这段双引号。双引号里的 $ 变量会被 bash 展开——于是 $_ 被替换成了 bash 的 $_ 的值(通常是上一条命令的某个参数,一个毫不相干的字符串)。等这个字符串终于交给 PowerShell 时,里面原来的 $_ 早就没了,取而代之是别的东西。
PowerShell 拿着一个被污染过的字符串执行,自然报「无法识别」。这不是 PowerShell 的问题,是 bash 在执行你自己的代码之前,先替你做了一次替换。
关键认知:多层 shell 嵌套时,展开是从最外层往最内层逐层发生的。 你以为写的是「PowerShell 脚本」,但最外层的 bash 先把它当「bash 字符串」处理了一遍。
解决
有几种办法,按推荐程度排列。
方法一:改用脚本文件执行(最推荐)。
powershell -File script.ps1
把内容存进 .ps1 文件,用 -File 执行。文件内容不经过 bash 的字符串解析,$_ 原样进入 PowerShell,没有任何展开问题。这也顺带避开了所有多层引号的麻烦。
方法二:外层用单引号。
bash 里单引号内的内容不做变量展开,$_ 会原样保留:
powershell -Command '... $_ ...'
单引号是 bash 里「原样照传」的写法。但如果脚本内部本来就需要用到 bash 变量,单引号就不方便了。
方法三:对 $ 转义。
如果必须用双引号(比如要让 bash 先展开某些变量),就把属于 PowerShell 的 $ 转义掉,让它跳过 bash 的展开:
powershell -Command "... \$_ ..."
\$ 告诉 bash「这个 $ 不是给你的」,于是 $_ 原样传下去。
延伸与预防
一句话原则:
跨 shell 传字符串时,嵌套展开的优先级永远比你想的靠前。
你脑子里想的是「这段代码是给 PowerShell 的」,但实际执行顺序是 bash 先看一遍、PowerShell 再看一遍。每一次经过一层 shell,就是一次被改写的风险。
判断方法:数一数你的字符串要穿过几层。bash → powershell 是两层;如果 PowerShell 里又调用了别的程序、再拼一段命令,就是三层。层数越多,$、反引号、引号这些「有特殊含义的字符」被提前消费的概率越大。
这条坑的一个通用信号是:「同样内容,存文件能跑、内联不行」。 一旦出现这个信号,几乎可以断定是「外层 shell 提前展开了内层的内容」。此时不要试图在引号里跟它搏斗,直接改成存文件执行——把复杂代码从「字符串」变成「文件」,就绕开了所有展开层级。
这个思路跟前一条「命令复杂到需要数引号时就该传文件」是同一个原则:字符串是用来传参数的,不是用来传代码的。 代码一旦复杂,就让它以文件的形态存在。