解决
文章借助了AI,有些啰嗦,现在懒得改,留着自己看吧。
1. 诊断:先确认环境状态(30 秒)
bash
pwsh -NoProfile -Command '
$oem = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePage").OEMCP
$cp = [Console]::OutputEncoding.CodePage
$verdict = if ($cp -eq 65001) { "环境正常,管道中文不会乱" } else { "GBK 环境,管道中文会乱 → 执行第二步" }
"系统代码页 OEMCP = $oem"
"pwsh 管道输出编码 = $cp"
"判定: $verdict"
'
输出示例(需要修复的环境):
ini
系统代码页 OEMCP = 936
pwsh 管道输出编码 = 936
判定: GBK 环境,管道中文会乱 → 执行第二步
如果判定是 GBK,直接看下一步;如果是 65001,你的环境没有这类问题,可以直接跳到原理部分了解来龙去脉。
2. 修复:把系统代码页切到 UTF-8
bash
# ① 备份(回滚靠它,建议先做)
reg export "HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage" "$HOME\code-page-backup.reg" /y
# ② 切换:ACP=ANSI 代码页,OEMCP=控制台代码页,MACCP=Mac 代码页 → 全部 65001(UTF-8)
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage" /v ACP /t REG_SZ /d 65001 /f
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage" /v OEMCP /t REG_SZ /d 65001 /f
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage" /v MACCP /t REG_SZ /d 65001 /f
不想敲命令也可以:控制面板 → 区域 → 管理 → 更改系统区域设置 → 勾选 "Beta: 使用 Unicode UTF-8 提供全球语言支持"------本质是同一件事。
3. 验证与安全须知
bash
pwsh -NoProfile -Command '[Console]::OutputEncoding.CodePage' # → 65001
pwsh -NoProfile -Command "Write-Output '中文测试'" # → 中文测试(不再乱码)
动手前先了解这几件事:
| 项 | 说明 |
|---|---|
| 改了什么 | 系统 3 个代码页注册表键:ACP / OEMCP / MACCP → 65001 |
| 怎么回滚 | reg import "$HOME\code-page-backup.reg"(或删掉这几个键) |
| 需要权限 | 管理员(reg add HKLM 要求提权) |
| 可能的影响 | 极少数硬编码 GBK 的老软件可能显示乱码(现代开发工具不受影响) |
这三个键分别管什么(都是历史遗留,各管一个"编码世界"):
| 键 | 管什么 | 谁受影响 |
|---|---|---|
| ACP | Win32 ANSI 字符串(非 Unicode 程序的窄字符串 API) | 硬编码 GBK 的老 GUI 软件------这就是"老软件可能乱码"的风险源;现代工具走 Unicode API,不受影响 |
| OEMCP | 控制台/管道输出(新控制台默认代码页、无控制台进程的输出回退编码) | cmd、pwsh 管道输出------本次乱码的根源就在它 |
| MACCP | 古 Macintosh 兼容 | 基本已废弃,勾选框会一起改,顺手为之 |
改完不用重启------新进程立即生效,原因在第二部分。
第二部分 · 原理与延伸
1. 乱码先看字节
乱码的本质是编码错位:发送端和接收端各说各话。与其猜,不如直接看发送端发的是什么字节:
bash
$ echo '中文测试' | od -A x -t x1z
e4 b8 ad e6 96 87 e6 b5 8b e8 af 95 # UTF-8(e4 b8 ad = "中")
$ pwsh -NoProfile -Command "Write-Output '中文测试'" | od -A x -t x1z
d6 d0 ce c4 b2 e2 ca d4 # GBK(d6 d0 = "中")
同为"中文测试"四个字,UTF-8 和 GBK 的字节完全不同。看到 d6 d0 开头,基本可以断定是 GBK 环境的数据,被按 UTF-8 解读了。
2. 乱码的四种类型
"乱码"是个统称,实际按字节在哪个环节错位,大致分四类:
| 类型 | 典型症状 | 字节证据 | 根因 | 修法 |
|---|---|---|---|---|
| 文件读写 | 脚本读旧文件中文乱 | 文件字节是 GBK(或 BOM 不匹配) | 老文件是 GBK,现代工具默认按 UTF-8 读 | 新文件一律用 UTF-8;读旧 GBK 文件时显式 -Encoding GBK,后续转存为 UTF-8 |
| 管道传输 | 程序输出到终端/CI 乱 | d6 d0 vs e4 b8 ad(本文主线) |
输出用系统代码页 936,接收端按 UTF-8 | 系统代码页改 65001 |
| 终端显示 | 终端里直接显示乱 | --- | 终端 code page ≠ 输出字节 | 终端编码设成 UTF-8(chcp 65001 或终端设置) |
| 参数传递 | 中文参数变问号/被吞 | --- | 旧版 $OutputEncoding 默认 ASCII |
用 pwsh 7(默认 UTF-8) |
方向只有一个:能 UTF-8 就 UTF-8。它是国际标准、现代工具链的默认值;GBK 是历史遗留,只该出现在"读旧文件"这一个场景里。
早期用 Windows PowerShell 5.1 写脚本,UTF-8 编码总是出问题------写出来要么带 BOM 要么被 ANSI 默认值坑,读写双方对不上,怎么调都别扭。最后干脆用 GBK 写脚本,眼不见心不烦。后面试用 Zed 编辑器,它只认 UTF-8,GBK 文件直接没法看------那一刻意识到,UTF-8 是大势所趋,逃避不是办法。于是认真找"UTF-8 下不乱码"的写法,最终落到 pwsh:文件默认 UTF-8、参数传递也默认 UTF-8,5.1 时代最磨人的两类乱码当场消失。
为什么 5.1 执行 UTF-8 脚本会乱?三个机制叠加:
- 解析不认"无 BOM 的 UTF-8" 。5.1 读 .ps1 文件时,文件头有 BOM 就按 BOM 的编码解码,没有 BOM 就按系统 ANSI 代码页(中文系统 = GBK)解码 。而现代编辑器默认存 UTF-8 无 BOM------文件里是正确的 UTF-8,5.1 却按 GBK 读,中文从解析那一刻就错位了。(对调验证:pwsh 7 无 BOM 按 UTF-8 读,把 GBK 文件喂给它同样乱------方向反了,机制逻辑一样。)
- 自己的输出默认值不统一 。5.1 里
>/ Out-File 默认 UTF-16LE(带 BOM),Set-Content 默认 ANSI(GBK)。用 5.1 写"UTF-8 脚本",写出来的往往根本不是 UTF-8,换个工具打开又是一轮乱。 - $OutputEncoding 默认 ASCII。脚本里把中文传给 git、curl 等原生命令时,中文参数变成问号(微软官方差异文档确认:5.1 默认 ASCII → 7 改为 UTF-8 NoBOM)。
pwsh 7 修了这三条:无 BOM 按 UTF-8 读、文件默认 UTF-8 无 BOM、$OutputEncoding 默认 UTF-8。但它没修第四条------管道/控制台输出仍跟随系统代码页,这正是下一节"换了 pwsh 7 还乱"的原因。
这套分类的价值在于:先抓字节确认编码,再查"发送端用什么编码输出、接收端按什么编码解读",错位在哪一环,就修哪一环------不用背方案,每次都能自己推。
3. 为什么换了 pwsh 7 还会乱
"我从 powershell 5.1 换到 pwsh 7 了,怎么管道乱码还在?"------因为 pwsh 7 的 UTF-8 默认值只覆盖部分场景:
| 输出对象 | 编码规则 | pwsh 7 表现 |
|---|---|---|
文件 (Out-File / Set-Content / >) |
文件默认编码 | UTF-8 无 BOM ✅ |
| 管道/控制台(stdout 被重定向) | [Console]::OutputEncoding = 系统代码页 |
936,GBK ❌ |
powershell 5.1 和 pwsh 7 在"管道输出"上完全一样 ,都跟随系统代码页。换版本能治好文件乱码,但管道乱码和版本无关------它由系统代码页 决定。所以诊断脚本查的是 [Console]::OutputEncoding.CodePage,不是 PowerShell 的版本号。