Windows / MSYS bash 环境修复
症状识别
出现以下任一现象,基本可判定为本机 bash 的 PATH 缺失,而非脚本本身有问题:
| 现象 | 真实原因 |
|---|---|
shell-runtime-bash-env.sh: line 3: dirname: command not found |
PATH 缺 PortableGit/.../usr/bin |
line 3: cd: null directory |
同上,dirname 失败导致路径解析为空 |
spawn sh ENOENT(Node 脚本自动安装子工具时报) |
spawn 出的 sh 找不到 usr/bin |
| CLI 自动安装失败,但手动 curl 能通 | 同上 |
sha256sum 比对"失败"但哈希看起来一模一样 |
MSYS 给哈希输出加了前缀 \ |
根因
WorkBuddy 通过 BASH_ENV 注入
D:\workbuddy\resources\app.asar.unpacked\cli\vendor\shim\shell-runtime-bash-env.sh,
该脚本第 3 行用 dirname 解析自身目录。若启动时的 PATH 不含
PortableGit/<ver>/usr/bin,这一步失败 → 后续依赖 sh 的子进程全部 ENOENT。
注意:这个 shim 的报错通常只是"噪音",bash 本身仍能跑;真正致命的是它导致
第三方工具的 spawn sh 失败。
修复:显式补 PATH
拿到 PortableGit 实际版本目录(本机为 1.2.0,用 Glob 确认):
C:\Users\<user>\.workbuddy\binaries\PortableGit\versions\<ver>
每次调用 bash 前先补:
export PATH="/c/Users/<user>/.workbuddy/binaries/PortableGit/versions/<ver>/usr/bin:/c/Users/<user>/.workbuddy/binaries/PortableGit/versions/<ver>/mingw64/bin:$PATH"
补完先自检,再跑真正的活:
which dirname curl sha256sum bash uname && uname -s && uname -m
# 期望:能全部解析;uname -s 形如 MINGW64_NT-10.0
三条硬约束
- PowerShell 工具禁止起 bash 子进程(安全策略拦截:
Spawning a non-PowerShell shell ... bypasses command validation)。
凡是要跑 bash 的活,一律用 Bash 工具,不要试图从 PowerShell 里& bash.exe -c ...。 - MSYS 会改写看起来像 Unix 路径的参数。当前盘符是 D: 时,参数里的
/c/Users/...
会被改写成D:\c\Users\...。
- 传给 Node/程序的参数用C:/Users/...风格;
- 只给 bash 自己用的命令位置才用/c/Users/...。 npm install --prefix不可靠(会去当前目录找 package.json)。
先cd <项目目录>再跑,且 npm-cli 路径用C:/...风格。
curl | bash 类安装脚本的正确跑法
不要直接 curl ... | bash(路径、校验都可能被 MSYS 干扰,且无法审计)。改为:
# 1) 先落盘
curl.exe -sSL -o "$TEMP/install.sh" "https://vendor/install"
# 2) 审一遍(用 Read 工具看内容,确认真实行为)
# 3) 补 PATH 后用文件跑,而不是管道
export PATH="<补好的 PATH>"
export VENDOR_BIN="$HOME/.local/bin"
bash "$TEMP/install.sh"
如果脚本内部 SHA256 校验假失败(期望值与实际值只差一个前缀 \):
不要加 SKIP_VERIFY。改用 Node 自己做真校验,从 manifest 取 url+sha256 → 下载 →
node:crypto 计算比对 → 通过才落盘。参考实现见
alipay-aipay-projects/pay-demo 同类脚本,或直接写:
import crypto from 'node:crypto';
const buf = Buffer.from(await (await fetch(art.url)).arrayBuffer());
const actual = crypto.createHash('sha256').update(buf).digest('hex');
if (actual !== art.sha256) process.exit(5); // 不通过就拒绝安装
Windows 上跑 curl | bash 第三方安装器的四个必踩坑(2026-09-23 补)
以 SkillHub 安装器(https://skillhub.cn/install/skillhub.md → install.sh)为实证,
Windows + PortableGit 下这类脚本必然依次撞到以下四堵墙,按序处理:
坑 1 · mktemp 路径含 C: → tar 当成远程主机
TMP / TEMP / TMPDIR 是 Windows 风格(C:\Users\...\Temp),
mktemp -d -t xxx.XXXXXX 会输出 C:\Users\WIN10-~1\AppData\Local\Temp/xxx.aBcD,
该字符串传给 tar -xzf 后 tar 解析成 host:path,报:
tar (child): Cannot connect to C: resolve failed
tar: Child returned status 128
修法:把三个变量都指向 MSYS 风格可写路径,mktemp 才会输出 /c/...:
mkdir -p /c/Users/<user>/AppData/Local/Temp/shk
export TMPDIR=/c/Users/<user>/AppData/Local/Temp/shk \
TMP=/c/Users/<user>/AppData/Local/Temp/shk \
TEMP=/c/Users/<user>/AppData/Local/Temp/shk
> ⚠️ 别图省事写 /tmp —— 本机 /tmp 存在但不可写,脚本会转成报
> curl: (23) client returned ERROR on write of N bytes,更容易误判成网络问题。
> 先落盘探针验证:echo probe > "$TMPDIR/probe.txt" && echo writable OK。
坑 2 · MSYS 版 curl 写盘失败(error 23)
curl 解析到 /c/WINDOWS/system32/curl(Windows 原生 curl)混用 shell 语义时,
-o <MSYS路径> 会失败。修法:改用 curl.exe 落盘,再本地跑:
curl.exe -fsSL -o kit.tar.gz "<url>"
tar -xzf kit.tar.gz -C extract
bash extract/cli/install.sh # 包内 install.sh 会检测到本地 kit,跳过联网下载
> 关键洞察:绝大多数安装脚本都有「本地 kit 探测」分支(如 SkillHub 的
> detect_local_kit_dir)。手动落盘解压 = 绕过所有下载/校验坑,
> 比在管道里跟 MSYS 搏斗省事得多。
坑 3 · wrapper/generated 脚本把 $HOME 传给 Windows Python → d:\c\Users\...
安装器生成的 wrapper 常写成 BASE="${HOME}/.skillhub" 后 exec python3 "${CLI}"。
Windows 版 Python 不做 MSYS 路径转换,/c/Users/... 被原样当相对路径,
在 D: 盘下拼成 d:\c\Users\...:
can't open file 'd:\\c\\Users\\xxx\\.skillhub\\skills_store_cli.py': [Errno 2]
修法:wrapper 里传给 python 的路径改成 C:/... 正斜杠风格(Windows Python 认识,
MSYS 也不会改写)。命令位置(command -v、[[ -x ]])才用 /c/...:
BASE="C:/Users/<user>/.skillhub" # 传给 python:C:/ 风格
CLI="${BASE}/skills_store_cli.py"
[[ -f "/c/Users/<user>/.skillhub/skills_store_cli.py" ]] || exit 1 # 自检:/c/ 风格
exec python3 "${CLI}" "$@"
> MSYS_NO_PATHCONV=1 / MSYS2_ARG_CONV_EXCL='*' 在本环境下对 exec 参数无效,
> 不要指望它,直接改路径风格最可靠。
坑 4 · 无 python3 / python 命令
WorkBuddy 托管 Python 只提供 python.exe,且不在 PATH。
修法:在 ~/.local/bin/ 建软链,一劳永逸:
mkdir -p "$HOME/.local/bin"
ln -sf "C:/Users/<user>/.workbuddy/binaries/python/versions/<ver>/python.exe" "$HOME/.local/bin/python3"
ln -sf "C:/Users/<user>/.workbuddy/binaries/python/versions/<ver>/python.exe" "$HOME/.local/bin/python"
(若 ln -sf 被拦,直接在 wrapper 内做 command -v python3 || PY=<绝对路径> 回退即可。)
通用调用模板
export PATH="/c/Users/<user>/.workbuddy/binaries/PortableGit/versions/<ver>/usr/bin:/c/Users/<user>/.workbuddy/binaries/PortableGit/versions/<ver>/mingw64/bin:$PATH"
export PATH="/c/Users/<user>/.local/bin:/c/Users/<user>/.workbuddy/binaries/python/versions/<ver>:$PATH"
export TMPDIR=/c/Users/<user>/AppData/Local/Temp/shk TMP=$TMPDIR TEMP=$TMPDIR
坑 5 · heredoc 里的反斜杠被改成正斜杠(2026-09-24 补,静默且致命)
症状:往 bash heredoc 里写 Python/正则代码,脚本能跑但行为诡异 ——
例如 re.search(r'^name:\s*(\S+)', text) 永远返回 None,
但同一文件里用 r'^---\r?\n(.*?)' 又正常工作。
根因:MSYS 的路径转换把内容里的 \s、\S、\d、\w 当成 Windows 路径
改写成 /s、/S、/d、/w。用 cat -A 一看原形毕露:
# 写进去的是 re.search(r'^name:\s*(\S+)', fm, re.M)
# 实际落盘的是 re.search(r'^name:/s*(/S+)', fm, re.M)
^、\r、\n 这类恰好能凑成合法正则的转义可能侥幸存活,
所以症状表现为「一半能跑一半不能」,极难联想。
修法(按优先级):
- 不要用 heredoc 写含反斜杠的代码 —— 改用文件写入工具(Write / Edit)落盘。
这是最可靠的做法,<<'EOF'引号拦不住这个改写。 - 必须用 heredoc 时,用
printf '%s\n' ...逐行拼,或把反斜杠写成\\s
(但可读性差、易错,不推荐)。 - 怀疑时先自证:
cat -A <脚本>或
grep -n '/[sSdDwW][*+]' <脚本>,命中即被破坏。
排查口诀:脚本「能跑但结果不对」+ 含正则/转义 + 是 heredoc 写的
→ 先看反斜杠有没有被换成正斜杠。
验证清单
- [ ]
which dirname curl sha256sum全部能解析 - [ ]
uname -s/uname -m能正确识别平台 - [ ]
echo probe > "$TMPDIR/probe.txt"成功(临时目录可写) - [ ]
mktemp -d -t x.XXXXXX输出以/c/开头(不是C:\) - [ ] 目标 CLI 落到目标目录,且版本命令能跑(比安装器退出码更可信)
- [ ] 安装器报 FAILED 时,用"CLI 实际能不能跑"来判定,而不是只看安装器输出