用 timeout 给命令设时限,避免脚本被挂死
自动化脚本里最怕「某一行永远不返回」:远端 SSH 握手卡住、curl 连不上半开连接、find 扫到僵死的 NFS、备份工具在等文件锁。人可以 Ctrl+C,定时任务和无人值守的 Agent 不行。Linux 自带的 timeout(coreutils)就是给命令加硬截止时间的标准做法,比自己写「后台 sleep + kill」省心得多,也比把整条流水线交给外层调度硬杀进程更可控。
基本用法
timeout 30s curl -fsS https://example.com/health
echo $?
超时后 timeout 默认向子进程发 SIGTERM,再等一小段(默认约 1 秒,可用 -k / --kill-after 调整)仍不退则发 SIGKILL。退出码约定很实用,写脚本时建议显式区分:
- 124:命令因超时被终止(最常见判断条件)
- 125:
timeout自身失败(极少见) - 137:被 SIGKILL 杀掉时也可能看到(视发行版与包装脚本而定)
- 其它:子命令自己的退出码(成功或业务失败)
if ! timeout 20s ./backup.sh; then
ec=$?
if [ "$ec" -eq 124 ]; then
echo "backup timed out" >&2
else
echo "backup failed: $ec" >&2
fi
exit "$ec"
fi
把「超时」和「命令本身报错」分开,告警才不会变成「一律重试」或「一律忽略」。
常用选项
| 选项 | 作用 |
|---|---|
-k 5s / --kill-after=5s |
TERM 后仍存活,再等 5 秒发 KILL |
-s INT |
改用 SIGINT 等信号(兼容只处理 Ctrl+C 的程序) |
--foreground |
在前台进程组跑命令,适合需要接收终端信号的交互/调试场景 |
--preserve-status |
超时时尽量保留被杀进程的退出状态,而不是固定 124 |
时长写法支持 30(秒)、30s、2m、1h。给 CI 或巡检加「最长跑 2 分钟」非常直接:
timeout -k 10s 2m python3 collect_metrics.py
对只响应 SIGINT、忽略 SIGTERM 的老工具,可改:
timeout -s INT -k 5s 45s ./legacy_exporter
和管道、sudo、循环一起用时的注意点
timeout 只直接盯着它的直接子进程。管道右侧若另起进程,或命令内部又 exec / nohup / 守护化出孙进程,超时后可能留下孤儿。稳妥写法是把整段逻辑包进 bash -c,并在需要时配合 --foreground:
timeout -k 5s 60s bash -c 'find /data -type f -mtime -1 | wc -l'
需要 root 时,优先想清楚要限制哪一层。一般希望「整条特权命令」限时,用:
sudo timeout 45s apt-get update
在 for / xargs 批处理里,给每一项单独加 timeout 往往比给整批加一个总时限更合理:单项挂死不会拖死整晚的任务,也方便按退出码 124 单独记失败清单。
实践建议
- 先估正常耗时,再留余量:健康检查用十几秒,全量备份用数十分钟;余量太紧会制造假超时,太松则失去保护意义。
- 超时要可观测:日志里写清命令名、时限、退出码,方便和监控告警对齐。
- 反复 124 要查根因:网络抖动、DNS、锁文件、对端无响应,限时只是护栏,不是修复。
- 不要和无限重试叠成放大事故:超时后若自动重试,务必加次数上限与退避,避免打爆对端。
若在容器或极简镜像里发现没有 timeout,先确认是否安装了 coreutils;BusyBox 环境则可能是精简实现,选项与退出码要以当前版本 timeout --help 为准。
小结
把「可能挂起」的外部调用一律包上 timeout,是脚本从「本机能跑」升级到「无人值守可跑」的廉价改进。配合退出码 124 做告警或有限次重试,比盲目拉长 sleep、或依赖外层任务调度硬杀进程更清晰。记住:限时是保护自动化,不是替代修好根因——同一命令连续超时,优先去查网络、锁和阻塞点,而不是把时限再翻一倍了事。
- 点赞
- 收藏
- 关注作者
评论(0)