写了个自动发布脚本,本机跑得好好的,一上 CI 就崩:传给子进程的中文标题,到对面变成了一串 ??。不是编码写错,是命令行本身在中间把字节截断了。这篇文章把根因和两条解法讲清楚,都是亲手踩过的。
现象:中文参数失踪成问号
最直观的表现是这样:
text
本机调用:子进程读到的标题 = "Windows 中文传参坑 命令行乱码变量来救场"
CI 调用:子进程读到的标题 = "Windows ??????? ??????????????"
两边代码一字不差,差异只在「谁启动的进程」。本机是 IDE 直接拉起,CI 是 cmd.exe 包了一层再拉起。问题就出在这一层包装上。
根因:命令行是 GBK 字节流,不是 UTF-8
Windows 的 CreateProcess 接收命令行参数是一段「窄字符」字节流。当你用 subprocess 或 shell 把中文塞进 argv 时,Python 默认按当前代码页(中文 Windows 是 GBK,代号 936)把 Unicode 编码成字节。中文的 UTF-8 三字节序列,被当成 GBK 多字节去解释,解释不动的位置就塌成 ?,而且这一步发生在进程启动之前,子进程拿到的已经是坏数据,再也救不回来。
下面这张图把数据流画了出来:

关键结论:命令行 argv 不可信,它是字节流不是文本流。 只要中间经过 cmd.exe/shell,Unicode 就会被代码页重新解释一次。
解法一:走环境变量,绕开 argv
Windows 的进程环境块(environment block)是 UTF-16 编码的,父进程 SetEnvironmentVariable 写进去什么,子进程 getenv 读出来就是什么,不经过代码页转换。所以把中文参数从「命令行参数」挪到「环境变量」,乱码当场消失。
powershell
# 父进程(PowerShell)写环境变量,中文走 UTF-16 环境块
$env:J_TITLE = "Windows 中文传参坑 命令行乱码变量来救场"
$env:J_CATEGORY = "开发工具"
Remove-Item Env:ELECTRON_RUN_AS_NODE -ErrorAction SilentlyContinue
& "C:\path\mmcli_env.cmd"
bat
@echo off
set "ELECTRON_RUN_AS_NODE="
set "EXE=C:\path\matrixmedia.exe"
REM 子进程从环境变量读中文,GBK 命令行全程不碰中文
"%EXE%" cli publish-article -p juejin --phone "%J_PHONE%" -t "%J_TITLE%" --category "%J_CATEGORY%" --file "%J_FILE%"
注意 mmcli_env.cmd 里 set "J_TITLE=..." 这行是 .cmd 内部赋值,不经过命令行 argv 传给别人,只在脚本作用域内生效;真正跨进程的是 cmd.exe 启动时由父进程注入的环境变量,那份是 UTF-16,安全。
解法二:标题里的冒号更致命
同一次排障还撞到一个更隐蔽的坑:标题里若带冒号(全角 : 或半角 :),发布程序会在打印任何启动日志之前直接崩溃,退出码 -1、输出全空。排查用的是「隔离变量法」------ASCII 参数正常、中文分类正常、只有加上冒号才崩,所以锁定是冒号触发了发布 EXE 启动期的某个解析。
text
标题含冒号 → EXE 启动前崩溃(exit -1,MMCLI_OUT 空)
标题改空格 → 49 秒,退出码 0,正常发布
经验法则:标题用「空格 / 顿号」代替冒号,既躲开崩溃,又不影响检索和分类。
记一次真实踩坑
上面两支解法不是纸上谈兵。自动化发布流水线在 Windows 沙箱里跑时,bash shim 一度坏了(dirname: command not found、退出码 127),中文经 cmd.exe 的 argv 被 GBK 乱码成 ??;切到 PowerShell + 环境变量传参 之后,标题、分类、文件路径全是中文也零乱码。同一批调试里还顺手定位了「标题冒号使 EXE 崩溃」,两件事一起解决,发布成功率从时灵时不灵变成稳定。
小结
命令行传中文,记住两条:
- argv 是字节流不是文本流 ,经过
cmd.exe/shell 会被代码页重新解释,中文塌成?;改用环境变量(UTF-16 环境块)绕开。 - 标题别用冒号,全角半角都会让某些发布 EXE 在启动期崩溃;用空格或顿号替代。
这两条都不需要改业务代码,只是在「怎么把字符串交给子进程」这一点上换了个通道,收益却是发布链路从偶发失败变稳定。配图里画的那个数据流,下次再遇到 ?? 直接对照即可。