别再盲目执行 rsync!我用 Dry Run 揪出差异文件,再安全完成增量同步
先说结论
rsync最值得养成的习惯,不是背更多参数,而是把同步拆成两步:先用--dry-run生成差异计划,确认无误后再执行正式同步。如果还会删除目标端文件,则必须把删除能力单独授权,不能让--delete悄悄成为默认动作。文首那段
rsync -n -avz --delete --out-format="%n"确实能看到一部分将变化的路径,但它只输出文件名,后续再靠grep猜“哪些是文件、哪些是删除”并不稳。更可靠的做法是使用--itemize-changes,并把%i与%n一起输出,让每一行自己说明是新增、更新、删除还是目录属性变化。

为什么“先比较再同步”比想象中重要
很多同步任务最初都很简单:把本地目录传到服务器、把照片复制到 NAS、把构建产物发布到另一台机器。第一次执行时,目录里只有几十个文件,直接敲一条 rsync -av 看起来没有风险。
但目录会慢慢长大,脚本也会慢慢变成定时任务。几个月后,源目录里可能有几万个文件,目标端还可能保存了一些只存在于目标端的历史资料。这时再随手加上 --delete,含义就不再是“帮我同步”,而是“让目标端严格变成源端的镜像,多出来的东西全部删除”。
这像搬家:正常搬家是把旧房子的箱子搬到新房子;--delete 则相当于搬家公司发现新房子里有旧房子清单之外的东西,就顺手扔掉。它不是错误参数,但必须在你清楚后果时使用。

从一段常见脚本说起
原始思路大致如下:
#!/bin/bash
DIFF_FILE="/tmp/rsync_diff.txt"
echo "正在比较本地和远程目录..."
rsync -n -avz --delete --out-format="%n" $LOCAL_DIR $REMOTE_DIR > $DIFF_FILE
grep -v '/$' $DIFF_FILE | grep -v 'deleting' > "${DIFF_FILE}_filtered"
它的方向没有错:-n 代表 dry-run,只预演、不修改;输出先写入临时文件,再过滤目录与删除记录。但在真实自动化中,它有几个容易被忽略的问题。
变量没有加引号
$LOCAL_DIR 和 $REMOTE_DIR 如果包含空格、通配符或某些特殊字符,Shell 会重新拆分它们。更稳妥的写法是:
rsync ... "$LOCAL_DIR" "$REMOTE_DIR"
只输出 %n,丢失了变化类型
%n 只告诉你路径名,不告诉你为什么传输。后续脚本只能继续通过字符串过滤猜测。新增文件、内容变化、时间变化和权限变化都可能只剩下一个名字。
grep -v 'deleting' 会隐藏最危险的信息
如果命令启用了 --delete,删除项恰恰应该重点审查。把它们过滤掉,就像审核付款清单时把所有负数金额隐藏了:画面更干净,但风险更大。
源目录末尾的 / 会改变语义
下面两条命令看起来只差一个斜杠:
rsync -av source destination/
rsync -av source/ destination/
前者通常把 source 目录本身放进目标目录;后者同步的是 source 里面的内容。这个区别很小,却经常造成目标端多套一层目录。
本地复制通常不需要 -z
-z 会压缩传输数据,跨较慢网络时可能节省带宽;本机磁盘、局域网高速链路或已经压缩过的图片、视频、压缩包未必受益,反而会消耗 CPU。它不是必须删除的坏参数,而是应该按链路和数据类型决定。
我做了一个真实、隔离的实验
为了避免拿真实文件冒险,我在临时目录中创建了源端和目标端,并故意制造以下差异:
added.txt:只在源端存在,应该新增。modified.txt:两端都有,但版本和时间不同,应该更新。deleted.txt:只在目标端存在,启用--delete后应该删除。old-name.txt与new-name.txt:内容相同但路径不同,在普通 rsync 看来通常表现为删除旧文件并新增新文件。unchanged.txt:两端相同,不应该传输。

先运行接近原脚本的命令:
rsync -n -av --delete --out-format="%n" "source/" "destination/"
它确实列出了要删除和要传输的路径,但普通文件行没有明确告诉我们“新增”还是“更新”。当输出要交给下一段脚本、监控系统或 Agent 判断时,这种信息就不够结构化。
更可靠的差异清单:--itemize-changes
我把命令改成:
rsync -ani --delete \
--out-format="%i|%n%L" \
"source/" "destination/"
这里的关键不是参数变多,而是输出从“名字列表”升级成了“带动作标签的计划表”:
*deleting photos/old-name.txt
*deleting docs/deleted.txt
>f+++++++|docs/added.txt
>f..t....|docs/modified.txt
>f+++++++|photos/new-name.txt

