timeout命令是系统级工具,通过发送SIGTERM(可配SIGKILL)强制终止超时进程,支持s/m/h/d单位、返回码124标识超时,适用于单命令包裹、多阶段分步控制,但不可嵌套使用,且需注意后台子进程清理问题。

timeout 命令是解决脚本任务“挂死”的最直接手段,它不依赖 Shell 内置逻辑,而是由系统级工具控制子进程生命周期。只要目标命令在指定时间内没退出,timeout 就会发信号终止它——这比在脚本里轮询 $SECONDS 或用 alarm 更可靠、更通用。
用 timeout 包裹单条命令最稳妥
绝大多数挂死场景(如卡在 curl 等待响应、ping 无休止重试、自定义脚本陷入死循环)都可通过一层 timeout 解决:
-
timeout默认发送SIGTERM,多数程序能优雅退出;若程序忽略该信号,加-k参数补发SIGKILL(例如timeout -k 5s 30s ./fetch.sh) - 超时单位支持后缀:
10s、2m、1h,避免手算秒数出错 - 注意:不要对整个脚本文件加
timeout后再在脚本里又调timeout——嵌套可能导致信号传递混乱,子进程被提前杀掉而父脚本还在跑 - 返回值有含义:
$?为124表示超时被杀,125表示timeout自身参数错误,其他值才是原命令的退出码
Shell 脚本内需要多阶段超时怎么办
当一个脚本包含多个可能卡住的步骤(比如先 rsync,再 mysql 导入),不能只靠外层 timeout ——它只能保整个脚本总时长,无法区分哪个环节拖慢了进度:
- 每个耗时操作单独包裹:
timeout 60s rsync -av /src/ /dst/、timeout 300s mysql -u root db < dump.sql - 别用
set -e依赖timeout的失败退出:某些程序超时后仍返回 0(比如部分旧版wget),建议显式检查$?是否等于124 - 如果某步骤必须“尽力执行”,可设宽松时限 + 重试逻辑,例如:
for i in {1..3}; do timeout 20s curl -f http://api/ && break || sleep 2; done
为什么不用 TMOUT 或 ulimit -t
TMOUT 是交互式 Shell 的空闲超时机制,对非交互式脚本完全无效;ulimit -t 限制的是 CPU 时间而非真实耗时,遇到 I/O 等待(如网络阻塞、磁盘慢)不会触发:
-
ulimit -t 60可能让一个卡在read上的脚本永远不退出,因为 CPU 没在运行 -
TMOUT=60在bash script.sh这种非交互模式下根本不会被读取 - 二者都无法控制子进程行为,而
timeout会递归终止整个进程组(除非加--preserve-status或--foreground)
真正容易被忽略的是信号继承问题:如果被 timeout 杀掉的命令启动了后台子进程(比如 nohup long_task &),那些子进程默认不会被清理。需要确保关键任务不依赖后台派生,或改用 timeout --kill-after=10s 60s ./script.sh 强制兜底。


















