rsync 命令详解与参数实测(Ubuntu 22.04)
以 Ubuntu 22.04 rsync 3.2.7 为实测环境,逐参数验证 rsync:-av 基础同步与输出剖析、尾斜杠语义、推送与拉取、增量传输字节对照、-n 预演、--delete、-u/--checksum 跳过策略、-a=rlptgoD 逐字母拆解、--exclude/--include、-z 压缩、-e 指定端口、-P 进度条与 --partial 断点续传、--bwlimit 限速与错误排障,每项附真实回显。
rsync 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 rsync 的核心参数——本地与远程同步、尾斜杠语义、增量传输的字节级实证、预演与删除同步、时间与校验两种跳过策略、档案模式
-a的逐字母拆解、过滤与压缩、指定远程端口、进度显示与断点续传、限速与错误排障,全部命令与回显已在 Ubuntu 22.04(rsync 3.2.7)实测通过。
一、环境信息
本节列出实测环境的完整信息,文中所有命令与回显均来自该环境,读者在同版本系统上可复现同样的结果。
- 操作系统:Ubuntu 22.04.5 LTS(jammy),架构 x86_64,内核
6.8.0-138-generic - rsync:本机与"远程"两端均为
rsync 3.2.7 protocol version 31(Ubuntu 仓库自带版本) - 传输底层:OpenSSH 套件
OpenSSH_8.9p1 Ubuntu-3ubuntu0.18,加密库OpenSSL 3.0.2 - sshd 监听:默认实例监听
22端口;为实测-e指定端口,另启动了一个监听2222端口的测试实例 - 测试账号:普通用户
tester(密码tester123,uid 1012)、tester1(密码tester1pass)——两个账号均为公开演示环境专用的测试凭据 - 密码自动化工具:
sshpass 1.09(apt 安装),用于在非交互脚本中自动填充密码 - 演示数据目录:
/root/demo/rsync/,其中演示文件的属主统一设置为1012:1012(即 tester 在本机的 uid/gid),让-a的属主保留在同步时不产生干扰(属主问题的专门实验见 3.13 节) - 网络方式:传输目标全部使用本机回环地址
127.0.0.1自连自收,本地与"远程"同机,全程不依赖公网
二、命令概览
2.1 语法与测试数据
rsync(remote synchronize)是一个支持增量传输的文件同步工具:它先比对源与目标的差异,只传输变化的部分,因此第二次同步同样的目录往往只消耗极少的流量。其基本语法有三类形态:
rsync [选项] 源... 目标 # 本地拷贝,类似 cp -r
rsync [选项] 本地路径... [user@]host:远程路径 # 推送(本地 → 远程)
rsync [选项] [user@]host:远程路径... 本地路径 # 拉取(远程 → 本地)
rsync 有两条著名硬规则:
- 尾斜杠语义:源路径以
/结尾表示"复制目录内容",不带/表示"复制目录本身"——src/与src一字之差,结果结构完全不同(3.2 节实测对照); - 单冒号走远程 shell:
host:path形态底层由 ssh 承载;host::path与rsync://形态连接的是 rsync 守护进程,需要服务端预先配置模块,本文实测全部使用单冒号形态。
本文的测试数据约定:
- 目标主机:
127.0.0.1(回环自连),默认端口 22,测试实例端口 2222; - 登录账号:
tester,密码tester123,非交互场景在 rsync 命令整体前加sshpass -p tester123。注意与 scp 的用法差异:rsync 内部自行调用 ssh,密码提示出现在 rsync 的终端上,所以 sshpass 必须包住 rsync 整体(sshpass -p ... rsync ...),只包住某一段是喂不进密码的(忘记包裹时的真实报错形态见 3.21 节); - 演示目录:
/root/demo/rsync/src/(三个普通文件 + 一级子目录),每个实验块自带数据准备与清理,可独立复现。
2.2 usage 用法总览
不带任何参数直接执行 rsync,会先打印版本横幅与完整选项清单(约 150 行),最后以语法主体和一行错误收尾、退出码 1。下面逐行照抄其中最关键的语法主体与收尾错误行(横幅与选项清单从略,可用 rsync --help 查看):
$ rsync; echo rc=$?
Usage: rsync [OPTION]... SRC [SRC]... DEST
or rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST
or rsync [OPTION]... SRC [SRC]... [USER@]HOST::DEST
or rsync [OPTION]... SRC [SRC]... rsync://[USER@]HOST[:PORT]/DEST
or rsync [OPTION]... [USER@]HOST:SRC [DEST]
or rsync [OPTION]... [USER@]HOST::SRC [DEST]
or rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST]
The ':' usages connect via remote shell, while '::' & 'rsync://' usages connect
to an rsync daemon, and require SRC or DEST to start with a module name.
rsync error: syntax or usage error (code 1) at main.c(1802) [client=3.2.7]
rc=1
从中可以看到 rsync 的七种语法形态与两大连接方式。本文逐项实测其中最常用的一批参数,对应关系如下:
| 参数 | 作用 | 实测小节 |
|---|---|---|
(无参数 / -av) | 本地拷贝与输出剖析 | 3.1 |
| (源尾斜杠) | src 与 src/ 语义对照 | 3.2 |
| (远程路径) | 推送与拉取 | 3.3 |
| (增量机制) | 第二次只传变化文件 | 3.4 |
-n / --dry-run | 预演不落盘 | 3.5 |
--delete | 删除目标端多余文件 | 3.6 |
-u | 跳过目标端较新的文件 | 3.7 |
-c | 按内容校验决定传输 | 3.8 |
-r | 递归目录(-a 的成员) | 3.9 |
-l / -L | 保留 / 解引用符号链接 | 3.10 |
-p | 保留权限 | 3.11 |
-t | 保留修改时间 | 3.12 |
-o / -g | 保留属主 / 属组 | 3.13 |
-D | 保留设备与特殊文件 | 3.14 |
-H | 保留硬链接 | 3.15 |
--exclude / --include | 按模式过滤 | 3.16 |
-z | 压缩传输 | 3.17 |
-e | 指定远程 shell 与端口 | 3.18 |
-P | 进度显示 + 断点续传 | 3.19 |
--bwlimit | 限速传输 | 3.20 |
| (错误形态) | 退出码与排障 | 3.21 |
💡 说明:usage 中还有
--backup(同步前备份旧文件)、--link-dest(基于历史快照的硬链接增量备份)、--partial-dir、--whole-file、--itemize-changes等进阶参数,使用频率较低,本文不逐项展开。
2.3 版本信息
rsync --version
rsync version 3.2.7 protocol version 31
Copyright (C) 1996-2022 by Andrew Tridgell, Wayne Davison, and others.
Web site: https://rsync.samba.org/
Capabilities:
64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints,
socketpairs, symlinks, symtimes, hardlinks, hardlink-specials,
hardlink-symlinks, IPv6, atimes, batchfiles, inplace, append, ACLs,
xattrs, optional secluded-args, iconv, prealloc, stop-at, no crtimes
Optimizations:
SIMD-roll, no asm-roll, openssl-crypto, no asm-MD5
Checksum list:
xxh128 xxh3 xxh64 (xxhash) md5 md4 sha1 none
Compress list:
zstd lz4 zlibx zlib none
Daemon auth list:
sha512 sha256 sha1 md5 md4
rsync comes with ABSOLUTELY NO WARRANTY. This is free software, and you
are welcome to redistribute it under certain conditions. See the GNU
General Public Licence for details.
💡 说明:几行关键信息值得记住——
protocol version 31是 3.x 系列的传输协议版本,两端 rsync 版本差异过大时按较低一方的协议通信,排障时报版本号报这一个就够;Checksum list显示本版本支持的校验算法(优先xxh128,向下兼容md5),Compress list显示压缩算法(优先zstd,向下兼容zlib),-c与-z的实际表现依赖两端都支持的算法集合。
三、参数逐项实测
3.1 本地同步:-av 基础与输出剖析
rsync 不依赖 ssh 也能工作——源和目标都是本地路径时就是一次增强版拷贝。先准备演示目录:
rm -rf /root/demo/rsync && mkdir -p /root/demo/rsync/src/sub && printf "alpha\n" > /root/demo/rsync/src/a.txt && printf "bravo\n" > /root/demo/rsync/src/b.txt && printf "charlie\n" > /root/demo/rsync/src/sub/c.txt && chmod 640 /root/demo/rsync/src/a.txt && touch -d "2026-03-01 09:00:00" /root/demo/rsync/src/a.txt && chown -R 1012:1012 /root/demo/rsync && find /root/demo/rsync/src | sort
/root/demo/rsync/src
/root/demo/rsync/src/a.txt
/root/demo/rsync/src/b.txt
/root/demo/rsync/src/sub
/root/demo/rsync/src/sub/c.txt
执行最常用的组合 -av(-a 档案模式 + -v 详细输出):
rsync -av /root/demo/rsync/src /root/demo/rsync/dst-tree; echo rc=$?
sending incremental file list
created directory /root/demo/rsync/dst-tree
src/
src/a.txt
src/b.txt
src/sub/
src/sub/c.txt
sent 331 bytes received 137 bytes 936.00 bytes/sec
total size is 20 speedup is 0.04
rc=0
核对目标结构(注意源写的是不带尾斜杠的 src):
find /root/demo/rsync/dst-tree | sort
/root/demo/rsync/dst-tree
/root/demo/rsync/dst-tree/src
/root/demo/rsync/dst-tree/src/a.txt
/root/demo/rsync/dst-tree/src/b.txt
/root/demo/rsync/dst-tree/src/sub
/root/demo/rsync/dst-tree/src/sub/c.txt
stat -c "%a %y %U %n" /root/demo/rsync/dst-tree/src/a.txt
640 2026-03-01 09:00:00.000000000 +0000 tester /root/demo/rsync/dst-tree/src/a.txt
验证结论:
-av的一次输出分四段:sending incremental file list(文件清单)、逐条文件行(目录以/结尾、符号链接显示为名 -> 目标)、sent/received流量统计、total size ... speedup ...汇总;speedup是"理论总量 ÷ 实际传输量"的加速比,首传为 1 以下属正常(清单开销占比高),增量同步时它会远大于 1(见 3.4、3.17 节);- 权限
640与修改时间2026-03-01 09:00:00原样到达目标——这是-a中-p与-t的功劳(3.11、3.12 节逐项验证)。
3.2 尾斜杠语义:src 与 src/ 一字之差
同样的源目录,源路径末尾加一个 / 再同步一次:
rm -rf /root/demo/rsync/dst-flat && rsync -av /root/demo/rsync/src/ /root/demo/rsync/dst-flat; echo rc=$?
sending incremental file list
created directory /root/demo/rsync/dst-flat
./
a.txt
b.txt
sub/
sub/c.txt
sent 320 bytes received 136 bytes 912.00 bytes/sec
total size is 20 speedup is 0.04
rc=0
find /root/demo/rsync/dst-flat | sort
/root/demo/rsync/dst-flat
/root/demo/rsync/dst-flat/a.txt
/root/demo/rsync/dst-flat/b.txt
/root/demo/rsync/dst-flat/sub
/root/demo/rsync/dst-flat/sub/c.txt
验证结论:
- 源不带尾斜杠(3.1 节):目标出现
dst-tree/src/...,把src目录本身复制了过去; - 源带尾斜杠(本节):目标直接是
dst-flat/a.txt ...,只复制目录内容,文件清单里也变成了./、a.txt这样的相对形式; - 规则只作用于源路径——目标路径写不写尾斜杠不影响结构(目标目录不存在时会自动创建,输出中的
created directory行就是证据)。
⚠️ 注意:这是 rsync 最经典的误用点。想把"目录内容"同步进一个已存在的目录,源却忘了写尾斜杠,结果会多套一层目录。写脚本时建议:源目录内容同步固定写
src/,并在-n预演(3.5 节)里核对文件清单再真跑。
3.3 远程推送与拉取
远程形态把本地路径换成 [user@]host:路径 即可。先清空远端旧数据,然后推送(源带尾斜杠,目标是远端相对家目录的 rsync-demo/):
sshpass -p tester123 ssh tester@127.0.0.1 "rm -rf ~/rsync-demo; echo remote-cleaned"
remote-cleaned
sshpass -p tester123 rsync -av /root/demo/rsync/src/ tester@127.0.0.1:rsync-demo/; echo rc=$?
sending incremental file list
created directory rsync-demo
./
a.txt
b.txt
sub/
sub/c.txt
sent 312 bytes received 117 bytes 858.00 bytes/sec
total size is 20 speedup is 0.05
rc=0
登录远端核对结构与属性:
sshpass -p tester123 ssh tester@127.0.0.1 "find ~/rsync-demo | sort; stat -c %a ~/rsync-demo/a.txt; stat -c %y ~/rsync-demo/a.txt"
/home/tester/rsync-demo
/home/tester/rsync-demo/a.txt
/home/tester/rsync-demo/b.txt
/home/tester/rsync-demo/sub
/home/tester/rsync-demo/sub/c.txt
640
2026-03-01 09:00:00.000000000 +0000
反向拉取(源与目标位置互换):
rm -rf /root/demo/rsync/pulled && sshpass -p tester123 rsync -av tester@127.0.0.1:rsync-demo/ /root/demo/rsync/pulled/; echo rc=$?
receiving incremental file list
created directory /root/demo/rsync/pulled
./
a.txt
b.txt
sub/
sub/c.txt
sent 96 bytes received 315 bytes 274.00 bytes/sec
total size is 20 speedup is 0.05
rc=0
find /root/demo/rsync/pulled | sort
/root/demo/rsync/pulled
/root/demo/rsync/pulled/a.txt
/root/demo/rsync/pulled/b.txt
/root/demo/rsync/pulled/sub
/root/demo/rsync/pulled/sub/c.txt
验证结论:
- 推、拉只是源与目标的位置互换,命令结构完全对称;细节差异在首行——推送是
sending incremental file list,拉取是receiving incremental file list,日志排障时可以据此分辨方向; - 远端
a.txt权限640、修改时间2026-03-01与源一致,属性保留在远程形态下同样生效; - 远程路径冒号后写相对路径(
rsync-demo/)时相对远端家目录/home/tester,写绝对路径则以/开头。
💡 技术拓展:同为走 ssh 的传输工具,三者的选型参照:
| 能力 | cp | scp | rsync |
|---|---|---|---|
| 增量传输(只传变化) | 无 | 无 | 有(3.4 节) |
| 属性保留 | cp -a | scp -p(时间戳) | -a 全套(3.9 节) |
| 删除目标端多余文件 | 无 | 无 | --delete(3.6 节) |
| 断点续传 | 无 | 无 | -P(3.19 节) |
| 限速 | 无 | scp -l(Kbit/s) | --bwlimit(KB/s,3.20 节) |
| 指定端口 | — | -P 2222(大写) | -e "ssh -p 2222"(3.18 节) |
大目录的周期性同步、属性要求高、需要预演与过滤的场景选 rsync;单文件一次性传输 scp 更省事。
3.4 增量同步实证:第二次只传变化文件
在 3.3 节已同步的基础上,修改一个文件、新增一个文件,再执行同样的同步命令:
printf "alpha-CHANGED\n" > /root/demo/rsync/src/a.txt && printf "delta\n" > /root/demo/rsync/src/d.txt && chown 1012:1012 /root/demo/rsync/src/d.txt && sshpass -p tester123 rsync -av /root/demo/rsync/src/ tester@127.0.0.1:rsync-demo/; echo rc=$?
sending incremental file list
./
a.txt
d.txt
sent 289 bytes received 64 bytes 706.00 bytes/sec
total size is 34 speedup is 0.10
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "cat ~/rsync-demo/a.txt ~/rsync-demo/d.txt"
alpha-CHANGED
delta
验证结论:
- 文件清单里只有
a.txt和d.txt——未变化的b.txt、sub/、sub/c.txt一条都没出现; - rsync 的默认比对是 quick check:文件大小与修改时间都不变的文件直接跳过,不做内容比较(时间不可信场景的应对见 3.8 节
-c); - 本次实际发送 289 字节就完成了 34 字节内容 + 元数据的同步;目录越大,增量节省越显著。
3.5 -n/--dry-run:先看后跑
-n(长格式 --dry-run)完整执行比对过程,但不写入任何数据。新增 e.txt 后先预演:
printf "echo-test\n" > /root/demo/rsync/src/e.txt && chown 1012:1012 /root/demo/rsync/src/e.txt && sshpass -p tester123 rsync -avn /root/demo/rsync/src/ tester@127.0.0.1:rsync-demo/; echo rc=$?
sending incremental file list
e.txt
sent 200 bytes received 20 bytes 440.00 bytes/sec
total size is 44 speedup is 0.20 (DRY RUN)
rc=0
确认远端没有被写入:
sshpass -p tester123 ssh tester@127.0.0.1 "ls ~/rsync-demo"
a.txt
b.txt
d.txt
sub
去掉 -n 真正执行(输出重定向隐藏,只看退出码):
sshpass -p tester123 rsync -av /root/demo/rsync/src/ tester@127.0.0.1:rsync-demo/ >/dev/null; echo synced-e rc=$?
synced-e rc=0
验证结论:
- 预演回显与真实运行几乎一致,唯一区别是汇总行多了
(DRY RUN)标记; - 预演后远端目录列表没有
e.txt,确认-n零写入;正式执行后rc=0; -n是一切高风险操作(尤其--delete)的标准前置步骤,见 3.6 节的组合用法。
3.6 --delete:删除目标端多余文件
默认行为下 rsync 只增不删:源里删掉的文件在目标端会原样留着。先在源端删除 b.txt,普通同步后远端依旧保留:
rm /root/demo/rsync/src/b.txt && sshpass -p tester123 rsync -av /root/demo/rsync/src/ tester@127.0.0.1:rsync-demo/; echo rc=$?
sending incremental file list
./
sent 179 bytes received 20 bytes 398.00 bytes/sec
total size is 38 speedup is 0.19
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "ls ~/rsync-demo"
a.txt
b.txt
d.txt
e.txt
sub
加上 --delete 先预演——注意回显里出现了 deleting b.txt:
sshpass -p tester123 rsync -avn --delete /root/demo/rsync/src/ tester@127.0.0.1:rsync-demo/; echo rc=$?
sending incremental file list
deleting b.txt
sent 176 bytes received 29 bytes 410.00 bytes/sec
total size is 38 speedup is 0.19 (DRY RUN)
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "ls ~/rsync-demo"
a.txt
b.txt
d.txt
e.txt
sub
预演确认无误后真跑:
sshpass -p tester123 rsync -av --delete /root/demo/rsync/src/ tester@127.0.0.1:rsync-demo/; echo rc=$?
sending incremental file list
deleting b.txt
sent 176 bytes received 29 bytes 410.00 bytes/sec
total size is 38 speedup is 0.19
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "ls ~/rsync-demo"
a.txt
d.txt
e.txt
sub
验证结论:
- 不加
--delete:第一次同步的文件清单里只有./(目录时间戳变化),没有任何传输,远端b.txt原样保留; --delete让目标端"多余"的文件被删除,回显以deleting 文件名标注;-n预演同样会列出将删除的文件——先-n后真跑是使用--delete的固定流程;--delete删除的是"目标端有而源端没有"的文件,误删风险集中在源端过滤规则写错时(配合--exclude的行为见 4.1 节,防护手段见第五节 FAQ)。
3.7 -u:跳过目标端较新的文件
-u(--update)在 quick check 之外增加一条规则:目标端文件的修改时间比源端新时直接跳过,用于保护目标端的新改动。先推送 f.txt,再在远端把它改成一份"更新"的版本(内容不同、时间改到 2027 年):
printf "local-v1\n" > /root/demo/rsync/src/f.txt && chown 1012:1012 /root/demo/rsync/src/f.txt && sshpass -p tester123 rsync -av /root/demo/rsync/src/f.txt tester@127.0.0.1:rsync-demo/f.txt; echo rc=$?
sending incremental file list
f.txt
sent 117 bytes received 35 bytes 304.00 bytes/sec
total size is 9 speedup is 0.06
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "printf \"remote-v2-newer\n\" > ~/rsync-demo/f.txt; touch -d 2027-01-01 ~/rsync-demo/f.txt; stat -c %s ~/rsync-demo/f.txt; stat -c %y ~/rsync-demo/f.txt"
16
2027-01-01 00:00:00.000000000 +0000
带 -u 同步:远端文件较新,被跳过:
sshpass -p tester123 rsync -avu /root/demo/rsync/src/f.txt tester@127.0.0.1:rsync-demo/; echo rc=$?
sending incremental file list
sent 61 bytes received 12 bytes 146.00 bytes/sec
total size is 9 speedup is 0.12
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "cat ~/rsync-demo/f.txt"
remote-v2-newer
对照组:不带 -u 同步,旧版本覆盖回去:
sshpass -p tester123 rsync -av /root/demo/rsync/src/f.txt tester@127.0.0.1:rsync-demo/; echo rc=$?
sending incremental file list
f.txt
sent 117 bytes received 41 bytes 316.00 bytes/sec
total size is 9 speedup is 0.06
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "cat ~/rsync-demo/f.txt"
local-v1
验证结论:
- 带
-u:文件清单为空,远端较新的remote-v2-newer内容得以保留; - 不带
-u:quick check 发现大小不一致(9 字节 vs 16 字节)就传输,远端新内容被源的旧版本覆盖; -u的判定依据是修改时间,时区错乱或时间被人为改动会造成误判,要求严格的场景改用-c(3.8 节)。
3.8 -c/--checksum:按内容校验决定传输
-c(--checksum)把比对依据从"大小 + 时间"换成"对两端文件逐字节计算校验和"。构造一个能骗过 quick check 的场景:远端文件与源大小相同、修改时间相同、内容不同:
printf "AAAA" > /root/demo/rsync/src/x.txt && touch -d "2026-05-05 12:00:00" /root/demo/rsync/src/x.txt && chown 1012:1012 /root/demo/rsync/src/x.txt && sshpass -p tester123 rsync -av /root/demo/rsync/src/x.txt tester@127.0.0.1:rsync-demo/x.txt; echo rc=$?
sending incremental file list
x.txt
sent 108 bytes received 35 bytes 286.00 bytes/sec
total size is 4 speedup is 0.03
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "printf \"BBBB\" > ~/rsync-demo/x.txt; touch -d \"2026-05-05 12:00:00\" ~/rsync-demo/x.txt; stat -c %s ~/rsync-demo/x.txt; stat -c %y ~/rsync-demo/x.txt"
4
2026-05-05 12:00:00.000000000 +0000
quick check 视角下两端"完全一致",默认比对什么也不传:
sshpass -p tester123 rsync -av /root/demo/rsync/src/x.txt tester@127.0.0.1:rsync-demo/; echo rc=$?
sending incremental file list
sent 57 bytes received 12 bytes 138.00 bytes/sec
total size is 4 speedup is 0.06
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "cat ~/rsync-demo/x.txt"
BBBB
加 -c 强制按校验和比对,内容差异被识别并传输:
sshpass -p tester123 rsync -avc /root/demo/rsync/src/x.txt tester@127.0.0.1:rsync-demo/; echo rc=$?
sending incremental file list
x.txt
sent 124 bytes received 41 bytes 110.00 bytes/sec
total size is 4 speedup is 0.02
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "cat ~/rsync-demo/x.txt"
AAAA
验证结论:
- 默认比对被"同大小同时间、不同内容"的文件骗过(
BBBB原样留存);-c按内容发现差异并把源端AAAA同步过去; -c的代价是要读取两端全部文件内容算校验和,大目录上开销显著,只适合定期做一致性核对,不适合当日常同步参数。
3.9 -a 档案模式与 -r 递归
-a(--archive)是 rsync 的招牌参数,--help 对它的定义只有一行(逐行照抄):
--archive, -a archive mode is -rlptgoD (no -A,-X,-U,-N,-H)
即 -a = -r(递归)+ -l(符号链接)+ -p(权限)+ -t(时间)+ -g(属组)+ -o(属主)+ -D(设备与特殊文件),并且不包含 -A、-X、-U、-N、-H。本节与后续小节逐字母验证,先看 -r。rsync 默认不递归——关掉 -a 自带的递归试试(--no-OPTION 语法可以关闭任意隐含选项,help 中原文为 --no-OPTION turn off an implied OPTION (e.g. --no-D)):
rm -rf /root/demo/rsync/dst-nor && rsync -av --no-r /root/demo/rsync/src /root/demo/rsync/dst-nor; echo rc=$?
skipping directory src
sent 29 bytes received 12 bytes 27.33 bytes/sec
total size is 0 speedup is 0.00
rc=0
find /root/demo/rsync/dst-nor | sort
find: '/root/demo/rsync/dst-nor': No such file or directory
验证结论:
- 没有
-r,源目录本身被跳过(回显skipping directory src),什么都没有复制,目标目录压根没被创建; - 更隐蔽的是退出码
rc=0——rsync 把它当作正常的"跳过"而非错误,脚本里只判断退出码会漏掉这种失误,必须配合-v输出或传输后的存在性检查。
3.10 -l 保留符号链接与 -L 解引用
准备一个含符号链接、硬链接、设备文件的属性演示目录(后几节共用):
rm -rf /root/demo/rsync/attr && mkdir -p /root/demo/rsync/attr && printf perm > /root/demo/rsync/attr/p.txt && chmod 640 /root/demo/rsync/attr/p.txt && touch -d "2026-02-02 10:00:00" /root/demo/rsync/attr/p.txt && printf q > /root/demo/rsync/attr/q.txt && touch -d "2026-02-02 10:00:00" /root/demo/rsync/attr/q.txt && ln -s p.txt /root/demo/rsync/attr/link.txt && printf target > /root/demo/rsync/attr/real.txt && ln /root/demo/rsync/attr/real.txt /root/demo/rsync/attr/hard2.txt && printf rootfile > /root/demo/rsync/attr/ro.txt && mknod /root/demo/rsync/attr/dchar c 1 3 && chown -R 1012:1012 /root/demo/rsync/attr && chown 0:0 /root/demo/rsync/attr/ro.txt && ls -l /root/demo/rsync/attr
total 20
crw-r--r-- 1 tester u4a-ssh 1, 3 Oct 15 20:39 dchar
-rw-r--r-- 2 tester u4a-ssh 6 Oct 15 20:39 hard2.txt
lrwxrwxrwx 1 tester u4a-ssh 5 Oct 15 20:39 link.txt -> p.txt
-rw-r----- 1 tester u4a-ssh 4 Feb 2 2026 p.txt
-rw-r--r-- 1 tester u4a-ssh 1 Feb 2 2026 q.txt
-rw-r--r-- 2 tester u4a-ssh 6 Oct 15 20:39 real.txt
-rw-r--r-- 1 root root 8 Oct 15 20:39 ro.txt
只用 -rv(不含 -l)同步:符号链接被直接跳过:
rm -rf /root/demo/rsync/dst-rl && rsync -rv /root/demo/rsync/attr/ /root/demo/rsync/dst-rl/; echo rc=$?
sending incremental file list
created directory /root/demo/rsync/dst-rl
skipping non-regular file "dchar"
skipping non-regular file "link.txt"
./
hard2.txt
p.txt
q.txt
real.txt
ro.txt
sent 433 bytes received 239 bytes 1,344.00 bytes/sec
total size is 30 speedup is 0.04
rc=0
加上 -l 重来:符号链接按链接本身复制(清单里显示 link.txt -> p.txt):
rm -rf /root/demo/rsync/dst-rl && rsync -rlv /root/demo/rsync/attr/ /root/demo/rsync/dst-rl/; echo rc=$?
sending incremental file list
created directory /root/demo/rsync/dst-rl
skipping non-regular file "dchar"
./
hard2.txt
link.txt -> p.txt
p.txt
q.txt
real.txt
ro.txt
sent 442 bytes received 201 bytes 1,286.00 bytes/sec
total size is 30 speedup is 0.05
rc=0
ls -l /root/demo/rsync/dst-rl
total 20
-rw-r--r-- 1 root root 6 Oct 15 20:39 hard2.txt
lrwxrwxrwx 1 root root 5 Oct 15 20:39 link.txt -> p.txt
-rw-r----- 1 root root 4 Oct 15 20:39 p.txt
-rw-r--r-- 1 root root 1 Oct 15 20:39 q.txt
-rw-r--r-- 1 root root 6 Oct 15 20:39 real.txt
-rw-r--r-- 1 root root 8 Oct 15 20:39 ro.txt
对照组 -L(--copy-links):把符号链接"解引用"成它指向的普通文件再传:
rm -rf /root/demo/rsync/dst-L && rsync -rvL /root/demo/rsync/attr/ /root/demo/rsync/dst-L/; echo rc=$?
sending incremental file list
created directory /root/demo/rsync/dst-L
skipping non-regular file "dchar"
./
hard2.txt
link.txt
p.txt
q.txt
real.txt
ro.txt
sent 482 bytes received 216 bytes 1,396.00 bytes/sec
total size is 29 speedup is 0.04
rc=0
ls -l /root/demo/rsync/dst-L
total 24
-rw-r--r-- 1 root root 6 Oct 15 20:39 hard2.txt
-rw-r----- 1 root root 4 Oct 15 20:39 link.txt
-rw-r----- 1 root root 4 Oct 15 20:39 p.txt
-rw-r--r-- 1 root root 1 Oct 15 20:39 q.txt
-rw-r--r-- 1 root root 6 Oct 15 20:39 real.txt
-rw-r--r-- 1 root root 8 Oct 15 20:39 ro.txt
验证结论:
- 不带
-l:link.txt出现在skipping non-regular file清单里,目标端没有这个链接;-l让链接原样复制(lrwxrwxrwx ... link.txt -> p.txt); -L则跟踪链接取到目标文件内容:目标端link.txt变成-rw-r-----的普通文件、4 字节(与p.txt内容一致);-a内含-l,日常用-a时符号链接默认按链接保留;要把链接"实体化"才需要显式换-L。
3.11 -p 保留权限
无 -p 时新文件的权限由源权限与接收端 umask 共同决定——源端 umask 不参与,接收端 umask 才是关键。本地演示中 640 的文件经本机 umask 022 处理后仍是 640(看不出差异,见 3.10 节 dst-rl 的 ls -l);而远端 tester 的 umask 是 002,会抹掉 666 中的"其他用户写位"(666→664),所以演示文件特意用 666 推给远端。准备并首次同步(-rv,不带 -p):
printf pm > /root/demo/rsync/attr/pm.txt && chmod 666 /root/demo/rsync/attr/pm.txt && touch -d "2026-02-02 10:00:00" /root/demo/rsync/attr/pm.txt && chown 1012:1012 /root/demo/rsync/attr/pm.txt && stat -c %a /root/demo/rsync/attr/pm.txt
666
sshpass -p tester123 rsync -rv /root/demo/rsync/attr/pm.txt tester@127.0.0.1:rsync-attr/; echo rc=$?
sending incremental file list
created directory rsync-attr
pm.txt
sent 87 bytes received 68 bytes 310.00 bytes/sec
total size is 2 speedup is 0.01
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "stat -c %a ~/rsync-attr/pm.txt"
664
带 -p 重传(覆盖同一文件):
sshpass -p tester123 rsync -rpv /root/demo/rsync/attr/pm.txt tester@127.0.0.1:rsync-attr/; echo rc=$?
sending incremental file list
pm.txt
sent 85 bytes received 41 bytes 252.00 bytes/sec
total size is 2 speedup is 0.02
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "stat -c %a ~/rsync-attr/pm.txt"
666
验证结论:
- 无
-p:源666到远端变成664——接收端 umask 002 抹掉了其他用户写位; - 有
-p:666原样到达; 664恰好说明无-p时 rsync 并不会"拍脑袋"赋权,而是把源权限交由接收端 umask 处理——但结果不可控,属性敏感的同步一律用-a(内含-p)。
3.12 -t 保留修改时间
用 q.txt(修改时间 2026-02-02)对照。先无 -t 同步,远端时间变成接收时刻:
sshpass -p tester123 rsync -rv /root/demo/rsync/attr/q.txt tester@127.0.0.1:rsync-attr/; echo rc=$?
sending incremental file list
q.txt
sent 85 bytes received 35 bytes 80.00 bytes/sec
total size is 1 speedup is 0.01
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "stat -c %y ~/rsync-attr/q.txt"
2026-10-15 20:43:22.120173862 +0000
加 -t 重传:
sshpass -p tester123 rsync -rvt /root/demo/rsync/attr/q.txt tester@127.0.0.1:rsync-attr/; echo rc=$?
sending incremental file list
q.txt
sent 84 bytes received 41 bytes 250.00 bytes/sec
total size is 1 speedup is 0.01
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "stat -c %y ~/rsync-attr/q.txt"
2026-02-02 10:00:00.000000000 +0000
验证结论:
- 无
-t:远端 mtime 是"接收时刻";-t把源端 mtime 原样带过去(纳秒级精度保留); -t不只是"为了好看"——3.4 节的 quick check 靠"大小 + mtime"判定跳过,丢了-t,下一次同步所有文件都会因时间不符而重传。这是-a内含-t的根本原因。
3.13 -o 与 -g:属主与属组
ro.txt 是演示目录里唯一的 root 属主文件(-rw-r--r-- 1 root root,见 3.10 节的 ls -l)。help 对这两个参数的说明(逐行照抄):
--owner, -o preserve owner (super-user only)
--group, -g preserve group
用 -av(内含 -o -g)把 root 属主文件推给普通用户 tester:
sshpass -p tester123 rsync -av /root/demo/rsync/attr/ro.txt tester@127.0.0.1:rsync-attr/; echo rc=$?
sending incremental file list
ro.txt
sent 111 bytes received 35 bytes 292.00 bytes/sec
total size is 8 speedup is 0.05
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "ls -l ~/rsync-attr/ro.txt"
-rw-r--r-- 1 tester tester 8 Oct 15 20:39 /home/tester/rsync-attr/ro.txt
对照组:不带 -o/-g 的普通同步(上一轮 -a 已把文件同步过去且 mtime 一致,本轮 quick check 直接跳过、清单为空):
sshpass -p tester123 rsync -rv /root/demo/rsync/attr/ro.txt tester@127.0.0.1:rsync-attr/; echo rc=$?
sending incremental file list
sent 44 bytes received 12 bytes 112.00 bytes/sec
total size is 8 speedup is 0.14
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "ls -l ~/rsync-attr/ro.txt; rm -f ~/rsync-attr/ro.txt"
-rw-r--r-- 1 tester tester 8 Oct 15 20:39 /home/tester/rsync-attr/ro.txt
验证结论:
- 两种方式的最终结果完全一样:无告警、
rc=0、远端文件属主都是接收用户tester——rsync 对非 root 接收端是静默忽略属主/属组保留,连警告都不给; - 对照本地 root 接收的行为(3.10 节
dst-rl与dst-dev的ls -l):-a同步时属主被保留为1012(tester)、ro.txt保留为 root——接收端有特权时-o/-g才真正生效; - 这正是本文演示数据统一
chown 1012:1012的原因:让源属主与远端接收账号一致,-a的属主保留就不会产生任何差异;需要严格属主同步时,应让接收端以 root 运行(或了解-o静默失效的事实)。
3.14 -D:设备与特殊文件
-D 是 --devices --specials 的合写(help 原文两行):
--devices preserve device files (super-user only)
--specials preserve special files
-D same as --devices --specials
演示目录里的 dchar 是字符设备(crw-r--r-- 1, 3,即 /dev/null 同款)。先看 -a(内含 -D)的本地同步:
rm -rf /root/demo/rsync/dst-dev && rsync -av /root/demo/rsync/attr/ /root/demo/rsync/dst-dev/; echo rc=$?
sending incremental file list
created directory /root/demo/rsync/dst-dev
./
dchar
hard2.txt
link.txt -> p.txt
p.txt
q.txt
real.txt
ro.txt
sent 482 bytes received 167 bytes 1,298.00 bytes/sec
total size is 30 speedup is 0.05
rc=0
ls -l /root/demo/rsync/dst-dev
total 20
crw-r--r-- 1 tester u4a-ssh 1, 3 Oct 15 20:39 dchar
-rw-r--r-- 1 tester u4a-ssh 6 Oct 15 20:39 hard2.txt
lrwxrwxrwx 1 tester u4a-ssh 5 Oct 15 20:39 link.txt -> p.txt
-rw-r----- 1 tester u4a-ssh 4 Feb 2 2026 p.txt
-rw-r--r-- 1 tester u4a-ssh 1 Feb 2 2026 q.txt
-rw-r--r-- 1 tester u4a-ssh 6 Oct 15 20:39 real.txt
-rw-r--r-- 1 root root 8 Oct 15 20:39 ro.txt
显式 --no-D 关掉它,同样推给远端普通用户:
rm -rf /root/demo/rsync/dst-dev2 && sshpass -p tester123 rsync -av --no-D /root/demo/rsync/attr/ tester@127.0.0.1:rsync-dev2/; echo rc=$?
sending incremental file list
created directory rsync-dev2
skipping non-regular file "dchar"
./
hard2.txt
link.txt -> p.txt
pm.txt
p.txt
q.txt
real.txt
ro.txt
sent 537 bytes received 207 bytes 1,488.00 bytes/sec
total size is 32 speedup is 0.04
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "ls -l ~/rsync-dev2"
total 24
-rw-r--r-- 1 tester tester 6 Oct 15 20:39 hard2.txt
lrwxrwxrwx 1 tester tester 5 Oct 15 20:39 link.txt -> p.txt
-rw-rw-rw- 1 tester tester 2 Feb 2 2026 pm.txt
-rw-r----- 1 tester tester 4 Feb 2 2026 p.txt
-rw-r--r-- 1 tester tester 1 Feb 2 2026 q.txt
-rw-r--r-- 1 tester tester 6 Oct 15 20:39 real.txt
-rw-r--r-- 1 tester tester 8 Oct 15 20:39 ro.txt
验证结论:
-a内含-D:本地同步把dchar复制成了设备节点本身(crw-r--r-- 1, 3,主/次设备号原样),不是它的内容;--no-D关闭后,设备文件出现在skipping non-regular file清单里,远端ls中没有dchar;- 注意两个限制:
--devices标注 super-user only——接收端是普通用户时设备节点同样创建不了(本实验远端普通用户配合--no-D观察);rsync 复制的是设备节点,要复制设备里的数据流需要--copy-devices。
3.15 -H 保留硬链接
hard2.txt 与 real.txt 是同一 inode 的两个名字。先看 -a(不含 -H):
rm -rf /root/demo/rsync/dst-noH && rsync -av /root/demo/rsync/attr/real.txt /root/demo/rsync/attr/hard2.txt /root/demo/rsync/dst-noH/; echo rc=$?
sending incremental file list
created directory /root/demo/rsync/dst-noH
hard2.txt
real.txt
sent 187 bytes received 101 bytes 576.00 bytes/sec
total size is 12 speedup is 0.04
rc=0
ls -li /root/demo/rsync/dst-noH
total 8
30584595 -rw-r--r-- 1 tester u4a-ssh 6 Oct 15 20:39 hard2.txt
30584596 -rw-r--r-- 1 tester u4a-ssh 6 Oct 15 20:39 real.txt
加 -H 重来:
rm -rf /root/demo/rsync/dst-H && rsync -avH /root/demo/rsync/attr/real.txt /root/demo/rsync/attr/hard2.txt /root/demo/rsync/dst-H/; echo rc=$?
sending incremental file list
created directory /root/demo/rsync/dst-H
real.txt
hard2.txt => real.txt
sent 147 bytes received 96 bytes 486.00 bytes/sec
total size is 12 speedup is 0.05
rc=0
ls -li /root/demo/rsync/dst-H
total 8
30584598 -rw-r--r-- 2 tester u4a-ssh 6 Oct 15 20:39 hard2.txt
30584598 -rw-r--r-- 2 tester u4a-ssh 6 Oct 15 20:39 real.txt
验证结论:
- 无
-H:两个文件被当作独立文件传输,目标端 inode 不同(30584595 / 30584596),链接数为 1——硬链接关系丢失; -H:清单里的hard2.txt => real.txt用=>标注"这个文件以硬链接方式指向已传输的 real.txt",目标端两文件共享 inode 30584598、链接数 2;-a明确不含-H(3.9 节 help 原文),依赖硬链接的目录树(如增量快照库)同步时必须显式补-H。
3.16 --exclude 与 --include 过滤
准备四个文件(两个 .log、两个普通文件)并清空远端:
sshpass -p tester123 ssh tester@127.0.0.1 "rm -rf ~/rsync-ex ~/rsync-ex2 ~/rsync-dev2"
按模式排除所有 .log:
sshpass -p tester123 rsync -av --exclude "*.log" /root/demo/rsync/ex/ tester@127.0.0.1:rsync-ex/; echo rc=$?
sending incremental file list
created directory rsync-ex
./
code.txt
readme.md
sent 195 bytes received 88 bytes 188.67 bytes/sec
total size is 4 speedup is 0.01
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "ls ~/rsync-ex"
code.txt
readme.md
--include 与 --exclude 组合:先声明例外、再一刀排除,预演后真跑:
sshpass -p tester123 rsync -avn --include "code.txt" --exclude "*" /root/demo/rsync/ex/ tester@127.0.0.1:rsync-ex2/; echo rc=$?
sending incremental file list
created directory rsync-ex2
./
code.txt
sent 89 bytes received 54 bytes 286.00 bytes/sec
total size is 2 speedup is 0.01 (DRY RUN)
rc=0
sshpass -p tester123 rsync -av --include "code.txt" --exclude "*" /root/demo/rsync/ex/ tester@127.0.0.1:rsync-ex2/; echo rc=$?
sending incremental file list
created directory rsync-ex2
./
code.txt
sent 131 bytes received 70 bytes 402.00 bytes/sec
total size is 2 speedup is 0.01
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "ls ~/rsync-ex2"
code.txt
验证结论:
--exclude "*.log"让两个日志文件全程未出现在传输清单中;- 过滤规则自上而下、首个匹配生效:
--include "code.txt"必须写在--exclude "*"之前,顺序颠倒会被*先截住,什么都传不出去; --include的含义是"不排除"(help 原文don't exclude files matching PATTERN),它自己不触发传输,只是给后面的 exclude 规则开洞。
3.17 -z 压缩传输
-z 在网络传输层压缩文件数据。准备一个 1 MiB 的高重复文本,对同一文件做两次同步对比流量:
yes "the quick brown fox jumps over the lazy dog" | head -c 1048576 > /root/demo/rsync/big.txt && chown 1012:1012 /root/demo/rsync/big.txt && wc -c /root/demo/rsync/big.txt
1048576 /root/demo/rsync/big.txt
sshpass -p tester123 ssh tester@127.0.0.1 "rm -f ~/big.txt"
sshpass -p tester123 rsync -av /root/demo/rsync/big.txt tester@127.0.0.1:big.txt 2>&1 | grep -E "sent|total size"
sent 1,048,935 bytes received 35 bytes 699,313.33 bytes/sec
total size is 1,048,576 speedup is 1.00
sshpass -p tester123 ssh tester@127.0.0.1 "rm -f ~/big.txt"
sshpass -p tester123 rsync -avz /root/demo/rsync/big.txt tester@127.0.0.1:big.txt 2>&1 | grep -E "sent|total size"
sent 253 bytes received 35 bytes 576.00 bytes/sec
total size is 1,048,576 speedup is 3,640.89
验证结论:
- 同一个 1 MiB 文件:无
-z实发 1,048,935 字节,加-z只发 253 字节,speedup 从 1.00 跳到 3,640.89——重复文本被压缩到近乎没有; - 极值场景是"高度重复的文本/日志";对已压缩数据(jpg、mp4、gz)
-z几乎无收益还白耗 CPU,可按后缀跳过压缩(help 有--skip-compress=LIST); - 本版本可用算法见 2.3 节
Compress list: zstd lz4 zlibx zlib none,默认按两端共同支持的较优算法选择,--compress-choice可指定。
3.18 -e 指定远程 shell 与端口
-e 指定承载传输的远程 shell 程序(help 原文 --rsh=COMMAND, -e specify the remote shell to use),默认 ssh。向监听 2222 端口的 sshd 实例推送:
sshpass -p tester123 ssh tester@127.0.0.1 "rm -rf ~/rsync-2222"
sshpass -p tester123 rsync -av -e "ssh -p 2222" /root/demo/rsync/p22.txt tester@127.0.0.1:rsync-2222/; echo rc=$?
sending incremental file list
created directory rsync-2222
p22.txt
sent 121 bytes received 68 bytes 126.00 bytes/sec
total size is 10 speedup is 0.05
rc=0
sshpass -p tester123 ssh -p 2222 tester@127.0.0.1 "cat ~/rsync-2222/p22.txt"
port-test
指向一个没人监听的端口 2299,观察错误形态:
sshpass -p tester123 rsync -av -e "ssh -p 2299" /root/demo/rsync/p22.txt tester@127.0.0.1:rsync-bad/ 2>&1; echo rc=$?
ssh: connect to host 127.0.0.1 port 2299: Connection refused
rsync: connection unexpectedly closed (0 bytes received so far) [sender]
rsync error: unexplained error (code 255) at io.c(232) [sender=3.2.7]
rc=255
验证结论:
-e "ssh -p 2222"让 rsync 改经 2222 端口的 sshd 完成传输,文件正常到达;引号必须有——-e的值是一个整体命令;- 底层 ssh 连不上时,报错先来自 ssh(
Connection refused),rsync 把它包装成connection unexpectedly closed+code 255; -e还能透传任意 ssh 选项,如-e "ssh -i /root/.ssh/id_ed25519"指定私钥;与 scp 的大写-P不同,rsync 没有专属端口参数,端口永远写在这里。
3.19 -P:进度显示与 --partial 断点续传
-P 是两个参数的合写(help 原文):
--partial keep partially transferred files
--progress show progress during transfer
-P same as --partial --progress
rsync 的进度刷新依赖终端回显,而认证环节用 sshpass 会干扰输出捕获,所以本节先建立 ssh 免交互通道(ControlMaster 主控连接),再让 rsync 复用它:
rm -f /tmp/cm-rsync && sshpass -p tester123 ssh -o ControlMaster=auto -o ControlPath=/tmp/cm-rsync -o ControlPersist=5m -fN tester@127.0.0.1 && echo master-up
master-up
准备 5 MB 文件,用 script 包一层伪终端抓取进度帧,--bwlimit=1000 把速率压到 1000 KB/s 以便观察(tr 把进度条回车刷新转成逐行输出):
dd if=/dev/zero of=/root/demo/rsync/prog.bin bs=1M count=5 2>/dev/null; chown 1012:1012 /root/demo/rsync/prog.bin; ls -l /root/demo/rsync/prog.bin
-rw-r--r-- 1 tester u4a-ssh 5242880 Oct 15 20:44 /root/demo/rsync/prog.bin
sshpass -p tester123 ssh tester@127.0.0.1 "rm -f ~/prog.bin"
script -qec "rsync -P --bwlimit=1000 -e \"ssh -o ControlPath=/tmp/cm-rsync\" /root/demo/rsync/prog.bin tester@127.0.0.1:prog.bin" /dev/null | tr "\r" "\n" | grep -a "%" | head -3
32,768 0% 0.00kB/s 0:00:00
1,081,344 20% 1000.98kB/s 0:00:04
2,129,920 40% 1000.49kB/s 0:00:03
sshpass -p tester123 ssh tester@127.0.0.1 "rm -f ~/prog.bin"
script -qec "rsync -P --bwlimit=1000 -e \"ssh -o ControlPath=/tmp/cm-rsync\" /root/demo/rsync/prog.bin tester@127.0.0.1:prog.bin" /dev/null | tr "\r" "\n" | grep -a "%" | tail -3
3,178,496 60% 1000.65kB/s 0:00:02
4,227,072 80% 1000.49kB/s 0:00:01
5,242,880 100% 1019.43kB/s 0:00:04 (xfr#1, to-chk=0/1)
传输完成后远端校验大小并清理:
sshpass -p tester123 ssh tester@127.0.0.1 "ls -l ~/prog.bin; rm -f ~/prog.bin"
-rw-r--r-- 1 tester tester 5242880 Oct 15 20:44 /home/tester/prog.bin
进度行四列自左向右是:已传输字节、百分比、当前速率、剩余时间估计;完成行末尾的 (xfr#1, to-chk=0/1) 表示第 1 个文件传输完毕、待检查项 0/1。
-P 的另一半 --partial 是断点续传:timeout 1 模拟传输 1 秒后被中断(退出码 124):
sshpass -p tester123 ssh tester@127.0.0.1 "rm -f ~/part.bin"
timeout 1 rsync -P --bwlimit=1000 -e "ssh -o ControlPath=/tmp/cm-rsync" /root/demo/rsync/prog.bin tester@127.0.0.1:part.bin > /tmp/partout 2>&1; echo rc=$?
rc=124
有 --partial 时,已传部分保留在远端目标名下:
sshpass -p tester123 ssh tester@127.0.0.1 "ls -l ~/part.bin"
-rw-r--r-- 1 tester tester 1048576 Oct 15 20:44 /home/tester/part.bin
重新执行同样的 rsync 命令续传(只看流量统计):
sshpass -p tester123 rsync -av --bwlimit=1000 -e "ssh -o ControlPath=/tmp/cm-rsync" /root/demo/rsync/prog.bin tester@127.0.0.1:part.bin 2>&1 | grep -E "sent|total size"
sent 20,587 bytes received 6,179 bytes 53,532.00 bytes/sec
total size is 5,242,880 speedup is 195.88
sshpass -p tester123 ssh tester@127.0.0.1 "ls -l ~/part.bin"
-rw-r--r-- 1 tester tester 5242880 Oct 15 20:44 /home/tester/part.bin
验证结论:
- 中断后远端保留
part.bin1 MB(约 1 秒 × 1000 KB/s);重新执行同一命令后文件补全到 5,242,880 字节; - 续传只发 20,587 字节——rsync 的 delta-transfer 算法把远端已有的部分(以及与已有内容重复的块)全部识别为引用,只发送差异;本例文件内容全为零字节,块校验几乎全命中,剩余部分也被复用,实际流出的只有协议开销;内容随机的文件续传量约为剩余字节数;
- 没有
--partial时中断会丢弃临时文件、一切从头再来,所以大文件传输建议常备-P。
3.20 --bwlimit:限速传输
--bwlimit=RATE 限制 socket 带宽,单位是 KB/s。对同一个 5 MB 文件测三组:rsync 限速 125 KB/s、scp 限速 1000(注意 scp 的单位是 Kbit/s,1000 Kbit/s = 125 KB/s)、rsync 不限速:
dd if=/dev/zero of=/root/demo/rsync/bw.bin bs=1M count=5 2>/dev/null; chown 1012:1012 /root/demo/rsync/bw.bin; ls -l /root/demo/rsync/bw.bin
-rw-r--r-- 1 tester u4a-ssh 5242880 Oct 15 20:44 /root/demo/rsync/bw.bin
sshpass -p tester123 ssh tester@127.0.0.1 "rm -f ~/bw.bin"
time script -qec "sshpass -p tester123 rsync -av --bwlimit=125 /root/demo/rsync/bw.bin tester@127.0.0.1:bw.bin" /dev/null
sending incremental file list
bw.bin
sent 5,244,262 bytes received 35 bytes 123,395.22 bytes/sec
total size is 5,242,880 speedup is 1.00
real 0m41.157s
user 0m0.022s
sys 0m0.044s
sshpass -p tester123 ssh tester@127.0.0.1 "rm -f ~/bw.bin"
time script -qec "sshpass -p tester123 scp -l 1000 /root/demo/rsync/bw.bin tester@127.0.0.1:bw.bin" /dev/null
real 0m41.163s
user 0m0.035s
sys 0m0.028s
sshpass -p tester123 ssh tester@127.0.0.1 "rm -f ~/bw.bin"
time script -qec "sshpass -p tester123 rsync -av /root/demo/rsync/bw.bin tester@127.0.0.1:bw.bin" /dev/null
sending incremental file list
bw.bin
sent 5,244,262 bytes received 35 bytes 10,488,594.00 bytes/sec
total size is 5,242,880 speedup is 1.00
real 0m0.220s
user 0m0.013s
sys 0m0.011s
验证结论:
--bwlimit=125(125 KB/s)传 5 MB 耗时 41.157s;scp -l 1000(1000 Kbit/s ÷ 8 = 125 KB/s)耗时 41.163s——两者几乎秒秒不差,证实 rsync 单位是 KB/s、scp 单位是 Kbit/s,相差 8 倍;- 不限速时同样的文件 0.220s 完成(速率约 10 MB/s);
- 给生产环境做同步前,先用小文件换算好限速值:
Kbit/s ÷ 8 = KB/s,把scp -l的旧经验平移到 rsync 时切记除以 8。
3.21 错误形态与退出码速查
把实测遇到的错误形态集中列出,每一条都是真实回显。
形态一:源路径不存在——先正常输出文件清单结构,最后以 code 23 收尾:
rsync -av /root/demo/rsync/no-such /tmp/rsync-x 2>&1; echo rc=$?
sending incremental file list
rsync: [sender] link_stat "/root/demo/rsync/no-such" failed: No such file or directory (2)
sent 19 bytes received 12 bytes 62.00 bytes/sec
total size is 0 speedup is 0.00
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1356) [sender=3.2.7]
rc=23
形态二:源与目标都是远程路径——rsync 不支持第三方对传(scp 的 -3 才是干这个的):
rsync -av tester@127.0.0.1:~/rsync-demo/x.txt tester1@127.0.0.1:~/y.txt 2>&1; echo rc=$?
The source and destination cannot both be remote.
rsync error: syntax or usage error (code 1) at main.c(1446) [Receiver=3.2.7]
rc=1
形态三:远端没有 rsync——用 --rsync-path 指向一个不存在的程序模拟(效果与远端未安装 rsync 相同):
sshpass -p tester123 rsync -av --rsync-path=/no/such/rsync /root/demo/rsync/p22.txt tester@127.0.0.1:rsync-bad/ 2>&1; echo rc=$?
bash: line 1: /no/such/rsync: No such file or directory
rsync: connection unexpectedly closed (0 bytes received so far) [sender]
rsync error: error in rsync protocol data stream (code 12) at io.c(232) [sender=3.2.7]
rc=12
形态四:认证失败——rsync 内部调用 ssh,若 ssh 拿不到密码(如忘了用 sshpass 包住 rsync 整体),报错经过 rsync 包装后只有 code 255:
rsync -av /root/demo/rsync/src/ tester@127.0.0.1:rsync-demo/; echo rc=$?
Permission denied, please try again.
Permission denied, please try again.
tester@127.0.0.1: Permission denied (publickey,password).
rsync: connection unexpectedly closed (0 bytes received so far) [sender]
rsync error: unexplained error (code 255) at io.c(232) [sender=3.2.7]
rc=255
退出码速查(均为本环境实测取值):
| 退出码 | 含义 | 实测触发场景 |
|---|---|---|
| 0 | 成功(注意 --no-r 跳过目录也是 0) | 3.1、3.9 |
| 1 | 语法或用法错误 | 双 remote(本节形态二) |
| 12 | 协议数据流错误 | 远端无 rsync / 程序路径错(形态三) |
| 23 | 部分文件或属性未传输 | 源路径不存在(形态一) |
| 255 | ssh 连接层失败("unexplained error") | 连接拒绝、认证失败(形态四、3.18) |
💡 说明:
code 255对应 ssh 自身的失败,rsync 只能报告"连接意外关闭";排障顺序是先看 ssh 原始报错行(Connection refused / Permission denied / Host key verification failed),再回到 rsync 层。
四、实战示例
4.1 网站目录安全备份:预演 → 真跑 → 增量
场景:把本地 site/ 备份到远端,要求排除日志目录、清掉远端遗留的废弃文件。本地准备数据,远端模拟"已有旧备份"的状态(含一个废弃文件 obsolete.txt 和旧的 logs/ 目录):
rm -rf /root/demo/rsync && mkdir -p /root/demo/rsync/site/logs && printf "<html>home</html>\n" > /root/demo/rsync/site/index.html && printf "console.log(1)\n" > /root/demo/rsync/site/app.js && printf "GET / 200\n" > /root/demo/rsync/site/logs/access.log && chown -R 1012:1012 /root/demo/rsync/site && find /root/demo/rsync/site | sort
/root/demo/rsync/site
/root/demo/rsync/site/app.js
/root/demo/rsync/site/index.html
/root/demo/rsync/site/logs
/root/demo/rsync/site/logs/access.log
sshpass -p tester123 ssh tester@127.0.0.1 "rm -rf ~/site && mkdir -p ~/site/logs && touch ~/site/obsolete.txt && echo legacy-prepared && ls ~/site"
legacy-prepared
logs
obsolete.txt
第一步,-n 预演完整命令(-av --exclude "logs/" --delete),检查将发生的一切:
sshpass -p tester123 rsync -avn --exclude "logs/" --delete /root/demo/rsync/site/ tester@127.0.0.1:site/; echo rc=$?
sending incremental file list
deleting obsolete.txt
./
app.js
index.html
sent 124 bytes received 44 bytes 336.00 bytes/sec
total size is 33 speedup is 0.20 (DRY RUN)
rc=0
第二步,去掉 -n 真正执行:
sshpass -p tester123 rsync -av --exclude "logs/" --delete /root/demo/rsync/site/ tester@127.0.0.1:site/; echo rc=$?
sending incremental file list
deleting obsolete.txt
./
app.js
index.html
sent 241 bytes received 80 bytes 214.00 bytes/sec
total size is 33 speedup is 0.10
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "find ~/site | sort"
/home/tester/site
/home/tester/site/app.js
/home/tester/site/index.html
/home/tester/site/logs
第三步,网站更新(改一个文件、新增一个文件)后再次执行同一条命令,实现日常增量:
printf "<html>home-v2</html>\n" > /root/demo/rsync/site/index.html && printf "<html>about</html>\n" > /root/demo/rsync/site/about.html && chown 1012:1012 /root/demo/rsync/site/about.html && sshpass -p tester123 rsync -av --exclude "logs/" --delete /root/demo/rsync/site/ tester@127.0.0.1:site/; echo rc=$?
sending incremental file list
./
about.html
index.html
sent 277 bytes received 70 bytes 694.00 bytes/sec
total size is 55 speedup is 0.16
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "cat ~/site/index.html; ls ~/site"
<html>home-v2</html>
about.html
app.js
index.html
logs
sshpass -p tester123 ssh tester@127.0.0.1 "rm -rf ~/site; echo remote-cleaned"
remote-cleaned
rm -rf /root/demo/rsync; echo local-cleaned
local-cleaned
要点复盘:
- 预演与真跑的文件清单逐行一致,
(DRY RUN)标记是唯一的区别——预演没有副作用; deleting obsolete.txt说明--delete清掉了远端"源里没有"的文件;而远端的logs/目录在--delete之后依然存在——--exclude的模式在两端同时生效,源端被排除的logs/不会上传,远端的logs/也不会被--delete删除(要连远端的一起删需显式加--delete-excluded);- 第三步只传
about.html与index.html,增量语义让"每天跑一遍"的成本趋近于零。
4.2 大文件传输的进度监控与断点续传
跨机房传大文件时,把 3.19 节的要素组合成标准流程:先建立免交互通道,再带 -P 启动传输(进度四列随时可看),中断后直接重跑同一条命令续传:
rm -f /tmp/cm-rsync && sshpass -p tester123 ssh -o ControlMaster=auto -o ControlPath=/tmp/cm-rsync -o ControlPersist=5m -fN tester@127.0.0.1 && echo master-up
script -qec "rsync -P --bwlimit=1000 -e \"ssh -o ControlPath=/tmp/cm-rsync\" /root/demo/rsync/prog.bin tester@127.0.0.1:prog.bin" /dev/null | tr "\r" "\n" | grep -a "%" | tail -3
3,178,496 60% 1000.65kB/s 0:00:02
4,227,072 80% 1000.49kB/s 0:00:01
5,242,880 100% 1019.43kB/s 0:00:04 (xfr#1, to-chk=0/1)
中途被断(timeout 1,退出码 124)后重跑,续传只发 20,587 字节、文件补全到 5,242,880 字节(完整过程见 3.19 节)。用完关闭免交互通道:
ssh -o ControlPath=/tmp/cm-rsync -O exit tester@127.0.0.1; echo master-closed
Exit request sent.
master-closed
要点复盘:
-P(=--partial --progress)在大文件场景是默认该带的参数:进度可见、中断可续;--bwlimit按对端网络承受能力设置(单位 KB/s,见 3.20 节的换算对照);- 传输完成后可以用
-c做一次内容校验复核(3.8 节),再清理免交互通道。
五、注意事项与常见问题
Q1:源目录尾斜杠写反了,目标多套一层目录,怎么办?
这是 rsync 第一大误用点(3.2 节实测对照)。补救方式是删掉错误层级重新同步,或把源路径补上尾斜杠再跑一次;预防手段是把"源带尾斜杠"固化为脚本写法,并且任何新同步命令先过一遍 -n 预演看文件清单。
Q2:--delete 会不会误删目标端文件?怎么防护?
会——--delete 删除目标端"源里没有"的一切,源端路径写错时破坏力很大。三道防线:一是 -n 预演(3.6 节,deleting ... 行会提前列出所有删除动作);二是 --exclude 模式对目标端同样有删除保护(4.1 节实证);三是对重要目标用 --backup/--backup-dir 让被删改的文件先进备份区再删(参数见 help,本文未实测展开)。
Q3:rsync 报 Permission denied (publickey,password),最后是 code 255?
rsync 的 ssh 由 rsync 内部调用,认证喂不进去就是这个形态(3.21 节形态四)。非交互脚本要写 sshpass -p 密码 rsync ...——让 sshpass 包住 rsync 整体,而不是以为 rsync 会自己解决认证;长期任务建议配置密钥认证或 ControlMaster 免交互通道(3.19 节用法)。
Q4:明明加了 -a,目标端属主还是不对(或者属主根本没变)?
-a 内含的 -o/-g 只在接收端有特权时生效(help 原文 preserve owner (super-user only));普通用户接收时属主/属组被静默忽略,连警告都没有(3.13 节实测)。需要严格属主同步,让接收端以 root 运行;跨系统 uid 不一致时还可以用 --numeric-ids、--usermap、--chown 调整映射。
Q5:-u 和 -c 都能让同步"少传",实际选哪个?
默认的 quick check(大小 + mtime)覆盖绝大多数场景;-u 在此基础上保护目标端较新的文件,适合"源可能落后于目标"的合并场景,但完全信任时间戳(3.7 节);-c 逐字节算校验和,能识破"同大小同时间不同内容",代价是读全部文件(3.8 节)。时钟不可靠的环境选 -c,其余用默认或 -u。
Q6:两端 rsync 版本不一致,或者远端根本没装 rsync?
rsync 必须两端都安装。远端没有 rsync 可执行文件时,传输直接失败并报 code 12(3.21 节形态三);版本不一致时按较低一方的 protocol 版本通信,功能可能降级。排障第一步 rsync --version 两端各报一次(2.3 节)。
Q7:--bwlimit 和 scp -l 的数值能直接照抄吗?
不能。rsync --bwlimit 单位是 KB/s,scp -l 单位是 Kbit/s,相差 8 倍——同一 5 MB 文件在 --bwlimit=125 与 scp -l 1000 下耗时都是约 41.16s(3.20 节实测),这是把 scp 限速经验迁移到 rsync 时最容易踩的坑。
Q8:传输到一半断了,数据要重来吗?
带了 -P(--partial --progress)就不用:已传部分保留在远端,重跑同一条命令即续传(3.19 节实测,续传仅发 20,587 字节)。长距离不稳定链路再加 --timeout(单位秒,I/O 超时自动断开重试)。
六、参考链接
- 本地手册:
man rsync(完整参数说明)、man rsyncd.conf(守护进程模式配置) - rsync 官方网站:https://rsync.samba.org/(版本发布与更新说明)
- rsync 手册页在线版:https://download.samba.org/pub/rsync/rsync.html
- 同一实测环境下的关联篇目:《ssh 命令详解与参数实测(Ubuntu 22.04)》《scp 命令详解与参数实测(Ubuntu 22.04)》(OpenSSH 8.9p1 环境与传输工具选型对照)