常见标记可以先这样理解:
>f+++++++:向目标端发送一个新文件。>f..t....:文件需要更新,本次至少检测到时间属性不同;是否传输内容还与 quick check、校验和等条件有关。*deleting:目标端文件将被删除,只有启用删除选项时才会出现。.d..t....:目录属性发生变化,不等于正在传输一个普通文件。
不需要第一天就背下每一位。就像看快递单,先看“寄出、退回、签收”这些大状态,遇到特殊情况再查细节即可。

为什么 rsync 能做到增量同步
rsync 并不是每次都把全部文件重新复制一遍。它先比较文件的基本信息,常见判断包括大小和修改时间;满足条件时才进一步传输。远程同步时,它还能通过 rsync 算法减少需要跨网络发送的数据。
可以把它想成老师检查作业:如果作业本页数和最后修改时间都没变化,老师不会重新抄一份;如果只改了其中一部分,就重点处理变化内容。实际行为还会受到 --checksum、文件系统时间精度、rsync 版本和传输方式影响,因此关键任务不能只凭想象判断,仍应看 dry-run 输出并做同步后验证。
要不要使用 --checksum
--checksum 会读取两端文件内容并计算校验值,能发现“大小和时间碰巧一样、内容却不同”的情况,但代价是两端都要完整读取文件。文件很多、磁盘较慢时,它可能比真正传输还耗时。
日常镜像同步通常先使用默认 quick check;高价值归档、迁移验收或怀疑时间戳不可靠时,再增加 --checksum。安全不是把所有最重参数一次性打开,而是根据风险选择验证强度。
正式同步:删除能力必须单独授权
推荐把默认模式设为“不删除”。只有明确需要镜像语义时,才设置环境变量启用删除:
ALLOW_DELETE=1 ./rsync-safe-sync-ubuntu.sh \
"/data/source/" \
"backup-user@<目标地址>:/data/archive/" \
--plan
确认计划后再执行:
ALLOW_DELETE=1 ./rsync-safe-sync-ubuntu.sh \
"/data/source/" \
"backup-user@<目标地址>:/data/archive/" \
--apply
脚本会再次 dry-run,要求输入 APPLY,然后才正式同步;完成后再跑一次 dry-run。第二次结果为空,才说明目标端已经与计划一致。

三个平台的一键脚本
三套脚本都遵守同一套安全原则:默认只计划、默认不删除、正式执行前再次展示计划、同步后再次验证。区别只是操作系统外壳不同。

