Git Bash 执行中文命令报错 127:MSYS 参数编码 bug 排查与修复
Git for Windows 2.20.1(2018年版本)自带的 bin/bash.exe 存在 MSYS 参数编码 bug:通过 bash -c 传递包含 UTF-8 中文字符的命令时,参数内容会被破坏,导致所有中文命令报错 exit 127。
解决方案:升级 Git for Windows 到最新版(2.55.0),问题消失。
起因
在使用 AI Agent(Hermes / Claude Code)的 terminal 工具时,发现一个诡异现象:任何包含中文的 shell 命令都会失败。
$ echo hello
hello # 正常
$ echo 你好
exit code: 127 # 失败
$ ls '/e/作业/'
exit code: 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 Windows(2.20.1 → 2.55.0) |
升级后验证:
$ git --version
git version 2.55.0.windows.2 # 从 2.20.1 升级
$ echo 你好
你好 # 完美解决
$ ls -la '/e/作业/生产实习/'
total 880 # 引号中文路径也没问题了
排查过程(比较长,可以直接跳过)
Step 1:控制变量 — 确认中文是触发条件
第一反应是排除噪音。设计了一组对照实验:
| 命令 | 结果 | 说明 |
|---|---|---|
echo hello |
OK | 纯 ASCII |
echo 你好 |
FAIL 127 | 加中文 |
pwd && ls -la |
OK | 复合命令,无中文 |
ls '/e/作业/' |
FAIL 127 | 引号包裹中文路径 |
ls /e/作业/ |
OK | 不加引号反而成功 |
结论明确:中文是必要触发条件。
最后一个现象特别值得注意 —— 不加引号时中文路径能正常工作。这说明 bash 本身能处理中文,问题出在某个中间环节。
Step 2:分析错误输出 — 发现换行符丢失
echo 你好 的完整输出(这是 AI Agent 的 wrapper 脚本):
/usr/bin/bash: line 7: source .../hermes-snap-xxx.sh >/dev/null 2>&1 || true
builtin cd -- /c/Users/31548 || exit 126
eval 'echo 你好'
__hermes_ec=0
{ export -p > ... && mv -f ...; } 2>/dev/null || rm -f ... 2>/dev/null || true
pwd -P > .../hermes-cwd-xxx.txt 2>/dev/null || trueexit : No such file or directory
三个异常:
- wrapper 脚本内容泄露到了 stdout — 正常情况下不应该打印出来,说明 bash 解析脚本时出了问题
true和exit被合并成了trueexit— 两行之间的换行符丢失了echo 你好的输出没有出现 — 命令根本没执行到
这一步的关键收获:问题不在用户的命令,而在 bash 收到的脚本参数被破坏了。
Step 3:读源码 — 理解执行链路
AI Agent 执行 shell 命令的链路是这样的:
# 1. 构造 wrapper 脚本(多行字符串)
script = "\n".join([
"source snap.sh >/dev/null 2>&1 || 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 wrapper:
subprocess.run(['bash', '-c', 'echo line1\necho 你好\necho line3'])
# 结果: rc=0, stdout='line1\n你好\nline3' **OK** 正常!
bash 本身没问题。 那问题在哪?
答案藏在细节里 —— 这里的 bash 走的是 /usr/bin/bash(MSYS 内部路径),不是 D:/develop/Git/bin/bash.exe(Windows 原生路径)。
Step 5:定位到具体 bash 可执行文件
系统上有多个 bash:
| 路径 | 来源 |
|---|---|
D:/develop/Git/bin/bash.exe |
Git for Windows 自带的 launcher |
/usr/bin/bash |
MSYS 内部的 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 进程,分别调用两个 bash:
git_bash = "D:/develop/Git/bin/bash.exe" # Windows launcher
msys_bash = "/usr/bin/bash" # MSYS bash
# Windows launcher + 中文
subprocess.run([git_bash, '-c', 'echo 你好'])
# → rc=127 **FAIL**
# MSYS bash + 中文(通过 launcher 调用)
subprocess.run([git_bash, '-c', '/usr/bin/bash -c "echo 你好"'])
# → rc=127 **FAIL**
# 直接在 MSYS 环境内调用
# (execute_code 沙箱内)
subprocess.run(['/usr/bin/bash', '-c', 'echo 你好'])
# → rc=0 **OK**
bug 在 Git/bin/bash.exe 的参数传递层,不在 bash 本身。
Step 7:找到铁证 — stderr 中的转义痕迹
subprocess.run([git_bash, '-c', 'echo 你好\necho world'], capture_output=True, text=True)
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], input='echo 你好\necho world', capture_output=True, text=True)
# → rc=0, 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 Windows | 2.20.1 (2018-12) | 2.55.0 (2025) |
| bash | 4.4.23 (pc-msys) | 5.3.15 (pc-cygwin) |
| 测试命令 | 升级前 | 升级后 |
|---|---|---|
echo hello |
OK | OK |
echo 你好 |
FAIL 127 | OK |
echo "中文测试" |
FAIL | OK |
ls '/e/作业/' |
FAIL 127 | OK |
升级 Git for Windows 后问题完全解决。
更多推荐



所有评论(0)