Git Bash 执行中文命令报错 127:MSYS 参数编码 bug 排查与修复
Git for Windows 2.20.12018年版本自带的bin/bash.exe存在 MSYS 参数编码 bug通过bash -c传递包含 UTF-8 中文字符的命令时参数内容会被破坏导致所有中文命令报错exit 127。解决方案升级 Git for Windows 到最新版2.55.0问题消失。起因在使用 AI AgentHermes / Claude Code的 terminal 工具时发现一个诡异现象任何包含中文的 shell 命令都会失败。$echohello hello# 正常$echo你好exitcode:127# 失败$ls/e/作业/exitcode:127# 失败$ls/e/作业/# 不加引号正常列出目录内容# 竟然成功了更奇怪的是同样的命令在朋友电脑上完全正常。结论先行经过排查最终定位到根因项目详情根因Git for Windows 2.20.1 的bin/bash.exe中MSYS 运行时msys-2.0.dll将 Windows 命令行参数转换为 POSIX argv 时破坏了 UTF-8 多字节字符触发条件bash -c 含中文的参数表现参数中的换行符\n被转义为字面\\n中文字符被损坏修复升级 Git for Windows2.20.1 → 2.55.0升级后验证$git--versiongitversion2.55.0.windows.2# 从 2.20.1 升级$echo你好 你好# 完美解决$ls-la/e/作业/生产实习/total880# 引号中文路径也没问题了排查过程比较长可以直接跳过Step 1控制变量 — 确认中文是触发条件第一反应是排除噪音。设计了一组对照实验命令结果说明echo helloOK纯 ASCIIecho 你好FAIL127加中文pwd ls -laOK复合命令无中文ls /e/作业/FAIL127引号包裹中文路径ls /e/作业/OK不加引号反而成功结论明确中文是必要触发条件。最后一个现象特别值得注意 —— 不加引号时中文路径能正常工作。这说明 bash 本身能处理中文问题出在某个中间环节。Step 2分析错误输出 — 发现换行符丢失echo 你好的完整输出这是 AI Agent 的 wrapper 脚本/usr/bin/bash: line7:source.../hermes-snap-xxx.sh/dev/null21||truebuiltincd-- /c/Users/31548||exit126evalecho 你好__hermes_ec0{export-p...mv-f...;}2/dev/null||rm-f...2/dev/null||truepwd-P.../hermes-cwd-xxx.txt2/dev/null||trueexit:No suchfileor directory三个异常wrapper 脚本内容泄露到了 stdout— 正常情况下不应该打印出来说明 bash 解析脚本时出了问题true和exit被合并成了trueexit— 两行之间的换行符丢失了echo 你好的输出没有出现— 命令根本没执行到这一步的关键收获问题不在用户的命令而在 bash 收到的脚本参数被破坏了。Step 3读源码 — 理解执行链路AI Agent 执行 shell 命令的链路是这样的# 1. 构造 wrapper 脚本多行字符串script\n.join([source snap.sh /dev/null 21 || true,builtin cd -- /c/Users/31548 || exit 126,eval echo 你好,__hermes_ec$?,exit $__hermes_ec])# 2. 通过 bash -c 传递subprocess.Popen([D:/develop/Git/bin/bash.exe,-c,script])脚本在 Python 层面构造完全正确\n.join()不会丢失换行。问题一定在 bash 收到参数之后。Step 4隔离测试 — 排除 Agent 本身的 bug直接用 Python 调用bash -c不经过任何 Agent wrappersubprocess.run([bash,-c,echo line1\necho 你好\necho line3])# 结果: rc0, stdoutline1\n你好\nline3 **OK** 正常bash 本身没问题。那问题在哪答案藏在细节里 —— 这里的bash走的是/usr/bin/bashMSYS 内部路径不是D:/develop/Git/bin/bash.exeWindows 原生路径。Step 5定位到具体 bash 可执行文件系统上有多个 bash路径来源D:/develop/Git/bin/bash.exeGit for Windows 自带的 launcher/usr/bin/bashMSYS 内部的 bash同一个 Git 安装内AI Agent 通过_find_bash()查找 bash 时优先使用了D:/develop/Git/bin/bash.exe。这个文件是 Git for Windows 的原生 Windows launcher它的作用是接收 Windows 命令行参数 → 调用 MSYS 运行时msys-2.0.dll转换 → 传给底层的 MSYS bash。Step 6对照实验 — 锁定 bug 位置用同一个 Python 进程分别调用两个 bashgit_bashD:/develop/Git/bin/bash.exe# Windows launchermsys_bash/usr/bin/bash# MSYS bash# Windows launcher 中文subprocess.run([git_bash,-c,echo 你好])# → rc127 **FAIL**# MSYS bash 中文通过 launcher 调用subprocess.run([git_bash,-c,/usr/bin/bash -c echo 你好])# → rc127 **FAIL**# 直接在 MSYS 环境内调用# execute_code 沙箱内subprocess.run([/usr/bin/bash,-c,echo 你好])# → rc0 **OK**bug 在Git/bin/bash.exe的参数传递层不在 bash 本身。Step 7找到铁证 — stderr 中的转义痕迹subprocess.run([git_bash,-c,echo 你好\necho world],capture_outputTrue,textTrue)stderr 输出/usr/bin/bash: line 1: $echo 你好\\necho world: command not found$...是 bash 的 ANSI-C 引用语法。注意\\n—— 这不是换行符0x0A而是两个字符反斜杠 n。这意味着msys-2.0.dll在将 Windows 命令行转换为 POSIX argv 时把 UTF-8 字节流搞乱了连带把换行符也转义成了字面量。Step 8用 stdin 绕过验证subprocess.run([git_bash],inputecho 你好\necho world,capture_outputTrue,textTrue)# → rc0, stdout你好\nworld **OK**通过 stdin 传递脚本完全绕过了-c参数传递路径 → 正常。最终确认bug 存在于Git/bin/bash.exe的 MSYS 运行时将 Windows 命令行-c参数转换为 POSIX argv 的过程中对 UTF-8 多字节字符的处理有误。为什么无引号的中文路径能成功回到最初的困惑 ——ls /e/作业/能用ls /e/作业/不行。原因是两条路径走的是不同的处理链路有引号ls /e/作业/整体作为-c参数的一部分传递 → 经过 msys-2.0.dll 参数转换 → 中文被破坏无引号ls /e/作业/中的/e/作业/被 bash 直接作为文件路径处理 → 通过文件系统 API 访问 → 中文字节原样传递给 OS不经过参数解析完整证据链用户输入 echo 你好 │ Hermes 构造 wrapper 脚本 (Python 字符串, 换行正确) │ subprocess.Popen([D:/develop/Git/bin/bash.exe, -c, script]) │ Python list2cmdline() 拼成 Windows 命令行字符串 │ CreateProcess() 启动 bash.exe │ bash.exe → msys-2.0.dll 解析命令行 → 转换为 POSIX argv │ ← 此处 UTF-8 字节被破坏\n 变成 \\n │ bash 收到损坏的 -c 参数 │ bash 执行失败 → exit 127排查方法论1. 控制变量 → 确认中文是必要触发条件 2. 分析输出 → 发现 trueexit 合并 → 换行符丢失 3. 读源码 → 理解 wrapper 机制 → bash -c 传递 4. 隔离测试 → 绕过 Agent 直接调 bash → 正常 → 问题不在 bash 本身 5. 区分执行体 → 找到 Agent 用的 Git/bin/bash.exe 6. 对照实验 → 同一进程两个 bash一好一坏 → 锁定 launcher 7. stdin 绕过 → 确认是 -c 参数传递路径的问题 8. 找铁证 → stderr 中 $...\\n 暴露了 msys 转义 bug核心思路从现象出发每一步排除一个可能性逐步缩小嫌疑范围直到只剩一个解释。当遇到同样的代码在别人电脑上能跑的情况优先怀疑环境差异版本、路径、运行时。修复验证项目升级前升级后Git for Windows2.20.1 (2018-12)2.55.0 (2025)bash4.4.23 (pc-msys)5.3.15 (pc-cygwin)测试命令升级前升级后echo helloOKOKecho 你好FAIL127OKecho 中文测试FAILOKls /e/作业/FAIL127OK升级 Git for Windows 后问题完全解决。