工具与效率

「脚本能跑」和「脚本可靠」之间隔着一堆细节。这篇讲备份脚本里那些容易漏掉但又必须处理的部分。

从骨架开始

#!/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 pipefailtrap ERR 带行号、flock 防并发、gzip -t 加大小校验。全部加起来不到 30 行,但它决定你半夜是被叫醒还是睡得安稳。

参与讨论