Windows 11
Windows 11 脚本使用系统提供的 WSL 承载 rsync,不接入第三方同步服务。首次使用时,在管理员 PowerShell 中安装 WSL:
wsl --install
重启并完成 Ubuntu 初始化后,下载脚本:
生成计划:
powershell -ExecutionPolicy Bypass -File .\rsync-safe-sync-windows11.ps1 `
-Source "/mnt/c/Data/source/" `
-Destination "backup-user@<目标地址>:/data/archive/" `
-Mode Plan
确认后执行,并在确实需要镜像删除时显式增加 -AllowDelete:
powershell -ExecutionPolicy Bypass -File .\rsync-safe-sync-windows11.ps1 `
-Source "/mnt/c/Data/source/" `
-Destination "backup-user@<目标地址>:/data/archive/" `
-Mode Apply -AllowDelete
Ubuntu 26.04
chmod +x rsync-safe-sync-ubuntu.sh
./rsync-safe-sync-ubuntu.sh "/data/source/" \
"backup-user@<目标地址>:/data/archive/" --plan
脚本检测不到 rsync 时,只通过 Ubuntu 官方软件仓库安装 rsync 和 openssh-client。正式执行:
./rsync-safe-sync-ubuntu.sh "/data/source/" \
"backup-user@<目标地址>:/data/archive/" --apply
macOS 26
chmod +x rsync-safe-sync-macos.sh
./rsync-safe-sync-macos.sh "$HOME/Documents/source/" \
"backup-user@<目标地址>:/data/archive/" --plan
macOS 系统自带的 rsync 版本可能较旧,但本文使用的基本 dry-run、itemize 和归档同步方式可先通过脚本实际测试。若你的任务需要新版本特性,再从可信包管理渠道升级,不要只因“版本号旧”就盲目替换系统工具。
人工自动执行方法
所谓“人工自动执行”,不是手工逐个复制文件,而是由脚本完成计算与同步,人只负责风险确认。
推荐顺序如下:
- 明确源端和目标端,尤其确认源路径末尾斜杠的语义。
- 先运行
--plan,把输出保存为带时间戳的计划文件。 - 单独检查所有
*deleting行,确认没有目标端独有资料。 - 检查新增和修改数量是否符合预期;突然出现几万条变化时应停止。
- 运行
--apply,输入明确确认词。 - 查看同步后的第二次 dry-run;关键数据再抽样执行哈希或
diff验证。
这种方式像飞机自动驾驶:大部分重复操作交给程序,但起飞、降落和异常处置仍需要人看仪表并作决定。
Agent 自动配置方法
Agent 可以帮助生成配置、运行 dry-run、归类差异并给出风险摘要,但不应该在没有授权时自行启用删除。可以把下面的约束直接交给本地 Agent:
目标:配置 rsync 增量同步。
要求:
- 只使用本机工具、SSH 和目标操作系统官方软件仓库;
- 首次只能执行 dry-run,不得修改文件;
- 使用 --itemize-changes 和结构化输出;
- 默认禁止 --delete;
- 汇总新增、修改、删除数量,并单独列出全部删除项;
- 等待我回复 APPLY 后才能正式同步;
- 正式同步后再次 dry-run,并报告是否为空;
- 日志中隐藏完整地址、账号、密钥和机器名。
Agent 自动化的价值是减少重复劳动,不是替人承担数据删除责任。一个合格的 Agent 流程必须允许中断、允许复核,并留下可读的计划文件。
如何改进原始“差异文件列表”脚本
如果只需要一个简洁的比较版本,可以这样写:
#!/usr/bin/env bash
set -euo pipefail
: "${LOCAL_DIR:?请设置 LOCAL_DIR}"
: "${REMOTE_DIR:?请设置 REMOTE_DIR}"
DIFF_FILE="${DIFF_FILE:-/tmp/rsync-diff-$(date +%Y%m%d-%H%M%S).txt}"
LOCAL_DIR="${LOCAL_DIR%/}/"
rsync --dry-run --archive --itemize-changes \
--out-format='%i|%n%L' \
"$LOCAL_DIR" "$REMOTE_DIR" \
| tee "$DIFF_FILE"
echo "差异计划已保存:$DIFF_FILE"
如果确实需要检查镜像删除,在审核阶段额外加 --delete,但不要再把 deleting 行过滤掉。相反,可以单独提取:
grep '^\*deleting ' "$DIFF_FILE" || true
这时输出的目的不是“得到一份看起来干净的文件清单”,而是“得到一份能支持正确决策的风险清单”。
常见问题 Q&A
dry-run 会不会百分之百保证正式执行结果相同
不能。如果 dry-run 与正式执行之间有人修改了文件、网络挂载变化、权限变化或目标端空间不足,结果就可能不同。因此安全脚本会在正式执行前再次生成计划,并在执行后再验证。
--delete 会删除源端文件吗
通常是删除目标端中源端不存在的文件,而不是反过来删除源端。但源和目标写反后,后果同样严重。执行前必须把两端路径完整显示出来,并确认方向。
为什么“重命名”看起来像删除加新增
rsync 主要根据路径和文件状态工作,并不总能把两个不同路径自动理解为一次重命名。普通同步中,旧路径消失、新路径出现,往往就表现为删除旧文件和传输新文件。
为什么第二次 dry-run 仍有目录变化
可能是目录时间、权限、所有者、扩展属性或两端 rsync/文件系统能力不同。先看 %i 的具体属性位,再决定是否需要保留对应元数据;不要为了追求“绝对空白”随意增加高权限参数。
可以把脚本直接放进 cron 或计划任务吗
可以,但建议先连续观察多次计划输出。无人值守任务应默认禁用删除、设置日志保留、超时、锁和失败告警。涉及删除的无人值守同步最好配合目标端快照或版本化备份。
rsync 是不是备份工具
rsync 是强大的同步工具,但“镜像”不等于“备份”。如果误删或勒索软件加密已经发生,镜像同步可能把错误也同步过去。重要数据还需要历史版本、快照、离线副本或不可变备份。
最后的安全清单
- 先确认同步方向,再确认源目录末尾斜杠。
- 所有路径变量都加双引号。
- 使用
--itemize-changes,不要只看文件名。 - 默认不启用删除;删除项必须单独审核。
- dry-run 与正式执行之间不要间隔太久。
- 正式同步后再次 dry-run,重要文件再做内容校验。
- 自动化日志必须脱敏,不能记录密钥和完整内部地址。
- 镜像之外还要保留历史版本,避免把误操作同步成“正确状态”。
真正安全的增量同步,不是一条参数很多的命令,而是一套可以解释、可以暂停、可以验证的流程。rsync 负责高效搬运文件,dry-run 负责提前亮出清单,而最终的风险判断,应该始终掌握在你手里。