Git Bash 里的 timeout 不是你以为的那个 timeout
# Git Bash 里的 timeout 不是你以为的那个 timeout
一个跑了很久的登录脚本,某天突然报出这段东西:
错误: 无效语法。默认选项不允许访问 '1' 次。
输入 "TIMEOUT /?" 以了解用法。
第一反应是脚本被谁改坏了。但脚本一个字都没动过——变的是执行它的 shell 环境。
现象
脚本里有这么一行,本意是给 adb 命令加一个 30 秒超时:
timeout 30 "$ADB" shell "content query --uri content://sms/inbox"
在 Linux 上这行完全正常。在 Git Bash(MSYS2)里执行,timeout 收到的却是 30 和后面整串参数,然后抱怨"参数语法不对"。
根因:PATH 里有两个同名命令
Windows 自带一个 timeout.exe,在 C:\Windows\System32\ 下。它的用途完全不同:
:: Windows 的 timeout:等待 N 秒,给批处理用
timeout /t 30
它只接受 /t 这种开关式参数,你把 30 adb shell "..." 一股脑丢过去,它当然报"无效语法"。
而 MSYS2 / Git Bash 默认并不附带 coreutils 的 timeout。于是 shell 顺着 PATH 找到了 Windows 那个,直接拿来用了。
一条命令就能确认:
type -a timeout
# timeout is /c/WINDOWS/system32/timeout
# timeout is /c/WINDOWS/system32/timeout
ls -l /usr/bin/timeout
# ls: cannot access '/usr/bin/timeout': No such file or directory
结论:MSYS2 里根本没有 GNU timeout,只有 Windows 那个冒牌货。
为什么这个坑特别阴
因为它不会报"命令找不到"。
如果 shell 报 timeout: command not found,你会立刻知道是缺命令。但实际上:
- 命令确实存在(Windows 那个)
- 它也确实执行了
- 它只是因为参数看不懂而失败
于是错误信息指向"我的参数写错了",而不是"我用错命令了"。排查方向从头就偏了。
更麻烦的是管道里的连锁反应:这个 timeout 包在管道的上游,它一失败,下游接收到的就是空输入,于是下游报"没有收到数据"——你会顺着下游去查,越查越远。
三种解法
解法一:干脆不要本地 timeout(推荐)
大多数场景下,本地那层 timeout 是多余的:
- 命令本身很快(
adb shell读短信就是几十毫秒),不需要超时保护 - 外层已经有超时机制(CI 的超时、
ssh -o ConnectTimeout、任务队列的时限)
直接去掉:
# 去掉本地 timeout,只保留远程那层的(远程是 Linux,timeout 是真的)
"$ADB" shell "content query --uri content://sms/inbox" \
| ssh host "timeout 200 node login.cjs"
注意上下两层的区别:本地的 timeout 是假的,远程的 timeout 是真的(远程跑在 Linux 上)。同一行命令里两层的语义不同,这点最容易搞混。
解法二:用绝对路径绕开 PATH 歧义
如果确实需要本地超时,装 coreutils 后用绝对路径调用,别依赖 PATH 顺序:
/usr/bin/timeout 30 some-command
装法(MSYS2):
pacman -S coreutils
解法三:用 bash 自带的方式实现超时
不装任何东西也能做,代价是代码丑一点:
some-command & pid=$!
( sleep 30 && kill $pid 2>/dev/null ) & watchdog=$!
wait $pid 2>/dev/null
kill $watchdog 2>/dev/null
如何避免再踩
写跨平台脚本时,对每一个"看起来是标准 Unix 命令"的东西都问一句:这个东西在我的 shell 里,真的是我以为的那个实现吗?
批量自检:
for c in timeout which nohup setsid grep sed awk curl; do
printf '%-8s -> %s\n' "$c" "$(type -a $c 2>/dev/null | head -1)"
done
我现在的习惯是:脚本第一行先做一次环境自检,把关键命令的实际路径打出来。这样一旦换机器、换 shell,日志里立刻能看出是环境变了,而不是先去怀疑业务代码。
一条更通用的教训
这次的排查成本大约 20 分钟,而这 20 分钟里有 18 分钟花在"怀疑脚本逻辑"上。真正有用的动作只有一个:
type -a timeout
当错误信息看起来"不像是我的代码能产生的",第一件事应该是查命令实现,而不是读代码。
Windows 风格的报错信息(中文提示 + 大写命令名 + /? 提示用法)出现在一个你以为在跑 Linux 命令的 shell 里,本身就是最强的线索——它在告诉你:你调到了另一个世界的程序。
- 点赞
- 收藏
- 关注作者
评论(0)