自动化脚本的价值不只是“能跑”,而是输入错误时拒绝执行、失败时不留下伪成功文件、恢复时能证明备份没有损坏。下面从一个目录备份需求开始。
一、明确输入、输出和安全边界
- 源目录必须存在,且不能是
/。 - 目标目录由脚本创建。
- 先写临时文件,成功后原子移动为最终文件。
- 生成 SHA-256 校验文件,并做一次测试恢复。
二、编写脚本骨架
#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'
usage() {
echo "Usage: $0 SOURCE_DIR BACKUP_DIR" >&2
exit 64
}
[[ $# -eq 2 ]] || usage
source_dir=$1
backup_dir=$2
[[ -d "$source_dir" ]] || { echo "Source directory not found" >&2; exit 66; }
[[ "$source_dir" != "/" ]] || { echo "Refusing to back up /" >&2; exit 64; }
mkdir -p -- "$backup_dir"
-e 遇到未处理失败时退出,-u 拒绝未定义变量,pipefail 让管道中任一命令失败都能被发现。变量展开始终加双引号,并在路径参数前使用 --。
三、用临时文件避免半成品
timestamp=$(date -u +%Y%m%dT%H%M%SZ)
archive="$backup_dir/backup-$timestamp.tar.gz"
tmp_archive=$(mktemp "$backup_dir/.backup.XXXXXX")
restore_dir=$(mktemp -d)
cleanup() {
rm -f -- "$tmp_archive"
rm -rf -- "$restore_dir"
}
trap cleanup EXIT
tar -C "$(dirname "$source_dir")" \
-czf "$tmp_archive" \
"$(basename "$source_dir")"
tar -tzf "$tmp_archive" >/dev/null
mv -- "$tmp_archive" "$archive"
临时文件和最终文件位于同一目录,mv 在同一文件系统内通常可原子完成。只有 tar 创建成功且列表校验通过,最终文件名才会出现。
四、生成校验和并执行恢复测试
sha256sum "$archive" > "$archive.sha256"
sha256sum --check "$archive.sha256"
tar -xzf "$archive" -C "$restore_dir"
test -d "$restore_dir/$(basename "$source_dir")"
printf 'Backup verified: %s\n' "$archive"
macOS 默认可用 shasum -a 256 替代 GNU sha256sum。团队应统一命令并把运行平台写入文档。
五、先做静态检查,再做破坏性测试
bash -n backup.sh
chmod +x backup.sh
mkdir -p /tmp/backup-demo/source
printf 'important\n' > /tmp/backup-demo/source/note.txt
./backup.sh /tmp/backup-demo/source /tmp/backup-demo/output
ls -lh /tmp/backup-demo/output
验收不止看退出码:解压备份,使用 diff -r 比较源目录与恢复目录。再测试不存在的源目录、带空格路径和目标目录不可写等失败场景。
六、恢复流程
sha256sum --check backup-20260101T000000Z.tar.gz.sha256
mkdir -p restore-test
tar -xzf backup-20260101T000000Z.tar.gz -C restore-test
find restore-test -maxdepth 2 -type f -print
先恢复到新目录进行检查,确认所有权、权限和内容正确后,再按业务流程切换。不要直接把归档解压覆盖生产目录。
上线前检查清单
- 脚本使用专用低权限账户。
- 备份目录不与源目录互相包含。
- 日志明确记录开始、成功、失败和归档路径。
- 磁盘空间、保留周期和异地副本有监控。
- 定期执行真实恢复演练;“备份成功”不等于“能够恢复”。