为什么要包装脚本
生产环境的定时任务不应该把一长串命令直接写进 crontab,而应写成独立的包装脚本:脚本内打开严格模式、加锁防重入、统一日志、设置超时,crontab 只保留一行调用。好处很直接:任务逻辑可以纳入版本管理、可以在本地单独调试、也能被其它入口(手工触发、监控系统)复用。模板里几个必备要素是 set -euo pipefail 让错误立即暴露、flock 防止实例叠加、timeout 防止任务僵死、日志带时间戳与退出码便于事后对账。
基本信息
| 适用平台 | Linux 与 macOS(bash 环境) |
|---|---|
| 必备要素 | set -euo pipefail、flock 单实例、日志函数、timeout 超时、退出码传递 |
| 日志要求 | 统一目录、带 ISO 时间戳与进程号、记录开始与结束 |
| 退出码约定 | 0 表示成功或主动跳过,非 0 表示失败,监控据此告警 |
| 配套措施 | logrotate 轮转日志、监控扫描非 0 退出码 |
典型使用场景
- 备份任务:需要防重入与超时保护
- 数据同步与清理:需要明确失败告警
- 报表生成:需要记录每次执行结果
- 巡检脚本:需要统一日志便于回溯
- 任何多人协作维护的周期任务
模板
模板
#!/usr/bin/env bash
set -euo pipefail
NAME=app-backup
LOG=/var/log/app/${NAME}.log
LOCK=/var/lock/${NAME}.lock
log() { echo "$(date -Is) [$$] $*" >> "$LOG"; }
exec 9>"$LOCK"
if ! flock -n 9; then log "skip: already running"; exit 0; fi
log "start"
if timeout 3600 /opt/app/backup.sh >>"$LOG" 2>&1; then
log "done rc=0"
else
rc=$?
log "fail rc=$rc"
exit $rc
fi模板要素的作用
| 要素 | 作用 | 不做会怎样 |
|---|---|---|
| set -euo pipefail | 出错即退出,管道中段失败也能捕获 | 局部失败被忽略,任务看似成功 |
| flock -n 单实例 | 防止上一次没跑完又被触发 | 两次执行同时操作同一份数据 |
| timeout 超时 | 防止任务僵死长期占用锁与资源 | 锁被长期持有,后续调度全部被跳过 |
| 带时间戳的日志 | 便于对账与排障 | 无法判断是否执行、何时失败 |
| 退出码传递 | 让监控能识别失败 | 失败被静默吞掉 |
日志要搭配 logrotate 轮转,并让监控扫描退出码不等于 0 的记录,任务才算真正可运营,而不是只靠人工翻日志。
脚本里的跳过分支也要写日志,例如「已有实例在跑,本次跳过」要留痕,否则事后无法区分是跳过还是根本没触发。