迁两个大目录时,最容易踩的坑不是参数记不全,而是把两条路径随手塞进一条命令,或者看终端跳出 100% 就把源盘格了。
更稳的做法是:每组源和目标一一对应,先 --dry-run,正式跑时保留未完成文件并写日志,结束后再做只读比对。普通比对没输出,只说明按大小、修改时间和相关属性没有差异;要确认内容一致,再加 --checksum。
本文关键参数和退出码对照过 rsync 手册 与上游退出码定义。
假设要做的是:
/mnt/disk1/photos/ → /mnt/disk2/photos/
/mnt/disk1/archive/ → /mnt/disk2/archive/
为什么不要写进一条命令
rsync 的最后一个位置参数是目标,前面的都是源。下面这种写法:
rsync -a /source/a/ /source/b/ /target/
是把两个源都汇进同一个 /target/,并不是 a → target/a、b → target/b。两个目录要落到两个目标时,就跑两次,或者写串行脚本。
分开跑还有实际好处:日志和退出状态独立,一组失败不会把另一组一起报成成功;机械硬盘也能顺序读写,少抢磁头。如果两个源真要进同一目标,先看有没有同名相对路径——两边都有 2026/report.pdf 时,后跑的会盖掉先跑的。
先确认挂载点
大目录迁移里最危险的一种失败,是目标盘没挂上,路径却还在。rsync 会把数据写进根文件系统里的普通目录,直到系统盘写满。
开始前看设备和挂载:
lsblk -o NAME,SIZE,FSTYPE,UUID,MOUNTPOINTS
findmnt -T /mnt/disk1/photos
findmnt -T /mnt/disk2/photos
findmnt -T /mnt/disk1/archive
findmnt -T /mnt/disk2/archive
df -hT /mnt/disk1 /mnt/disk2
readlink -f "$SRC1" "$DST1" "$SRC2" "$DST2"
确认两块盘是预期设备、目标空间够、不是只读、不是同一文件系统上的两个名字、目标也不是源的子目录。再做一个小写入测试,只删自己刚建的测试文件,别用通配符清目标。
末尾斜杠决定复制层级
rsync -a /source/ /target/
rsync -a /source /target/
第一条复制的是 /source/ 里面的内容;第二条会在目标下再造一层 source/。已经建好对应目录时,源和目标后面都写 /。第一次执行前用 --dry-run --itemize-changes 看将出现的相对路径,不要靠目录名猜。
可以直接用的命令
SRC1=/mnt/disk1/photos
DST1=/mnt/disk2/photos
SRC2=/mnt/disk1/archive
DST2=/mnt/disk2/archive
sudo mkdir -p "$DST1" "$DST2"
预演:
sudo rsync -aHAXn \
--numeric-ids \
--itemize-changes \
"$SRC1/" \
"$DST1/"
路径和变更列表都对了,去掉 -n 再正式跑。第一次迁移不要加 --delete:目标里多出来的文件先留着,结构和校验都确认后再决定要不要做成镜像。
sudo rsync -aHAX \
--numeric-ids \
--partial \
--partial-dir=.rsync-partial \
--info=progress2,stats2 \
--log-file=/var/log/rsync-photos.log \
"$SRC1/" \
"$DST1/"
第二组同样处理,换路径和日志文件。两组串行,不要并行抢同一块机械盘。
这些参数各自管什么
基础组合是 -aHAX:
| 参数 | 作用 | 注意 |
|---|---|---|
-a | 归档:递归并保留常见属性 | 不含硬链接、ACL、扩展属性 |
-H | 保留硬链接 | 大量硬链接会多占内存 |
-A | POSIX ACL | 目标文件系统要支持 ACL |
-X | 扩展属性 | 目标要支持 xattr |
--numeric-ids | 按数字保留 UID/GID | 适合 Linux 磁盘迁移 |
--partial | 中断后留下未传完的文件 | 下次会接着检查 |
--partial-dir=.rsync-partial | 未完成文件放专用目录 | 校验完再清 |
--info=progress2 | 整个任务的总体进度 | 比 --progress 更适合超大目录 |
--log-file | 写详细日志 | 日志目录必须可写 |
-a 等价于 -rlptgoD,这点手册写得很清楚。目标是 FAT、exFAT 或某些网络盘时,权限、ACL、xattr 可能存不住,参数不是越多越安全。先看两端文件系统:
findmnt -no FSTYPE -T "$SRC1"
findmnt -no FSTYPE -T "$DST1"
普通照片、视频、文档如果本来就没有硬链接和 ACL,可以按实际减参数。
增量同步会复制什么
默认按大小和修改时间判断要不要更新。再跑同一条命令时:新文件会复制,变过的会更新,没变的跳过,目标独有文件会留下(没加 --delete 的话),中断的文件会按当前状态继续。
这不是文件系统快照。源目录一边拷一边写,目标可能拼进不同时间点的文件。照片库可以拷完再跑一次增量补齐;数据库、虚拟机镜像、正在写的应用数据,应先停写或打快照。
--itemize-changes 里常见几行:
>f+++++++++:目标没有,将新建>f后面带属性标记:内容或属性要更新.d:目录属性要调*deleting:配合--delete时,目标独有内容会被删
预演里冒出意外的顶层目录名,多半是源路径末尾 / 写错。几乎所有大文件都要重传,就查时间精度、挂载参数,以及文件是不是还在被改。
长时间任务放进 tmux
数 TB 可能要跑很多小时。别指望 SSH 窗口一直开着:
tmux new -s rsync-copy
sudo /usr/local/sbin/sync-two-dirs.sh # 或直接跑上面的 rsync
# Ctrl+b 再按 d 脱离
tmux attach -t rsync-copy
脚本里用 set -Eeuo pipefail,第一组非零就停,不要把整次任务报成成功。已经一致的文件重跑会被跳过,一般不需要另写一条「续传命令」。
结束先看 echo $?。0 只表示这次运行没报错,不等于每个文件内容都核过。常见非零码(与上游 errcode.h 一致):
| 状态码 | 含义 |
|---|---|
| 11 | 文件 I/O 错误 |
| 12 | 协议数据流错误 |
| 23 | 部分文件未传完 |
| 24 | 源文件在传输中消失 |
| 30 | 发送/接收超时 |
日志里还有 IO error、Permission denied、failed、vanished file,就不能只看最后一行总字节数。
三层校验
第一层:正式复制后再 dry-run,并带上 --delete(仍有 -n,只报告不删除):
sudo rsync -aHAXn \
--numeric-ids \
--delete \
--itemize-changes \
"$SRC1/" \
"$DST1/"
没输出通常表示:没有要新增或更新的文件,目标也没有源里没有的路径,要求保留的属性也对齐。只有 .rsync-partial/ 时,先确认没有未完成任务。出现 *deleting 先核对那些文件该不该留。
第二层:加 -c(--checksum)。它会读双方内容再算校验和,用来决定要不要传,而不是只比大小和时间。带 -n 时只报告,不改文件。注意:这是 rsync 自己的传输判定校验,算法随版本协商(常见 MD5 / xxHash),不是 SHA-256 审计清单。数 TB 会把两块盘再读一遍,磁盘已经不健康时不要反复全盘扫。
第三层:比文件个数和逻辑字节数,用 find -xdev,别拿 du -sh 当唯一证据——稀疏文件、压缩、硬链接、块大小都会让占用对不上。
需要可留档的内容清单时,再在两端按相对路径做 SHA-256 再 diff。那只覆盖普通文件内容和路径,不管权限、ACL、硬链接和 xattr;完整 Linux 迁移要和 rsync -aHAXnc 一起看。
--delete 放到最后
--delete 会让目标变成源的镜像:源里没有的路径会被删。先只读预演,确认源目标没写反、挂载在线、每条 *deleting 都符合预期、目标没有要单独留的文件、另有一份可恢复备份。需要回看删除内容时,用 --backup --backup-dir=...,备份目录不要放进还会被同步的源树。
NAS 上常见 #recycle/、@eaDir/、.Trash-*/。排除 '#recycle/' 必须加引号,否则 shell 会把 # 当成注释。正式命令和校验命令要用同一套排除规则。
本地盘到本地盘通常不必 -z。照片、视频、压缩包再压一遍只浪费 CPU。生产机上可用 nice / ionice 降优先级;跨网络再考虑 --bwlimit(新版 rsync 支持 80M 这种后缀)。
常见翻车
- Permission denied:先分清是读源失败、写目标失败,还是设所有者 / ACL / xattr 失败。不要对整个目标
chmod -R 777。 - No space left:同时看
df -hT和df -i,小文件可能先耗尽 inode。 - Input/output error:立刻看
dmesg和 SMART。持续读错误时先保还能读的数据,别拿全盘校验去压故障盘。 - file has vanished(24):扫描后、复制前源文件被挪走或删了。业务目录应停写或从快照拷。
- 所有文件又传一遍:时间精度、时钟、挂载方式、文件被重新打包,都可能触发。用
--itemize-changes看触发的是内容、大小、时间还是权限。
什么时候才能删源
至少等到:两组 rsync 退出码为 0,日志没有未处理的 I/O / 权限 / 空间错误,普通 dry-run 没有意外输出,重要数据做过 -nc 或独立 SHA-256,关键文件能从目标打开。有条件的话再做一次从目标恢复的演练。
复制完成、校验通过、可恢复,是三件事。终端显示 100% 不等于源盘可以下线。