脚本执行失败时应使用until循环配合sleep实现可控重试,限制3–5次并区分真失败(如ssh、curl)与假失败(如grep),同时添加posix兼容的失败通知机制。

脚本执行失败时如何自动重试
linux 下自动化任务失败后直接退出,往往比失败本身更危险——它让问题被掩盖,直到某个关键节点崩掉。重试不是加个 while 循环就完事,得控制次数、间隔和退出条件。
常见错误现象:curl 请求超时后脚本立即退出,没重试;rsync 网络抖动失败,后续步骤全跳过;重试逻辑写在脚本开头,但实际失败发生在中间某条命令。
- 用
until比while更直观:只要命令返回非 0 就继续,成功即停 - 必须加
sleep,否则可能瞬间打爆目标服务(比如重试systemctl start会触发 systemd 的启动频率限制) - 重试上限建议设为 3–5 次,超过说明不是临时故障,而是配置或权限等根本问题
- 示例:
until timeout 10s curl -f http://api.example.com/health; do sleep 3; done——这里
timeout防卡死,-f让 curl 在 HTTP 错误码时也返回非 0
怎么判断“真失败”而不是临时性报错
很多命令失败只是表象,比如 grep 找不到内容返回 1,但这是正常流程;而 ssh 连不上返回 255,才是真异常。不区分就兜底,反而把逻辑搞反。
使用场景:监控脚本里检查日志关键词、部署脚本中验证服务端口是否监听、CI 中等待容器就绪。
-
grep、test -f、find这类“查无结果即失败”的命令,不该进重试——它们的非 0 返回是设计行为 - 真正要兜底的是网络类(
curl、wget、ssh)、系统调用类(systemctl start、docker run)和权限类(chown失败但目录存在) - 可以用
$?捕获上一条命令退出码,再结合具体数值做分支:比如ssh失败通常是 255,而cp权限不足是 1
crontab 任务挂了没人知道?加个失败通知
定时任务静默失败是最常见的兜底盲区。cron 默认只在 stderr 有输出时发邮件,而很多脚本把错误日志重定向到文件或丢弃了。
性能影响几乎为零,但能让你在凌晨三点前就知道数据库备份没跑成。
- 别依赖 cron 的邮件功能——多数生产环境没配本地 MTA,邮件根本发不出
- 用最简方式触发通知:失败时调用
curl推送企业微信/钉钉 Webhook,或写入一个/tmp/failed-jobs.log并由另一个轻量脚本轮询 - 关键点:通知逻辑必须放在整个任务链的末尾,且只在最终失败时触发。例如:
./deploy.sh || (echo "$(date) deploy failed" >> /var/log/deploy-fail.log; curl -X POST https://xxx/webhook) - 避免在通知里拼接大量日志,
tail -n 20 deploy.log足够定位,太大可能触发 webhook 限流
兜底策略本身不能成为单点故障
你给主流程加了重试、通知、回滚,但如果兜底代码自己出问题(比如依赖的 Python 脚本缺失、Webhook 地址写错),整个防御体系就形同虚设。
最容易被忽略的地方是:兜底逻辑的执行环境和主流程不一致。cron 用的是最小 shell(sh),但你的重试脚本用了 [[ 或 source,一跑就跪。
- 兜底代码优先用 POSIX 兼容语法:
if [ "$?" != "0" ]而不是[[ $? != 0 ]];用/bin/sh而不是#!/usr/bin/env bash - 所有外部命令(
curl、jq、logger)必须显式写绝对路径,或在脚本开头检查是否存在:command -v curl >/dev/null || { echo "curl missing"; exit 1; } - 不要在兜底逻辑里做复杂状态恢复(比如自动 rollback 数据库 migration),这类操作应由人工确认后手动触发——自动化兜底只负责“别让它彻底失联”,不负责“替你擦屁股”