「脚本能跑」和「脚本可靠」之间隔着一堆细节。这篇讲备份脚本里那些容易漏掉但又必须处理的部分。
从骨架开始
#!/usr/bin/env bash
set -euo pipefail
# set -e 任一命令失败立即退出,避免带着错误继续跑
# set -u 使用未定义变量时报错,抓出拼写错误
# set -o pipefail 管道中任一环失败即视为失败pipefail 尤其重要。默认情况下 cmd_a | cmd_b 的退出码只看 cmd_b,于是 mysqldump ... | gzip > x.gz 里 dump 失败了你却收不到任何信号。
加上失败可观测性
trap 'on_error $? $LINENO' ERR
on_error() {
local code=$1 line=$2
echo "[$(date '+%F %T')] 脚本在第 ${line} 行失败,退出码 ${code}" >&2
# 这里可以发通知
exit "$code"
}带着行号报错,排查时能省掉大量时间。
幂等与并发保护
定时任务可能因为上一次执行过久而重叠,用锁文件挡住:
LOCK=/var/lock/backup.lock
exec 9>"$LOCK"
flock -n 9 || { echo "已有实例在运行"; exit 0; }flock -n 是非阻塞模式,拿不到锁直接退出,不会堆积进程。
校验产物
gzip -t "$FILE" || { echo "备份文件损坏: $FILE" >&2; exit 1; }
SIZE=$(stat -c%s "$FILE")
[ "$SIZE" -gt 1024 ] || { echo "备份文件过小,疑似异常" >&2; exit 1; }大文件断在下载中途、磁盘写满导致的截断,都会表现为「文件存在但不可用」。一次 gzip -t 就能拦住。
完整的清理逻辑
find "$BACKUP_DIR" -name '*.tar.gz' -mtime +"$KEEP_DAYS" -print -delete加上 -print 让删除动作留下日志。回头对不上账时,这些记录就是线索。
一定要做的事:演练
脚本写完、跑通、绿了,这只是开始。真正需要确认的是:用它恢复一次。
# 恢复到临时目录,验证内容完整
mkdir -p /tmp/restore && tar -xzf backup.tar.gz -C /tmp/restore
diff -r /tmp/restore/data /opt/site/data && echo "内容一致"没做过这一步的备份,本质上只是「文件的副本」,不是「可恢复的备份」。
小结
四个关键点:set -euo pipefail、trap ERR 带行号、flock 防并发、gzip -t 加大小校验。全部加起来不到 30 行,但它决定你半夜是被叫醒还是睡得安稳。