1. 从一个最日常的操作说起
如果你刚接触 Linux,或者已经在用但没深究过,大概率每天都离不开复制文件。从最基础的cp a.txt b.txt,到跨机器同步几百 GB 数据,表面上看都是一个“把数据从源头弄到目标地”的动作,但底层涉及的权限、软链接、硬链接、稀疏文件、时间戳、ACL、xattr、校验和、网络带宽、断点续传……每一个节点都能把数据搞坏,也能把你搞疯。
这篇文章我只聊一件事:Linux 系统下的复制操作。我会从命令用法一路拆到内核拷贝路径,再给出一整套适合服务器运维、开发部署、数据备份、大规模迁移的实操方案,最后把我自己踩过的坑和总结的排查步骤一次讲透。无论你是刚入行的运维、写代码的开发者,还是自己折腾 NAS 和 Linux 主机的玩家,这篇内容应该都能让你少走不少弯路。
先说明白一个容易混淆的点:Linux 里“复制”不是一个单一动作。用户态的命令只是入口,真正干活的是open、read、write、sendfile、copy_file_range这些系统调用,再往下是页缓存、块层、文件系统驱动。不同工具包装了不同的系统调用组合,所以性能、语义、边界情况都不一样。理解了这一层,你就能明白为什么同一个“复制”,cp和rsync的行为可以差那么多。
2. 基础复制:cp 命令的底层逻辑与正确姿势
2.1 cp 的多种形态:从单文件到整棵目录树
cp是所有复制操作的地基。基本用法大家都会,关键是要理解它的两个核心选项:-r(递归)和-a(归档)。
cp file.txt /backup/:把单个文件复制到目录里,保留原名。cp file1 file2 dir/:一次复制多个文件到目录。cp -r project/ /backup/project_copy/:递归复制整个目录树。cp -a project/ /backup/project_copy/:递归复制,同时保留权限、所有者、时间戳、软链接、硬链接关系,相当于-dR --preserve=all的组合。
很多人习惯用-r,但如果是搞备份或者迁移环境,强烈建议用-a。原因很简单:-r在旧版本里对符号链接的处理是“跟着链接指向的目标复制”,而不是复制链接本身,这会导致目标目录里出现一堆实际的普通文件,而不是软链接,恢复出来环境就变了。-a则把链接、属性、时间全部保留,基本上是把目录树“克隆”过去。
2.2 为什么 cp 复制大目录那么慢:单线程与缓存机制
cp默认是单线程的。复制一个大目录时,它按顺序逐个文件处理,每个文件都经历“打开源文件→创建目标文件→把数据从源读到用户态缓冲区→再写到目标文件→关闭”的循环。这个过程中,如果源和目标在同一块磁盘上,实际上是在同一块物理盘上又读又写,磁头来回摆动,性能很差;如果跨磁盘,则受限于较慢的那块盘。
还有一个反直觉的地方:cp大量读取文件时会把数据填进页缓存,如果源文件特别大,可能导致缓存被“冲”掉,其他正在运行的服务的文件缓存被挤出,造成暂时的 IO 抖动。我在生产服务器上复制几十 GB 的日志目录时就遇到过,复制期间数据库查询变慢,就是因为缓存被大量复制操作污染了。后来加了ionice和nice才缓解。
2.3 硬链接、符号链接和稀疏文件的复制陷阱
用cp -a复制目录树时,硬链接关系会被保留,这是一个很多人不知道的细节。比如一个目录里有两个文件a.txt和b.txt硬链接到同一个 inode,普通cp -r会把它们复制成两个内容相同但独立的新文件,磁盘占用翻倍;而cp -a则会在目标目录中重新建立相同的硬链接关系,省空间也保留了原语义。
稀疏文件是另一个容易翻车的地方。一个 100 GB 的文件,实际只写了 1 GB 数据,其余都是空洞(全零字节)。如果用普通cp复制,它会老老实实读取那些空洞,然后写出 100 GB 的真实文件——目标磁盘直接爆掉。正确的做法是cp --sparse=always,让目标文件也变成稀疏文件;--sparse=auto则只在源文件本身是稀疏时才保留稀疏属性,这也是很多发行版默认的行为。生产环境里遇到数据库的 ibd 文件或者虚拟磁盘镜像,一定要确认一下源文件是不是稀疏文件,否则灾难就在前方。
3. 增量同步:rsync 为什么是服务器上最值得信任的复制工具
3.1 rsync 的核心:分块校验与增量传输
如果说cp是“无脑全量复制”,那rsync就是“有脑增量同步”。它厉害的地方在于:先比较源和目标,找出差异,只传输变化的部分。具体原理是它把文件切成固定大小的块(默认 700 字节左右,可调),对每一块计算弱校验(滚动校验)和强校验(MD5 或 xxhash),目标端用这些校验值判断哪些块是自己已有的,然后只把缺失的、改动的块发过来。这样即使一个 10 GB 的文件只改了 1 字节,rsync也能在秒级完成同步,而不是重传整个文件。
这个机制特别适合两类场景:一是备份,二是大规模文件分发。我维护过一套一百多台机器的配置文件同步任务,用rsync+ 定时任务,每次执行只花几秒到几十秒,就是因为绝大部分文件根本没变化。
3.2 rsync 常用参数组合速查表
这里是一套我实际用的参数组合,覆盖了大多数同步需求:
| 参数 | 含义 | 我的建议 |
|---|---|---|
-a | 归档模式,保留权限、时间戳、软链接等 | 几乎必用 |
-v | 显示同步过程 | 调试时用,跑定时任务时去掉 |
-z | 传输时压缩 | 跨公网时用,局域网可不加 |
--delete | 删除目标端多余的文件 | 做镜像时用,慎用 |
--exclude | 排除匹配的文件或目录 | 必学 |
--partial | 保留因中断而没传完的文件 | 大文件同步必加 |
--progress | 显示进度 | 手动执行时用 |
-e ssh | 指定远程 shell | 跨机器同步时用 |
一个典型的长命令:
rsync -avz --delete --partial \ --exclude '*.tmp' \ --exclude 'cache/' \ /data/project/ user@remote:/backup/project/它会把本机/data/project/同步到远程机器的/backup/project/,保持属性不变,删除远程多余的旧文件,跳过临时文件和缓存目录,支持断点保留。注意结尾的斜杠:/data/project/表示把目录内容同步过去,目标端形成/backup/project/下的内容;如果去掉斜杠写成/data/project,则目标端会多出一层/backup/project/project/。这个小细节我见过太多人栽过跟头。
3.3 大文件断点续传与校验:--partial 和 --append 的区别
同步大文件时,网络中断是常态。--partial会把已经传了一半的文件保留在目标端,下次再跑rsync时,它会基于这个半成品继续。但--partial的继续是基于分块校验的,它需要重新扫描已经接收的块,对于超大文件来说,扫描本身也有开销。
如果想更激进地“接着写”,可以用--append,它假设目标文件的前缀是正确的,直接从文件末尾继续写入。这个选项速度更快,但有一个致命前提:如果源文件已经被修改过且修改位置在已传输的部分中,--append就会把新旧数据混在一起,造成文件损坏。所以我只在确认源文件完全不会再变化时才用--append,比如备份静止的磁盘镜像时;日常同步一律用--partial。
3.4 实战:定时增量备份加日志轮转
我整理过一个很稳的备份方案,核心是 rsync 加硬链接实现“快照式备份”。原理很简单:第一轮用rsync -a --delete把数据同步到一个目录,下一轮同步时,先把上一轮目录做一次硬链接复制(cp -al,注意是-al不是-a),生成一个看起来像完整拷贝的新目录,再对新目录执行 rsync 增量更新。这样每一轮备份都像是一个完整的快照,但实际只占用差异数据的空间,因为硬链接让未变化的文件共享同一个 inode。
这个方案我跑了几年,恢复过多次误删文件,从来没有失手过。执行流程可以写成脚本,核心命令就是:
# 创建硬链接快照目录(瞬间完成,几乎不占空间) cp -al /backup/current /backup/$(date +%F) # 增量同步 rsync -a --delete /data/source/ /backup/current/用cp -al做快照的关键是:所有文件都只共享 inode,不复制数据,所以瞬间完成。只有被修改的文件才会在下一步 rsync 里产生新的 inode 和实际数据,空间占用非常可控。
4. 跨机器复制:scp 与 rsync 的取舍,以及高效传输方案
4.1 scp 的局限:为什么新环境里我推荐用 rsync 替代
scp是最常见的跨机器复制工具,用法简单:
scp file.txt user@remote:/path/ scp -r project/ user@remote:/path/但scp有几个长期为人诟病的问题:一是它不保留硬链接关系、不保留文件的时间戳(默认按当前时间创建)、不保留 ACL 和 xattr;二是传输大目录时没有断点续传能力,一旦中断就得从头再来;三是它依赖 SSH 协议中的 SFTP 子系统的旧实现,安全性上也有一些历史争议。虽然新版本 OpenSSH 里的scp已经改用 SFTP 协议,但断点续传和增量传输依然没有。
所以我在实践中跨机器复制优先用 rsync:
rsync -avz --partial /data/project/ user@remote:/backup/project/它走 SSH 加密通道,传输时压缩,中断后可以续传,属性保留完整。怕有人问“rsync 不是也要先扫描所有文件吗,几百 GB 目录扫描时间也很长”,确实,扫描开销是 rsync 的固有成本,但换来的是增量传输和正确性,这笔账在绝大多数场景下是划算的。如果追求极限速度且不关心属性,可以使用tar管道直接打包并通过 SSH 传输,下面会说。
4.2 高效传输大目录:tar 管道加 SSH 的骚操作
当你需要一次性把一个大目录完整搬到另一台机器,而且不关心目标端是否已有同名文件时,rsync的扫描开销就显得多余。这时候可以用 tar 管道:
tar -C /data/project -czf - . | ssh user@remote "tar -C /backup/project -xzf -"这个命令做的事情是:在源机器上把目录打包并通过标准输出流式输出,SSH 把流传输到远程机器,远程机器再解包写入磁盘。整个过程没有产生临时文件,没有磁盘读两遍的浪费,速度接近网络上限。如果内网带宽好,甚至可以去掉z参数,因为压缩反而会成为瓶颈:
tar -C /data/project -cf - . | ssh user@remote "tar -C /backup/project -xf -"这个方式的缺点是不方便增量、不方便排除部分文件(当然也可以用--exclude),但胜在简单粗暴、性能极佳。我第一次用这个命令迁移一个 200 GB 的网站目录,千兆内网只用了十几分钟,比先用 tar 打包再传输再解压快了一倍还不止,因为少了中间态的磁盘 IO。
4.3 多线程并行传输:让大文件复制跑满带宽
scp和rsync默认都是单通道单线程,受制于 TCP 窗口和单流拥塞控制,在延迟高、带宽大的网络上跑满速度很困难。比如跨地域传输,单流可能只能跑几十 MB/s,但多流可以轻松跑满带宽。
常用的并行方案是rsync配合多进程分片,或者用aria2c、lftp这些支持多连接的工具。不过更优雅的工具是parsync或fpsync,它们把文件按列表平分给多个 rsync 进程。我自己常用的是一个简单的 xargs 并行方案:
# 列出所有文件,按 10 个进程并行 rsync find /data/project -type f -print0 | \ xargs -0 -n 200 -P 10 -I {} \ rsync -avzR --relative {} user@remote:/backup/project/思路是把文件列表切块,每个 rsync 进程负责一部分文件,并行传输。注意-R选项保留了相对路径。这个方案在大量小文件场景下提升尤其明显,因为单个 rsync 进程处理小文件时开销主要花在建立连接和文件头扫描上,并行能让这些开销重叠。我自己用 10 进程并行同步过几十万个文件的小文件目录,耗时从三小时缩短到四十分钟。
提示:并行 rsync 的方案要小心 CPU 和网络占用,进程数不要盲目开大,一般先观察
top和iftop,根据机器负载调整。
4.4 没有 SSH 时的选择:nc、ncat 与 HTTP
有些场景下 SSH 不可用(比如目标是一个嵌入式设备,或者临时环境不想配置密钥),可以用nc裸传,也称 netcat 传输:
源端:
tar -C /data/project -cf - . | nc -l -p 9000目标端:
nc host_ip 9000 | tar -C /backup/project -xf -nc是明文裸流,没有加密和校验,只适合在可信的内网里使用,而且必须注意先启动接收端再启动发送端,否则连接直接失败。安全要求高的环境绝对不要这么做,老老实实走 SSH。还有一点:nc传输出错后没有任何提示,解压出来可能缺文件,所以传完最好用du -sb对比一下源和目标的字节数。
5. 复制之外的高级技巧:稀疏文件、dd 克隆与文件系统级快照
5.1 dd:复制块设备的正确用法与坑
dd是另一个强大的复制工具,它不像文件系统那样理解文件和目录,而是直接按字节流复制块设备或者文件。最常见的用途是制作启动盘、备份分区、克隆磁盘:
# 备份整个分区为镜像 dd if=/dev/sda1 of=/backup/sda1.img bs=4M status=progress # 从镜像恢复 dd if=/backup/sda1.img of=/dev/sda1 bs=4M status=progressbs=4M设置了块大小,这个参数对性能影响很大。默认bs=512会导致每次只读写 512 字节,在机械盘和 SSD 上性能都极差。推荐至少bs=1M到bs=64M,实际效果我测过,4M 到 16M 之间差异不大,但比默认值快几十倍。
用dd复制物理磁盘时有个大坑:目标磁盘的驱动器和文件系统分区信息也会被“照抄”过去,包括分区表里的设备 GUID,可能引起冲突。另外,如果源磁盘正在被使用(比如系统盘),dd 出来的镜像可能是不一致的,因为边写边改。所以克隆正在运行的系统盘前,最好先卸载文件系统或者使用能够感知快照的工具。
5.2 稀疏文件检测与 dd 的稀疏复制
前面提到稀疏文件,如果你想确认一个文件是不是稀疏的,可以用du和ls对比:
# 查看磁盘占用(实际数据量) du -h file.img # 查看逻辑大小(包含空洞) ls -lh file.img如果du显示远远小于ls,这就是稀疏文件。用dd复制稀疏文件时,默认会把它还原成非稀疏文件,也就是把空洞都填成真实零字节,目标文件会变得巨大。解决方法是dd加conv=sparse:
dd if=source.img of=target.img bs=4M conv=sparse status=progress它会自动检测读出来的全零块,并在目标文件上创建空洞,复制后的文件依然保持稀疏。这个参数日常很多人不知道,等磁盘被撑爆就晚了。
5.3 快照与 Reflink:比复制更高效的方式
真正高性能的“复制”有时候并不是复制,而是快照或者 Reflink。如果你的文件系统是 btrfs 或 XFS(开启了 reflink 支持),可以用cp的--reflink=auto选项创建 CoW(写时复制)文件:
cp --reflink=auto source.img target.img这个操作对文件系统来说只是拷贝了一份元数据,底层数据块被共享,创建瞬间完成、几乎不占空间。只有当你修改其中一个文件时,被修改的块才会真正发生复制。这非常像前面提到的硬链接快照,区别在于它是对单个文件操作的,而且后续修改是隔离的。XFS 上使用需要文件系统挂载时开启reflink=1属性,btrfs 默认支持。
btrfs 的快照更强大,可以对整个子卷做快照:
btrfs subvolume snapshot /mnt/data /mnt/snapshots/data-20250101瞬间完成,空间按需分配,而且原卷继续正常写入。我用 btrfs 快照做过很多次系统升级前的保险,回滚也只是一条命令的事。相比cp -al的硬链接快照,文件系统级快照还有一个优势:它能保留目录的完整语义,甚至未来可以增量发送到远端。
5.4 超大文件复制的最后细节:校验与验证
复制再快,校验不能省。我做数据迁移的标准流程是“先验证、后删除”,除非目标是从磁盘镜像直接恢复,否则我都会在传输完成后用校验工具确认。
小文件目录用rsync -c做校验最方便,它会逐块重新计算校验值:
rsync -avc --delete /data/project/ /backup/project/单个大文件可以用md5sum或者sha256sum:
md5sum source.iso target.iso如果只是快速判断传输是否完整,可以对比文件大小和时间戳,但这不保险。更严谨的方案是在传输完成后用cksum算循环冗余校验,或者干脆用--checksum让 rsync 自己再扫一遍。我的经验是:对于极重要的数据,双算法校验(比如 md5 加 size)几乎能杜绝静默损坏的风险。
6. 权限与属性的复制、常见失败场景及全套避坑清单
6.1 复制之后权限全乱:ACL 与 xattr 那些事
跨机器复制文件后,最常遇到的现象是:所有者变成 root、权限变成默认值、原来的 ACL 丢了。根源在于目标文件系统或目标用户对某些属性没有权限写入。
普通文件的所有权复制需要 root 权限。如果你是用普通用户执行rsync -a,它会尝试把文件所有者设置为源端的所有者,但普通用户没有 chown 权限,所以要么报错、要么静默忽略。遇到这种情况,要么在目标端用 root 执行,要么明确使用--chown指定目标所有者:
rsync -a --chown=deploy:deploy /data/project/ user@remote:/app/project/ACL 的情况更隐蔽。rsync -a默认不保留 ACL,需要额外加-A参数;保留 xattr 需要加-X。如果目标文件系统不支持这些属性(比如挂载时去掉了user_xattr),rsync 会报错,但不影响普通文件复制。所以完整保留权限的命令是:
rsync -avzAX --delete /data/project/ user@remote:/backup/project/这个组合把权限、属主、时间戳、ACL、扩展属性全部同步,是我做完整备份的标配。注意-A和-X在某些版本里必须配合-a使用,否则行为不一致。
6.2 复制时出现“设备上没有空间”,但 df 显示还有空间
这是一个经典怪问题:df -h显示还有几十 GB,但复制文件时报No space left on device。有两个主要原因:
第一,inode 耗尽。df -i查看,如果使用率 100%,即使有空间也创建不了新文件。解决方法是删除大量不必要的小文件,或者在创建文件系统时增加 inode 密度。
第二,硬链接限制。某些文件系统(比如老式 ext3)对单个文件的硬链接数有限制,快照式备份用多了会触发。
检测手段很简单,先跑df -h再看df -i,确认后对症下药。我遇到过一台跑了很多年的服务器,定期备份一直没有清理,最后/backup分区 inode 100%,但空间还剩 80%,排查了很久才发现。
6.3 目标文件系统不支持某些操作:FAT/exFAT/NFS 导致的怪异行为
复制到 U 盘(FAT32/exFAT)或者 NFS 挂载目录时,很多高级属性复制不了,而且命令可能不报错,只是静默降级。比如 FAT32 不支持符号链接、不支持权限位、不支持硬链接、文件大小有 4 GB 上限。你把一个 5 GB 的文件复制到 FAT32 U 盘,会直接报File too large;复制带软链接的目录树,链接会变成普通文本文件。
所以跨文件系统复制时,一定要提前确认目标文件系统的能力。最简单的办法是复制完用ls -l和find -type l抽查几个文件,别等到环境启动失败才想起来。
6.4 常见问题速查表
| 症状 | 可能原因 | 处理方法 |
|---|---|---|
| 复制大目录速度极慢 | 单线程 IO、同盘读写 | 并行 rsync、分流到不同磁盘、用 tar 管道 |
| 大文件传一半中断 | 网络波动 | 用 rsync--partial续传,不要用 scp |
| 目标端空间被占满 | 稀疏文件被实化 | 用cp --sparse=always或dd conv=sparse |
| 权限所有者变了 | 普通用户无法 chown | 用 root 执行或--chown指定 |
| 符号链接变成普通文件 | 未用-a/-l,或目标文件系统不支持 | 使用cp -a、rsync -a,避免 FAT |
| 复制后硬盘 IO 飙升 | 页缓存被填充 | 用ionice -c3降低优先级、分批复制 |
出现No space left | inode 耗尽 | 查df -i,清理小文件 |
| 传完文件损坏 | 网络或磁盘错误、未校验 | 用 rsync-c或sha256sum校验 |
6.5 复制操作中我总结的几条铁律
第一条:永远不要删源数据,直到目标端的校验通过。我做再小规模的迁移也遵循这条,尤其是覆盖式备份场景,一旦 rsync 加了--delete后又没校验,源端被误判为“多余文件”而删除的风险是真实存在的。
第二条:备份命令里加--dry-run先跑一遍。rsync -avn --delete不会真正执行,只列出差异。这个习惯能预防绝大多数误操作,尤其是当你不确定--exclude规则写对没有的时候。我见过有人把--exclude 'cache/'写成了--exclude 'cache',结果只排除了顶层的 cache 文件,子目录里的 cache 目录全被删了。
第三条:跨机器传输时优先考虑加密通道,但也别裸奔。如果数据不敏感且内网环境可信,rsync 不走 SSH 的远程 shell 模式(用 rsync daemon)速度可能更快;但生产环境我还是坚持 SSH,安全比那一点速度重要得多。
7. 工具选型解析与复制场景的最终建议
7.1 一张表说清什么时候用哪个工具
| 场景 | 首选工具 | 备选 | 关键原因 |
|---|---|---|---|
| 单文件、小目录本地复制 | cp -a | cp -r | 简单可靠,保留属性 |
| 大目录本地复制 | cp -a或rsync | tar管道 | 数据完整性优先时用 rsync |
| 定时备份 | rsync+ 硬链接快照 | btrfs 快照 | 增量同步 + 版本保存 |
| 跨机器一次性大迁移 | tar管道 + SSH | rsync | 速度快、无中间文件 |
| 跨机器持续同步 | rsync+ SSH | 同步工具 | 增量传输、断点续传 |
| 克隆磁盘/分区 | dd | ddrescue | 字节级复制 |
| 创建稀疏镜像 | dd conv=sparse | cp --sparse=always | 节省空间 |
| 文件系统级快照 | btrfs/xfsreflink | 硬链接快照 | 瞬间完成、按需分配 |
7.2 版本差异与兼容性提示
不同发行版、不同版本的 coreutils 对cp的支持有细微差异。比如cp --reflink需要 coreutils 8.23 以上,cp --sparse在更早的版本就支持了;rsync的--chown需要 2.6.7 以上,老版本会报未知选项。跨机器同步时,两端的 rsync 版本最好保持一致,否则新版本命令的一些选项在旧版本上无法识别,导致同步任务直接失败。
我的建议是:在脚本里做版本检测,或者干脆把常用命令的参数组合写成函数,统一管理。脚本里用了rsync时,加上--version输出到日志,排障时一手信息就有了。
7.3 让复制操作可观测
复制不是“敲一下命令就完事”的简单操作,尤其是服务器上的大批量复制,一定要让它可观测。我最常用的是pv工具,可以显示进度条和速率:
tar -C /data/project -cf - . | pv -s $(du -sb /data/project | awk '{print $1}') | ssh user@remote "tar -C /backup/project -xf -"pv的作用是统计管道中流过的字节数并显示速度、剩余时间。du -sb算出总字节数作为预估总量,进度条就变得非常有参考价值。如果你不想装 pv,也可以直接在 rsync 命令里加--progress,但它在文件数量多时会刷屏,适合单文件大文件场景。
另外,长时间复制任务建议用screen或tmux挂起,免得 SSH 断开导致任务中断。我见过太多人直接用终端跑几小时的 rsync,半夜断线白跑。正确的做法是tmux new -s sync进去跑,断线了也能tmux attach回来。
7.4 一套可以“抄作业”的完整备份脚本
下面是我现在生产环境里用的一个精简版定时备份脚本,它综合了 rsync 增量、硬链接快照、日志输出和旧快照清理。你可以在自己机器上调整路径后直接用:
#!/bin/bash # 简单可靠的 rsync 快照备份脚本 # 用法:backup.sh <配置名> set -euo pipefail SOURCE_DIR=${SOURCE_DIR:-/data/project} BACKUP_ROOT=${BACKUP_ROOT:-/backup} KEEP_DAYS=${KEEP_DAYS:-7} DATE=$(date +%F_%H%M%S) SNAPSHOT="$BACKUP_ROOT/current" DEST="$BACKUP_ROOT/$DATE" # 1. 确保当前快照存在,先做一次初始化 if [ ! -d "$SNAPSHOT" ]; then rsync -a --delete "$SOURCE_DIR/" "$SNAPSHOT/" fi # 2. 用硬链接从当前快照创建新的快照目录(瞬间完成) cp -al "$SNAPSHOT" "$DEST" # 3. 执行增量同步,把新数据更新到当前快照 rsync -a --delete \ --exclude '*.tmp' \ --exclude 'cache/' \ "$SOURCE_DIR/" "$SNAPSHOT/" >> /var/log/backup.log 2>&1 # 4. 清理过期快照 find "$BACKUP_ROOT" -maxdepth 1 -type d -name '20*' -mtime +$KEEP_DAYS -exec rm -rf {} + echo "备份完成:$DEST"这个脚本的逻辑是:cp -al创建硬链接快照,几乎瞬时完成;然后 rsync 更新current目录。每跑一次,就多一个快照目录,一个文件没变化的话,所有快照共享同一份数据,不占额外空间;文件变化后,只有变化的部分会占用新空间。KEEP_DAYS控制保留天数,按需调整。
注意:
find -delete或rm -rf删除硬链接快照目录时,只是删除了相应目录条目的链接计数,不会删除其他快照仍引用的数据块,所以不用担心误删共享数据。
8. 聊点我的个人体会与复制的扩展思考
我刚开始用 Linux 的时候,觉得复制就是cp一个命令的事。后来在真实环境里栽了太多次,才慢慢意识到,文件复制这件事从来都不只是“数据搬移”,而是对文件系统语义、元数据、性能模型、网络特性的综合考验。一个能把复制做到滴水不漏的工程师,往往对 inode、文件系统布局、系统调用路径都有着不错的掌控力。
举一个小例子:cp和rsync在复制同一个文件时,写入模式是不同的。cp会先创建目标文件,然后一边读一边写,如果复制到一半磁盘满了,你会得到一个截断的、不完整的文件,而且没有简单办法知道它缺了多少。rsync则不同,它会把数据先写到目标目录下一个隐藏的临时文件(.[文件名]),传输完整后再改名为正式文件。临时文件机制保证了:如果同步中途失败,目标位置不会出现半个正式文件。这个设计哲学对我影响很深——很多时候,我们缺的不是执行能力,而是对“不完整状态”的处理预案。
再分享一个扩展思路:你可以把复制操作和校验、审计结合起来。比如每次 rsync 完成后,自动生成一份校验清单(find . -type f -exec md5sum {} + > manifest.md5),下次同步时先校验上次的清单,再决定要不要拉取。这样既能发现静默损坏的文件,也能追踪哪些文件在什么时候被动过。这个思路比单纯“备份”更接近“数据资产管理”的层次,我在治理一批历史遗留数据时就是这么做的。
工具会迭代,协议会升级,但复制背后的那些基本原则——先备份、再操作、保留完整语义、进行校验、留有余地——是常年不变的自保之道。希望你读完这篇文章,下一次敲复制命令的时候,能多留个心眼,少踩一个坑。