上周有位朋友在群里贴了张截图同一个仓库、同一份构建脚本在 Codex Desktop 的终端面板里跑出一屏红字他切到外面的窗口手敲一遍干干净净。他一开始怀疑是依赖装歪了重装了两遍node_modules又把缓存清了问题照旧。最后定位到的原因朴素得有点好笑Codex Desktop 默认拉起来的还是系统自带的 Windows PowerShell 5.1而他手敲的那个窗口是 PowerShell 7。一条在 5.1 里根本不是合法语法脚本从第二行起就没执行过。这类问题在桌面端代码工具上特别高频。Codex Desktop 本身不解释你敲的命令,它只是按配置 spawn 一个 shell 子进程而 Windows 上的出厂默认值十几年没变过——System32\WindowsPowerShell\v1.0\powershell.exe也就是 PowerShell 5.1。这个版本稳定、到处都有、不需要额外安装作为默认值它很合格但作为日常开发环境它缺的东西已经多到会实质影响效率、三元运算符、??、-Parallel、UTF-8 默认编码、原生命令的规范传参一个都没有。下面这份东西我想把三件事讲透Codex Desktop 里那个默认 Shell 到底是怎么被决定的5.1 和 7 之间的差异里哪些是真会咬人的、哪些只是听着好听以及从换过去到把老脚本跑通中间那些没人写进文档的坑。不管你是刚装好工具的新手还是手里压着一堆历史脚本要迁移的老手应该都能直接抄到点东西。1. 默认 Shell 这件事为什么值得单独拎出来写1.1 Codex Desktop 背后到底拉起了什么很多人下意识以为终端面板是工具自己实现的一个 shell其实完全不是。绝大多数桌面端代码工具的终端本质是进程启动器 伪终端渲染层它按配置里写的可执行文件路径 spawn 一个子进程把标准输入输出接进自己画的那个框里剩下的解释工作全交给那个进程。所以真正决定你敲的命令被谁执行的不是 Codex Desktop 的版本而是那份配置里的path和args两个字段。这就解释了一个特别常见的困惑明明在系统里装好了 PowerShell 7为什么工具里没生效。因为安装动作只做了一件事——把pwsh.exe放到磁盘某个位置再往系统 PATH 里加一条记录。而 Codex Desktop 并不会去猜你的偏好它读的是自己的默认配置。Windows 平台的默认配置指向系统自带的那个powershell.exe这一步跟工具本身先不先进没关系纯粹是出厂值的问题。站在工具开发方的角度这个选择其实挺合理任何一台 Windows 上都有 5.1路径固定不用用户额外装任何东西开箱就能用不会出现我装完了但是终端起不来的客服工单。但对于天天在里面敲命令的人来说这份保守是有代价的而且代价往往不在第一天暴露而是在某次构建脚本莫名其妙失败、某次中文输出变成一堆问号的时候才浮出水面。1.2 5.1 和 7 不是版本号连续的两个东西新手最容易误会的一点是以为 5.1 到 7 之间隔了 1.9 个大版本、属于渐进升级。实际上它们是两条独立的产品线共享语法和大部分命令名但底层运行时完全不是一个东西。5.1 建立在 .NET Framework 上2016 年随系统定型之后只做安全修补不再加新特性7 建立在新一代的 .NET 运行时上代码库经过了大规模重构跨平台和 5.1 并行安装、互不覆盖。判断自己此刻跑在哪个引擎里最快的办法不是看窗口标题而是看$PSVersionTable。里面两个字段最关键PSVersion是版本号PSEdition是产品线——Desktop就代表 5.1 那条老线Core就代表 7。养成一个习惯遇到任何我这个 PowerShell 行为很怪的问题第一件事是把这个表打出来比在群里问十句都管用。$PSVersionTable | Format-List PSVersion, PSEdition, OS, Platform另一个肉眼可见的区别是文件名。5.1 的可执行文件叫powershell.exe7 的叫pwsh.exe两个名字不冲突所以可以共存。共存是好事但也意味着任何写死了powershell.exe的脚本、快捷方式、任务计划、持续集成配置都不会因为你装了 7 而自动切过去。你得手动改或者至少在关键位置加一个显式路径。1.3 什么情况下留在 5.1 也说得过去我不是那种见人就说升就完事了的人。确实有几类场景应该谨慎硬切过去反而给自己找麻烦。第一类是脚本依赖只在 Windows PowerShell 里注册的旧模块或管理单元这类东西在新运行时下压根加载不了除非走兼容层。第二类是脚本被系统级任务计划或者某个内部管理框架调用改解释器要走变更流程收益不足以覆盖沟通成本。第三类是团队镜像统一你个人改了会和别人对不上反而制造在我机器上是好的这种经典事故。除了这几种,剩下绝大多数日常场景——跑构建、跑测试、跑格式化、看 git 状态、写一次性小脚本——那点兼容性顾虑基本不成立。我的判断标准很粗糙但很好用如果你的脚本里出现、出现中文注释、或者需要处理非 ASCII 的文件路径那就换如果只是几行Get-ChildItem之类的只读操作留哪个都行。还有一条必须说清楚不要试图卸载 5.1。它是系统组件的一部分很多管理界面和内置脚本还在用它删掉会连带出一堆你意想不到的问题。7 是并存的不是替代关系这个前提想清楚了心态会放松很多。2. 真正会咬人的差异从语法到编码2.1 语法糖不是重点但它决定了命令能不能跑先说最容易被撞上的。在 5.1 里写A B命令根本进不到执行阶段解析器直接报标记 不是此版本中的有效语句分隔符。这条报错在很多自动化日志里长得一模一样因为不少代码助手默认就会输出npm install npm run build这种写法人类看惯了 bash也不会觉得有问题结果在 5.1 里第一段就跑不动。在 7 里、||、三元? :、空合并??、??全都在短路语义和 bash 一致。这些不是花架子——省掉的是上一条失败了还继续跑这种隐性错误??省掉的是if ($null -eq $x) { $x default }这种占位代码。脚本越长省下来的行数越可观。# 7 里可以直接写 $mode $args[0] ?? dev $label $count -gt 0 ? 命中 $count 条 : 一条没命中ForEach-Object -Parallel是另一个明显拉开差距的东西。5.1 里想并发得自己开 runspace 池或者上工作流代码又长又难读调试起来还容易死锁7 里一个参数解决配合-ThrottleLimit控制并发度处理几十个目录的批量操作时体感提升非常直接。但这里有个坑必须提前说-Parallel的脚本块跑在独立的 runspace 里外层的普通变量读不到必须用$using:显式传进去而当前管道项$_是可以直接用的。$root D:\work Get-ChildItem -Path $root -Directory | ForEach-Object -Parallel { $p $_.FullName # 当前项直接可用 $tag $using:root # 外层变量必须 $using: Write-Host [$tag] $p } -ThrottleLimit 4还有几个零碎但常用的小改进Join-Path在 7 里接受多个子路径Join-Path $root a b c一步到位5.1 里只能两两嵌套写得人手酸ConvertFrom-Json -AsHashtable让你别再为 PSCustomObject 的属性访问方式抓头发Get-Error能把错误记录完整展开比自己一层层翻$_.Exception.InnerException舒服太多。2.2 编码中文乱码的真正来源编码这块是重灾区而且症状特别有欺骗性脚本在别人机器上跑得好好的到你这就变成一堆问号或方块或者干脆报语法错误报错位置还指在一个看起来毫无问题的行上。根因通常不在脚本内容而在解释器用什么编码读这个文件。5.1 读.ps1文件的规则非常老派开头有 BOM 就按 BOM 认没有 BOM 就按系统 ANSI 代码页认中文环境下就是 GBK 系。于是有人用 UTF-8 无 BOM 存了一个带中文注释或中文字符串的脚本5.1 读进来直接按 GBK 解释。轻则注释乱码重则某个字节序列恰好凑出一对引号整段语法当场崩掉。7 的默认编码是 UTF-8无 BOM 也能正确读入这一条就消灭了大量历史遗留问题。输出方向的差异同样致命。5.1 里重定向和Out-File默认写 UTF-16LE就是选项中那个Unicode文件用记事本打开正常但拿去给别的工具解析、给git diff看就是一片乱。7 默认写 UTF-8 无 BOM通用性好得多。另外-Encoding utf8这个值本身语义也变了5.1 里它等于UTF-8 带 BOM7 里等于UTF-8 无 BOM想在 7 里明确表达就用utf8BOM或utf8NoBOM。同一行命令产出不同字节的文件做校验和比对的时候差异就冒出来了。行为PowerShell 5.1PowerShell 7读无 BOM 的 UTF-8 脚本按 ANSI 猜中文易乱码或报错默认按 UTF-8正常与Out-File默认编码UTF-16LEUTF-8 无 BOM-Encoding utf8的含义UTF-8 带 BOMUTF-8 无 BOM-Encoding byte支持改为-AsByteStream$OutputEncoding默认值ASCIIUTF-8管道给原生程序传中文容易变问号正常还有一个更隐蔽的$OutputEncoding管的是把字符串管道给外部程序时用什么编码[Console]::OutputEncoding管的是怎么解释外部程序吐回来的字节。5.1 的$OutputEncoding默认是 ASCII往git这类程序里管道中文路径时会被替换成问号然后就出现文件名明明存在却报找不到的鬼故事。很多人治这个毛病靠的是每次开窗口先敲三行[Console]::OutputEncoding [System.Text.UTF8Encoding]::new($false) $OutputEncoding [System.Text.UTF8Encoding]::new($false) ch