rsync 同步:参数怎么选才不出事

rsync 是服务器上最常用的同步工具,也是最容易因为一个参数误删数据的工具。搞清楚几个关键点再用。

一、-a 包含什么

bash
rsync -a src/ dst/
# -a = -rlptgoD
#   -r 递归目录
#   -l 保留软链接
#   -p 保留权限
#   -t 保留修改时间
#   -g 保留属组
#   -o 保留属主
#   -D 保留设备文件、特殊文件

常用组合:

组合用途
-av归档 + 显示过程(最常用)
-avz加压缩,跨网络传输用
-avP-P = --partial --progress,大文件断点续传 + 进度
-avn-n = --dry-run只预演不执行

异地备份我固定用 rsync -avzP,本地同步用 rsync -av

二、结尾斜杠:最容易搞错的地方

这一个字符的差别,决定了目录结构完全不同:

bash
rsync -av /data/www  /backup/     # 结果:/backup/www/...(连目录一起)
rsync -av /data/www/ /backup/     # 结果:/backup/...(只同步内容)
源路径效果
src (无斜杠)把 src 目录本身复制过去
src/(有斜杠)把 src 里面的内容复制过去

目标路径的斜杠影响不大,但建议统一加上。

镜像一个目录的标准写法:

bash
rsync -av --delete /var/www/baize/ /backup/baize/

三、--delete:强大且危险

--delete 会删除目标端「源端没有的文件」,让两边完全一致。

危险场景:

bash
# 源目录拼错或被清空 → 目标被清空
rsync -av --delete /var/www/typo/ /backup/baize/   # 灾难

安全措施:

bash
# 1. 永远先 --dry-run 看会删什么
rsync -av --delete --dry-run src/ dst/

# 2. 加备份目录,被删的文件先移过去
rsync -av --delete --backup --backup-dir=/backup/deleted-$(date +%F) src/ dst/

# 3. 限制删除量,超量就中止(很实用)
rsync -av --delete --max-delete=100 src/ dst/

我的规则:--delete 的命令,必须先跑一遍 --dry-run,看输出里 deleting 的行数是否合理。

四、排除文件

bash
rsync -av --exclude "*.log" --exclude "node_modules/" src/ dst/

# 从文件读排除列表
rsync -av --exclude-from=/etc/rsync-exclude.txt src/ dst/
bash
# /etc/rsync-exclude.txt
*.log
*.tmp
node_modules/
.git/
cache/
__pycache__/
.DS_Store

--exclude 的路径是相对于传输根的,写绝对路径不生效。这是常见困惑点。

五、远程同步

bash
# 推到本地 → 远程
rsync -avzP /data/ user@host:/backup/

# 拉:远程 → 本地
rsync -avzP user@host:/var/www/ /local/copy/

# 指定 SSH 端口和密钥
rsync -avzP -e "ssh -p 2222 -i ~/.ssh/id_ed25519" /data/ user@host:/backup/

# 限速(避免打满带宽,单位 KB/s)
rsync -avzP --bwlimit=2000 /data/ user@host:/backup/

六、增量备份:--link-dest

想要「每次备份看起来都是完整副本,但只占增量空间」,用硬链接:

bash
#!/usr/bin/env bash
set -euo pipefail
SRC="/var/www/baize/"
BASE="/backup/baize"
TODAY="$BASE/$(date +%F)"
LAST="$BASE/last"

mkdir -p "$BASE"

if [ -e "$LAST" ]; then
  # 与上次相同的文件做成硬链接,不占额外空间
  rsync -av --delete --link-dest="$LAST" "$SRC" "$TODAY"
else
  rsync -av "$SRC" "$TODAY"
fi

rm -f "$LAST"
ln -s "$TODAY" "$LAST"

# 清理 30 天前的
find "$BASE" -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +

效果:每天一个完整目录,实际磁盘只增加了变化的部分。

bash
# 看起来占了很多,其实共享文件
du -sh /backup/baize/*        # 每个都是几十 G
du -sh /backup/baize          # 总量只略大于一份

七、校验而不是信任

bash
# -c 按校验和比较(默认按大小和时间,慢但准)
rsync -avc src/ dst/

# 只看差异不传输
rsync -avn --itemize-changes src/ dst/

# 输出里的符号含义
# >f+++++++++  新增文件
# >f..t......  时间戳变了
# >f.s......  大小变了
# .d          目录
# *deleting   要删除

八、常见问题

现象原因 / 解法
权限被拒绝目标目录属主不对,或用 sudo
传输很慢-z(小文件多时反而不快)、检查带宽
中文文件名乱码--iconv=utf8,utf8(老系统)
每次都全量传时间/权限没保留,缺 -a 中的 -t
中断后重来-P 支持续传
bash
# 保留 ACL 和扩展属性(需要时)
rsync -avAX src/ dst/

九、一份日常同步脚本

bash
#!/usr/bin/env bash
# sync-web.sh
set -euo pipefail

SRC="/var/www/baize/"
DEST="backup@10.0.0.2:/backup/baize/"
LOG="/var/log/rsync-baize.log"

echo "[$(date '+%F %T')] 开始同步" >> "$LOG"

rsync -avzP --delete \
  --exclude-from=/etc/rsync-exclude.txt \
  --bwlimit=5000 \
  -e "ssh -p 2222 -o StrictHostKeyChecking=accept-new" \
  "$SRC" "$DEST" >> "$LOG" 2>&1

if [ $? -eq 0 ]; then
  echo "[$(date '+%F %T')] 同步完成" >> "$LOG"
else
  echo "[$(date '+%F %T')] 同步失败!" >> "$LOG"
  exit 1
fi

rsync 记住三句:--dry-run、源路径带斜杠表示内容、--delete 要配 --backup-dir