Henry
发布于 2025-10-20 / 3 阅读
0
0

Bash - 增量备份文件夹

背景简介

增量备份文件夹脚本笔记。

前置信息

  1. 系统:Debian 6.1.66-1 (2023-12-09) x86_64 GNU/Linux

详细信息

脚本详情

#!/bin/bash

# ==============================================================================
#                           Rsync Incremental Backup Script
# ==============================================================================
# 功能描述:
#   使用 rsync 对指定文件夹进行增量备份,保留指定天数的备份,
#   并将详细的日志记录到带时间戳的日志文件中。
#
# 依赖:
#   - rsync
#   - standard GNU coreutils (date, mkdir, find, rm, tee)
# ==============================================================================

# 函数:记录日志
log_message() {
    local message="$1"
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $message" | tee -a "$LOG_FILE"
}

# 函数:显示使用说明并退出
usage() {
    echo "使用方法: $0 -s <源目录> -d <备份目录> -l <日志目录> -r <保留天数>"
    echo "  -s  指定需要备份的源文件夹 (绝对路径)"
    echo "  -d  指定备份文件存储的目标文件夹 (绝对路径)"
    echo "  -l  指定日志文件存储的文件夹 (绝对路径)"
    echo "  -r  指定备份保留天数 (正整数)"
    echo "  -h  显示此帮助信息"
    exit 1
}

# 初始化变量
SOURCE_DIR=""
BACKUP_DIR=""
LOG_DIR=""
RETENTION_DAYS=""

# 使用 getopts 解析参数
# ":s:d:l:r:h" 表示接受 -s, -d, -l, -r, -h 五个选项,其中 s,d,l,r 需要参数
while getopts ":s:d:l:r:h" opt; do
  case ${opt} in
    s)
      SOURCE_DIR="$OPTARG"
      ;;
    d)
      BACKUP_DIR="$OPTARG"
      ;;
    l)
      LOG_DIR="$OPTARG"
      ;;
    r)
      RETENTION_DAYS="$OPTARG"
      ;;
    h)
      usage
      ;;
    \?) # 当传入无效选项时
      echo "无效选项: -$OPTARG" >&2
      usage
      ;;
    :) # 当选项缺少参数时
      echo "选项 -$OPTARG 需要一个参数." >&2
      usage
      ;;
  esac
done

# 检查是否所有必需的参数都已提供
if [[ -z "$SOURCE_DIR" || -z "$BACKUP_DIR" || -z "$LOG_DIR" || -z "$RETENTION_DAYS" ]]; then
    echo "错误:缺少必需的参数。" >&2
    usage
fi

# 检查保留天数是否为正整数
if ! [[ "$RETENTION_DAYS" =~ ^[0-9]+$ ]]; then
    echo "错误:保留天数 (-r) 必须是一个正整数。" >&2
    exit 1
fi


# ------------------------------------------------------------------------------
#                           脚本主逻辑
# ------------------------------------------------------------------------------

# --- 1. 初始化和前置检查 ---

# 检查 rsync 是否已安装
if ! command -v rsync &> /dev/null; then
    echo "[错误] rsync 未安装。请先安装 rsync。"
    exit 1
fi

# 检查源目录是否存在且可读
if [ ! -r "$SOURCE_DIR" ]; then
    echo "[错误] 源目录 '$SOURCE_DIR' 不存在或不可读。"
    exit 1
fi

# 创建日志和备份目录 (如果不存在)
mkdir -p "$LOG_DIR" "$BACKUP_DIR"
if [ $? -ne 0 ]; then
    echo "[错误] 无法创建日志目录 '$LOG_DIR' 或备份目录 '$BACKUP_DIR'。请检查权限。"
    exit 1
fi

# 生成时间戳
TIMESTAMP=$(date +"%Y%m%d%H%M%S")

# 定义本次备份的目录和日志文件
CURRENT_BACKUP_DIR="$BACKUP_DIR/backup_$TIMESTAMP"
LOG_FILE="$LOG_DIR/backup_$TIMESTAMP.log"

# --- 2. 开始备份流程 ---

log_message "INFO" "========== 备份任务开始 =========="
log_message "INFO" "源目录: $SOURCE_DIR"
log_message "INFO" "目标备份目录: $CURRENT_BACKUP_DIR"

# 查找最新的备份目录,用于 --link-dest 参数
LATEST_BACKUP=$(ls -dt "$BACKUP_DIR"/backup_* 2>/dev/null | head -n 1)

# 构建 rsync 命令参数
RSYNC_ARGS=(-a -v --delete)
if [ -n "$LATEST_BACKUP" ] && [ -d "$LATEST_BACKUP" ]; then
    log_message "INFO" "找到上一次备份: $LATEST_BACKUP,将作为硬链接基准。"
    RSYNC_ARGS+=(--link-dest="$LATEST_BACKUP")
else
    log_message "ERROR" "未找到上一次备份,将执行首次完整备份。"
fi

# 执行 rsync 备份
# 注意: SOURCE_DIR 后面的斜杠 / 表示只复制目录内容,而不复制目录本身
log_message "INFO" "正在执行 rsync 命令..."
rsync "${RSYNC_ARGS[@]}" "$SOURCE_DIR/" "$CURRENT_BACKUP_DIR/" 2>&1 | tee -a "$LOG_FILE"
RSYNC_EXIT_CODE=$?

# 检查 rsync 是否成功
if [ $RSYNC_EXIT_CODE -eq 0 ]; then
    log_message "INFO" "Rsync 备份成功完成。"
else
    log_message "ERROR" "[错误] Rsync 备份失败,退出码: $RSYNC_EXIT_CODE。请检查日志文件 '$LOG_FILE' 获取详细信息。"
    # 可以选择在失败时删除不完整的备份目录
    # rm -rf "$CURRENT_BACKUP_DIR"
    exit $RSYNC_EXIT_CODE
fi

# --- 3. 清理旧备份 ---

log_message "INFO" "开始清理超过 $RETENTION_DAYS 天的旧备份..."

# 使用 find 命令查找并删除旧备份
# -maxdepth 1: 只在指定目录下查找,不进入子目录
# -type d: 只查找目录
# -name "backup_*": 匹配我们的备份目录命名格式
# -mtime +$RETENTION_DAYS: 查找修改时间超过 N 天的文件/目录
# -exec rm -rf {} +: 对找到的目录执行删除命令
DELETED_COUNT=$(find "$BACKUP_DIR" -maxdepth 1 -type d -name "backup_*" -mtime +$RETENTION_DAYS -print -exec rm -rf {} + | wc -l)

if [ $DELETED_COUNT -gt 0 ]; then
    log_message "INFO" "清理完成,共删除了 $DELETED_COUNT 个旧备份目录。"
else
    log_message "INFO" "没有找到需要清理的旧备份。"
fi

# --- 4. 任务结束 ---

log_message "INFO" "========== 备份任务成功结束 =========="
exit 0

以上便是本文的全部内容,感谢您的阅读,如遇到任何问题,欢迎在评论区留言讨论。



评论