同步一个几个 G 的目录到服务器,哪怕只改了几个文件,用 scpcp 还是得把整个目录重新传一遍——网络稍微差一点,等待时间就长得离谱。rsync 解决的正是这个问题:只传输真正变化的部分,其余的原样跳过。

这是 Linux 命令系列的一篇,承接 Linux 常见命令与工具地图

rsync 为什么”增量”

rsync 判断”变了没”,不是简单看文件名或者改动时间——它用的是一套块级别的对比算法(由 Andrew Tridgell 提出,通常直接叫 rsync algorithm):把文件切成一个个数据块,两边同时计算校验和,只有校验和对不上的块才会真正传输,没变的块直接跳过不传。

这意味着即使是同一个文件,哪怕只改了中间一小段,rsync 也只传那一小段变化的数据,不会把整个文件重传一遍。目录同步同理:目录里没变的文件直接跳过,只有新增、删除、内容变化的文件才会被处理。这是它比 cp/scp 快的根本原因,不是”压缩得更好”这类表面优化。

最常用的参数组合:-avz

日常用 rsync 基本都是这个组合起手:

1
2
3
4
5
6
rsync -avz ./local_dir/ user@host:/remote/path/
# -a:archive,归档模式,等价于同时打开 -rlptgoD 这几个选项——
# 递归子目录、保留符号链接、保留权限、保留修改时间、保留属组属主、保留设备文件和特殊文件
# 简单说就是"尽量把文件的原始状态完整保留",日常同步基本都要带上
# -v:verbose,显示每个被传输的文件名,方便确认到底同步了什么
# -z:compress,传输过程中压缩数据,跨网络传输时能省带宽;本地磁盘之间同步不需要这个

-a 是这几个参数里最关键的一个:不加 -a 的话,rsync 默认不会递归进子目录,也不会保留权限和时间戳这些元信息,同步出来的结果跟预期会差很多。

-P:进度条和断点续传

同步大文件的时候,光有 -avz 还不够——看不到进度,网络一断就得从头再来:

1
2
3
4
rsync -avzP bigfile.iso user@host:/remote/path/
# -P:--partial 和 --progress 两个选项的简写
# --progress:实时显示当前文件的传输进度(速度、百分比、预计剩余时间)
# --partial:传输中断时保留已经传完的部分,下次续传时接着传,不用从头开始

-P 对大文件、不稳定的网络环境特别有用——没有 --partial 的话,传到一半中断,之前传的部分会被删掉,下次又得从零开始。

–delete:让目标和源完全一致

前面的同步只会”新增/更新”文件,源目录里删掉的文件不会影响目标目录——目标那边的旧文件会一直留着。想让目标和源保持完全一致(源删了,目标也跟着删),要加 --delete

1
2
3
rsync -avz --delete ./local_dir/ user@host:/remote/path/
# --delete:目标目录里凡是源目录没有的文件,都会被删除
# 这是个危险参数——用反了方向(源和目标搞反),可能会把不该删的文件删掉

--delete 属于”威力大但容易出事”的参数,用之前一定要先确认源和目标的方向没搞反。

–dry-run:先演练一遍,别直接动手

尤其是带了 --delete 这种会删除文件的操作,实际执行前应该先跑一遍演练模式,只看会发生什么,不真的执行:

1
2
3
rsync -avz --delete --dry-run ./local_dir/ user@host:/remote/path/
# --dry-run(也可以用简写 -n):只打印会做的操作,不实际传输或删除任何文件
# 确认输出的文件列表符合预期之后,去掉 --dry-run 再跑一次真正执行

只要命令里带了 --delete,先 --dry-run 看一眼输出,是能避免”手滑删错文件”最简单也最有效的办法。

排除规则:跳过不想同步的文件

项目目录里的 .gitnode_modules 这类文件,同步时通常不需要带上:

1
2
3
4
5
6
7
8
9
rsync -avz --exclude='.git' --exclude='node_modules' ./project/ user@host:/remote/project/
# --exclude:排除匹配指定模式的文件或目录,可以写多次排除多种内容

rsync -avz --exclude-from='exclude-list.txt' ./project/ user@host:/remote/project/
# --exclude-from:从文件里批量读取排除规则,规则多的时候比堆一串 --exclude 清晰
# exclude-list.txt 里每行写一条规则,比如:
# .git
# node_modules
# *.log

远程同步和一个经典的坑:源目录带不带末尾斜杠

rsync 同步远程主机走的是 SSH 通道,语法跟 scp 类似但功能强得多:

1
2
3
rsync -avz ./local_dir/ user@host:/remote/path/
# 基于 SSH 传输,需要目标主机开着 SSH 服务、当前用户有权限登录
# 跟 scp 语法接近,但增量传输、断点续传这些能力 scp 都没有

源目录路径末尾带不带 /,行为是不一样的——这是 rsync 最容易踩的坑,官方手册专门用例子说明过:

1
2
3
4
5
6
7
8
rsync -av /src/foo /dest
# 源路径不带末尾斜杠:把 foo 这个目录本身同步过去
# 结果是 /dest/foo/ 里存放 foo 目录的内容——多出了一层 foo 目录

rsync -av /src/foo/ /dest/foo
# 源路径带末尾斜杠:表示"同步这个目录里的内容",不是目录本身
# 这条命令和上面那条命令,最终产生的结果是一样的——都是 /dest/foo/ 里存放 foo 的内容
# 区别在于目标路径要不要手动多写一层 foo

简单记忆:源路径末尾的 / 决定”要不要在目标里多包一层目录”——同步目录内容用 foo/,同步目录本身(连同目录名)用 foo(不带斜杠),目标那边对应地要不要手动加上这层目录名。搞混这个经常导致同步出来的目录结构和预期差一层,排查起来莫名其妙。

写到这里

rsync 的核心就是这几件事:增量传输省时间、-P 应付大文件和不稳定网络、--delete--dry-run 保证安全、--exclude 跳过不需要的内容。这几个参数组合起来,日常的目录同步和轻量级备份基本够用——更复杂的定时增量备份场景,通常是在这套参数基础上配合 cron 或者专门的备份工具,但底层同步逻辑还是这些。