手头的活儿还没干完,就被人问了一句“Linux下怎么拷贝目录”。我那会儿正折腾一个数据迁移,第一反应是“cp -r不就行了”,但转念一想,这事儿真没这么简单。如果你只是拷个两三层的小目录,cp -r确实够了;可一旦涉及几个G的网站目录、十万级的小文件、或者要跨机器搬数据,cp -r的坑就会一个接一个地冒出来——目标目录多套一层、软链接变成真文件、拷到一半Session断了还得从头再来。这些场景我全踩过,所以干脆把“拷贝目录”这件事从头到尾捋一遍,从最基础的cp到真正适合生产的rsync,再到断点续传、并发加速和进度查看,一篇讲透。
1. cp是底线,但别只会cp -r
拷贝目录最基础的工具就是cp,但很多人用错在意识上:只记得加-r,却不知道自己到底在拷什么。
1.1 基本命令与常见误区
最朴素的写法是:
cp -r /path/to/source_dir /path/to/dest_dir这个命令会把source_dir整个目录复制到/path/to/下面,如果dest_dir不存在,就创建一个新的;如果dest_dir已经存在,结果是/path/to/dest_dir/source_dir,多套了一层。这个差异非常容易踩:
cp -r /home/user/www /backup/ # 结果是 /backup/www,而不是把 www 的内容平铺进 /backup很多人以为“我指定了目标目录,内容就该直接进到目标目录里”,但cp的语义不是这样。它永远是把“源”作为一个整体放进“目标”中,除非目标本身不存在。想避免多套一层,要么目标路径写成不存在的全新目录名,要么就用后面的rsync。
其实cp -r在大多数发行版上还隐含了一个问题:它不一定保留权限、时间戳和属主属组。-r的全称是--recursive,只管递归,不管“原样保留”。所以业内更推荐用-a:
cp -a /path/to/source_dir /path/to/dest_dir-a等于-dR --preserve=all,也就是递归、保留软链接本身、保留权限、时间戳、属主属组、ACL等能保留的全保留。日常做目录归档,哪怕只是从临时目录拷到项目目录,我基本都直接用-a,让“拷贝”这件事从一开始就具备备份属性,而不是拷过去一堆变了味的数据。
1.2 通配符与路径展开的坑
用cp拷贝目录时还有个隐蔽问题是通配符。写脚本的人经常这么干:
cp -r /data/logs/* /backup/logs/这条命令的本意是“把logs下面所有内容拷过去”,但它有两个隐患:
- 如果
/backup/logs不存在,cp不会自动建目录,命令直接报错; - 如果某些子目录名以
.开头,*默认不匹配隐藏文件,结果就是拷贝不全。
更稳妥的做法是目标目录先建好,再用ls -A这类方式确认要拷贝的文件范围,或者干脆放弃通配符,直接处理整个目录再按需删除。
要说实用经验,我最想提醒的是:不要在一条cp命令里同时处理“递归拷贝”和“平铺拷贝”两种心智模型。要么始终拷贝整个目录,要么明确逐个拷贝子项。混着用,脚本早晚会在某个午夜出问题。
2. rsync才是日常主力,不只是同步工具
只要涉及“拷贝目录”而且数据量不小,我第一个想到的工具永远是rsync。它表面上是同步工具,但本质上就是“增量拷贝目录”的终极形态。
2.1 为什么推荐用它拷贝目录
rsync相比cp的核心优势有三点:
- 断点续传:拷贝中途断了,重跑一遍会接着从断点开始,而不是全部重来;
- 增量同步:目标目录已有且未改动的文件,直接跳过,节省大量IO和时间;
- 保留属性全面:软链接、硬链接、权限、时间戳、属主属组、ACL、扩展属性都可以显式保留。
我用它做目录拷贝的典型命令是:
rsync -av --progress /home/user/www/ /backup/www/各参数含义:
| 参数 | 作用 |
|---|---|
-a | 归档模式,递归并保留大部分属性 |
-v | 输出详细过程 |
--progress | 显示每个文件的传输进度 |
--delete | 删除目标端多余的文件,让两端完全一致 |
-z | 传输时压缩,适合跨网络拷贝 |
-e ssh | 指定远程shell(默认也常用) |
这里还有个必须分清的概念:rsync如果不加--delete,相当于“增量拷贝”;加了--delete,才真正是“镜像同步”。日常做目录复制备份,我会先不加--delete跑一遍,确认源和目标的关系符合预期后,再在需要严格镜像的定时任务里加上它。
2.2 本地拷贝与远程拷贝的实际操作
本地目录拷贝,我最常用这一条:
rsync -av --delete /data/project/ /data/backup/project/注意源路径末尾的斜杠。带着斜杠表示“拷贝目录里面的内容”;不带斜杠表示“拷贝整个目录本身”。稍不留神,目标路径下的结构就会差一层。远程拷贝则这样写:
rsync -avz --progress /data/project/ user@remote_host:/backup/project/它会通过SSH传输,默认端口22,也可以-e "ssh -p 2222"指定端口。跨机器搬完目录后记得去目标机器ls -la核对,我第一次远程跑rsync就因为在源路径上省略了斜杠,结果目标目录里多套了一层文件夹而不自知。
2.3 实际项目中我常用的备份参数组合
如果是定期备份一个会持续增长的目录,我会用一个比较完整的组合:
rsync -avzh --delete --partial --exclude='*.tmp' --exclude='cache/' /srv/data/ /backup/data/--partial允许保留不完整文件,配合重跑时的断点续传逻辑;--exclude可以剔除日志、缓存这类不需要进备份的目录。拷贝目录不是简单的“全都要”,很多时候“排除什么”才是水平所在。
3. 源路径末尾的斜杠,是目录拷贝的第一大陷阱
这个坑我至少见过五个人踩过,包括我自己。源路径带不带/,结果天差地别。
3.1 行为差异对比
以rsync举例,假设源目录/data/www下有index.html和assets/两个条目,目标目录是/backup/www:
rsync -av /data/www /backup/ # 结果:/backup/www 目录被创建,里面是原先 /data/www 下的内容 # 也就是说,源头的不带斜杠,是把 www 目录本身拷过去 rsync -av /data/www/ /backup/www/ # 结果:/backup/www 下的内容直接等于 /data/www/ 下的内容 # 源头的斜杠表示“把里面的内容拷贝到目标目录中”cp其实也有类似语义,只是很多人在目标目录不存在时没注意到。区别在于:cp -a /data/www /backup/不管怎么变,语义都趋向“整体放入”;而rsync因为和--delete搭配,斜杠不但影响层级,还影响目标端多余文件是否被删除。
3.2 如何系统性规避
我的习惯是在脚本里统一约定:源路径一律带尾斜杠,目标路径也带尾斜杠,并且保证目标目录存在。这样语义最明确——把源目录里的全部内容,同步到目标目录里。脚本里像下面这样写:
find /data -maxdepth 2 -name 'www' rsync -av --delete /data/www/ /backup/www/同时写个简单断言,确保源路径存在再执行:
if [ ! -d /data/www ]; then echo "source dir missing" exit 1 fi这些看起来微不足道,但目录拷贝的线上事故,有一半都出在这种“路径语义”上,先把斜杠约定确定下来,比调任何参数都重要。
4. 权限、链接与时间戳:目录拷贝最容易“看着成功实则失败”的部分
最坑的拷贝不是报错,而是“命令成功、数据也没丢,但拷出来的结果跟原目录不一致”。属性丢失就是这一类。
4.1 为什么普通拷贝会丢失属主属组
如果你用普通用户执行cp -r或rsync -a,目标文件的属主会变成当前用户,而不是保持源文件的属主。这在备份别人的网站目录、或者往移动硬盘里归档数据时会非常危险——表面文件都在,但回传回去后权限全乱,服务直接跑不起来。
遇到这种场景,几乎必须借助sudo:
sudo rsync -av --owner --group /srv/user_data/ /backup/user_data/同时要在目标文件系统支持Linux权限的前提下进行,比如ext4、xfs都没问题,但拷到FAT32或NTFS移动硬盘时,“属主属组”这个概念本身就存在不了,这种时候不要强求。
4.2 软链接、硬链接与特殊文件
目录里的软链接用cp -r拷贝时,部分老版本的行为会把它“解引用”,也就是把链接指向的目标文件整个复制一遍,而不是复制链接本身。rsync -a则更可控:默认保留软链接。如果你想显式展开软链接、把链接指向的真实内容也原样拷成普通文件,可以用-L:
rsync -avL /data/linked_dir/ /backup/linked_dir/而硬链接是另一种容易碎的属性。大目录里经常有几十个硬链接指向同一份数据,普通拷贝会把它变成几十份独立文件,白白浪费空间。cp -a会在可能的情况下保留硬链接关系,rsync用-H保留。我处理一个包含数千硬链接的资源库时,忘加-H导致备份体积膨胀了两倍多,这个教训很实在。
4.3 时间戳与复制结果的验证
时间戳在增量备份中既是优点也是隐患。rsync快速跳过文件靠的是文件大小和修改时间判断,“内容变了但修改时间没变”的文件会被漏掉。安全起见,校验类需求需要加-c:
rsync -avc /data/project/ /backup/project/这会逐个文件做校验和对比,传输速度会下降,但能保证正确性。
拷完如何确认?我常用这一组组合拳:
du -sb /data/www /backup/www # 对比两个目录的字节数,粗查 find /data/www -type f | wc -l find /backup/www -type f | wc -l # 对比文件数量 diff -r /data/www /backup/www # 逐文件对比内容、权限、链接,最严格diff -r的输出如果是干净的,那我基本可以放心收拾东西走人。
5. 大目录长拷贝:会话断开、断点续传与并发加速
拷贝一个几十GB、几百万文件的目录,跟拷贝小目录完全是两个物种。那种“直接敲命令盯着看”的做法是行不通的。
5.1 用tmux或screen托管拷贝任务
SSH连到服务器跑一个漫长的拷贝,一旦网络闪断、终端关闭,整个进程都会收到SIGHUP死掉。正确做法是先把任务放进tmux再执行:
tmux new -s copy_job rsync -av --progress /data/massive/ /backup/massive/然后按Ctrl+b再按d脱离会话。过几个小时重新连上,用tmux attach -t copy_job就能看到任务是否还在跑、输出到了哪一步。如果只用nohup写日志也行,但tmux能随时交互,对长任务的掌控感强得多。
5.2 断点续传与校验
rsync本身就支持断点续传,但有几个参数配合起来才真正“断得干净续得稳”:
rsync -av --partial --append-verify /data/big_data/ /backup/big_data/--partial:保留传输到一半的不完整文件,方便续传而不是把它当垃圾删掉;--append-verify:对已经存在的文件,只校验已传完的部分,然后追加剩余数据,再验证尾部。
实测在大文件场景下,这个组合比一次性同步快很多。要注意的是,--append-verify不适合源文件持续变化的情况,如果边写边拷,追加校验的结果会不可靠。
5.3 用xargs并行加速小文件拷贝
海量小文件是rsync的软肋,因为每个文件都有握手和校验开销。我导出一个包含120万张图片的目录时,单线程rsync跑了好几个小时。后来我给拷贝步骤加了并行:
find /data/images -type f -print0 | xargs -0 -n 100 -P 8 -I {} rsync -aR --ignore-existing {} /backup/images/注意几个细节:
-P 8表示8个并行进程,具体数值要看你主机的IO能力和CPU核数,盲目开太高反而让磁盘寻道效率爆炸;-R保存相对路径,保证目标端目录结构与源一致;--ignore-existing避免重复处理已拷贝完的文件。
并行rsync并不是万金油,目标端磁盘是机械硬盘时,并行度高了反而更慢。建议先用hdparm或fio简单测一下目标盘的写性能,再决定并行数。
5.4 实时查看拷贝进度
rsync --progress会给每个文件都刷进度,但大目录下输出太密,不够直观。我更常做的做法是开三个窗口,一个跑任务,一个看文件数增长:
watch -n 10 'find /backup/images -type f | wc -l'另一个看整体字节:
watch -n 10 'du -sb /backup/images'如果是本地同盘拷贝,还可以iostat -x 1观察磁盘利用率,确认瓶颈在源盘还是目标盘。拷贝不光是命令,还是要盯资源走势的。
6. 跨分区与跨机器拷贝:从mv到tar管道的一些冷门但稳妥的操作
不只是备份场景,日常迁移目录也经常用到“拷贝”这件事。跨分区mv看着是移动,实际是拷贝加删除,很多人不知道这一点。
6.1 mv跨分区时“慢得离谱”的真相
当你在同一个分区内执行mv,它只是改一下目录项,瞬间完成;一旦跨分区,比如从/根分区移到挂载的/data数据盘,mv会老老实实地把数据完整复制一遍,然后再删除源路径。如果你有一堆大文件要移动,又不知道这点,就会盯着命令行“卡住”怀疑人生。
配合rsync显式拷贝,反而更好管理:
rsync -av --remove-source-files /data/tmp/ /data2/final/--remove-source-files模仿了mv,但可以断点重跑、增量同步,也比直接mv更有掌控感。
6.2 没有rsync时,用tar管道完成远程拷贝
有时候目标机器没装rsync,又不想临时装包,可以用tar管道解决:
tar czf - /data/project | ssh user@remote_host 'tar xzf - -C /data/'原理是:本地把目录打包成流,通过网络管道传输,远端直接解包到目标目录。-C /data/确保解包到指定位置;如果想保留相对路径,源路径写法和tar的-C配合。
要注意的是,管道拷贝是一锤子买卖,中途网络断了就功亏一篑。所以这种方案适合临时赶工,不适合正式备份。正式场景还是rsync加tmux更可靠。
6.3 目录拷贝过程中的进度判断技巧
拷大目录时没有进度条会让你心里发慌。除了du和find配合watch,还有个小技巧:
# 在终端里按下 Ctrl+T某些Unix-like系统会打印当前正在执行的前台进程状态,能直观看到进程等了多久、系统调用卡在哪个文件上。另外可以用lsof +D /data/images看看当前还有多少文件被打开、正在写入,这套组合基本能回答“它是不是真的在动”的疑虑。
拷贝目录这件事,说穿了就是“把源文件系统里的那棵目录树,在目标位置上重建一遍”。cp -r能完成,rsync能做得更好,tar管道则是无rsync环境下的应急手牌。我个人的真实体会是:先弄清源和目标的关系是“放在里面”还是“覆盖对齐”,再决定斜杠和参数;先把任务放进tmux,再动手跑大目录;拷完必须用diff或校验和做最终确认。这三步看起来不起眼,但它们能挡住我在操作中遇到过的绝大多数目录拷贝事故。下次再有人问我“怎么拷贝目录”,我不会只甩一句cp -r,而是会先问他三个问题:拷到哪里、数据多大、要不要保留完整权限。这些问题清楚了,命令其实自然而然就出来了。