不夸张地说,cp、scp、rsync这三个命令,是我在协助团队排查服务器问题时被问得最多的三兄弟。很多人习惯了"cp本地复制、scp远程复制、rsync同步备份"这个粗糙印象,可真到了生产环境里,一个cp -a能不能保留ACL、一个scp端口写错连不上、一个rsync因为路径末尾少了斜杠把目录结构同步得乱七八糟,这些细节才是真正让人头疼的地方。这篇东西我打算把这几年实际踩过的坑、看过的文档、写过的同步脚本一次性梳理清楚,给那些已经在用这几个命令、但还没完全吃透它们脾气的人一份可以直接抄作业的参考。
1. 先搞明白三兄弟的分工:本地、远程、增量同步是三条完全不同的路
很多新人会把cp、scp、rsync当成同一类工具的不同版本,这个理解大方向没错,但它们解决的问题其实完全不同。cp是纯本地操作,只能在同一台机器的文件系统里做复制,它的核心任务是"把文件从这个路径复制到那个路径",不涉及网络,也不关心目标端是否已经存在相同内容。scp的本质是"用SSH封装的远程复制",它把本地文件通过加密通道推送到远程主机,或者从远程主机拉取到本地,适合偶尔传几个文件、不做复杂校验的场景。而rsync是个"同步专家",它不只复制,还会对比源和目标两边的文件差异,只传输变化的部分,既能本地同步,也能通过SSH或daemon模式做远程同步,是增量备份和镜像部署的标配工具。
打个比方:cp就像你把一本书从书桌这头搬到书桌那头,页面一字不差,但如果另一头已经放了一本一模一样的书,它不会管,照搬不误;scp像是你拿着书骑自行车送到隔壁小区,速度快但每次都是整本整本地送,哪怕对方已经有了同一本书也会再送一遍;rsync则像个聪明快递员,先看一眼对方家里缺哪几页,只补那几页。这个底层逻辑差异,决定了你在不同场景下该选谁。
还需要注意,scp和rsync虽然都能走SSH,但它们的传输协议和断点续传能力完全不一样。scp是古老的RCP协议升级版,只负责"把文件流从A搬到B",一旦传输中断,之前传了一半的文件直接作废,下次必须从头再来。rsync自己有一套校验和算法,支持--partial保留部分文件,下次同步时直接从断点继续。对于动不动几个GB的数据库备份文件,这个差异就是天壤之别。
明白了这个定位,后面所有参数和坑都好理解了。接下来我按命令一个一个拆,把我在生产环境里真正用过的参数、踩过的坑写出来。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. cp命令:别以为只是"复制",这些参数才是区分老手的标尺
cp看起来是所有命令里最没技术含量的,但凡是处理过大型目录迁移、文件属性异常、磁盘空间告急的人,都会对cp的几个隐藏参数记忆犹新。
2.1 保留一切的 -a 和容易搞混的 -d、-p、-R
我见过很多人的备份脚本里写着cp -r /data /backup,这个写法在绝大多数场景下是错的。-r只做递归复制,不保留文件权限、时间戳、属主和软链接结构。比如你复制一个带软链接的目录,用-r会把软链接指向的目标文件实打实复制一份,而不是保留软链接本身,恢复备份时整个目录结构就废了。
正确做法是cp -a。-a等于-dR --preserve=all,它做的事是:递归复制、保留软链接本身、保留权限、保留时间戳、保留属主属组、保留ACL和SELinux上下文。我建议把cp -a当成默认选项,除非你明确知道不需要保留任何元数据。它唯一的问题是复制大目录时速度不见得比-r快,因为要处理的属性多,但这点开销换来的可靠性值回票价。
还有个容易混淆的点:cp -p只保留权限和时间戳,cp -d只保留软链接而不跟随目标,cp -R跟-r行为几乎一样,只是对特殊文件的处理上略有差异。如果你不确定,直接cp -a,别纠结。
2.2 使用硬链接复制来省空间的 -l 和 --reflink
先看-l:它不复制内容,而是为文件创建硬链接。什么意思?目标文件不是一个副本,而是与源文件指向同一个inode,两边任意一边修改,另一边也会变。适合做"快照式"副本的场景:比如你有10个目录需要共享同一份只读资源,用cp -l可以瞬间完成,且几乎不占额外磁盘。但千万记住,硬链接不能跨文件系统,目录也不能硬链接,所以cp -l只适用于同一分区内的文件。
还有一个更现代的参数--reflink=auto,这是Btrfs、XFS等CoW文件系统上的黑科技。它在复制时生成一个"写时复制"的引用副本,复制操作瞬间完成,只有当你修改某个副本时才真正分配新数据块。日常用cp的时候可能感觉不到它,但做虚拟机镜像克隆、容器层复制时,这玩意能帮你省掉几个T的磁盘。文件系统不支持时它会自动降级为普通复制,所以我建议直接在脚本里写上cp -a --reflink=auto,既安全又高效。
2.3 稀疏文件与空块:--sparse 的意外之喜
数据库文件和虚拟机磁盘映像里常常有大片连续的零字节,正常复制会把这些空洞原样写出,白白浪费磁盘空间和IO。cp --sparse=auto会在复制时检测空洞,目标文件以稀疏文件形式存在,实际占用的物理块大幅减少。我之前迁移一个200GB的MySQL数据目录时,用了cp -a --sparse=auto之后,目标端实际只占了90GB,效果立竿见影。
如果你希望目标文件强制"填实"而非稀疏,用--sparse=never;默认的auto是智能判断,大多数时候够用。
2.4 cp的更新复制:-u 和交互提示 -i
cp -u只复制源目录中比目标更新的文件,或者目标中不存在的文件。这个参数适合"手动补数据"的场景,但说实话,一旦涉及增量同步,rsync才是最佳工具,cp -u只是个简化版。另外cp -i在覆盖文件前会询问,虽然交互会打断脚本,但在终端手动操作时它能救你一命——我至少有两次差一点用cp覆盖了同名的线上配置文件,全靠-i拦下来。
提示:
cp的别名问题在交互式shell里很常见。很多发行版默认把cp别名成了cp -i,脚本里要绕过别名可以写\cp或者/usr/bin/cp。
3. scp命令:远程传输表面的便利,和暗处必须知道的三个限制
scp的最大卖点是"零配置传输":只要目标机器有SSH服务,你有账号密码或密钥,就能传文件。但便利伴随着限制,我用它踩过不少坑。
3.1 基础用法和端口、目录上传的关键差异
最基础的几条命令大家都熟:
bash复制# 推送本地文件到远程
scp /data/app.tar.gz user@1.2.3.4:/opt/backup/
# 拉取远程文件到本地
scp user@1.2.3.4:/opt/backup/app.tar.gz /data/
# 复制目录
scp -r /data/config user@1.2.3.4:/opt/
注意端口参数是大写的-P,不是小写-p。小写-p在scp里是保留时间戳的意思,我第一次用scp -p 2222时直接被拒绝连接,后来才发现端口是大写P。习惯之后还是容易跟ssh的-p搞混,因为ssh用的是小写-p。解决方式就是:写到脚本里时永远显式用scp -P 2222,不要裸用默认端口,避免默认22端口被安全策略禁掉时一脸懵。
还有个隐藏细节:scp -r source/* user@host:/dest/ 和 scp -r source user@host:/dest/ 的语义不一样。前者只复制目录下的内容到dest,后者会在dest下面创建一个名为source的子目录。这个跟rsync的斜杠问题一样,容易在自动化脚本里产生路径漂移。
3.2 排除常见坑:Permission denied、known_hosts 和 -J 跳板机
scp报Permission denied是搜索榜前三的问题。实际原因通常不是密码错误,而是这几类:一是目标用户家目录或目标目录没有写权限,比如你想传到/root/下面,但用的是普通用户;二是ssh密钥权限不对,id_rsa必须是600,否则SSH直接拒绝加载;三是目标路径的所在文件系统只读或配额满了。排查顺序我建议是:先确认目标目录可写,再确认密钥权限,最后看服务端日志。
还有一个现代运维很常用的玩法:通过跳板机传输。以前要写scp -o ProxyCommand="ssh -W %h:%p jump_user@jump_host" src user@target:/dest,很长很反人类。OpenSSH 7.3之后支持-J参数,scp -J jump_user@jump_host src user@target:/dest就能直接走跳板机。热搜里的scp -j其实就是这个,注意-J大写。它绕过了手动配ProxyCommand的麻烦,还能跟ssh_config里的ProxyJump联动,是个很提效的更新。
3.3 scp的致命短板:没有断点续传,没有增量比较
我在迁移几百GB数据时吃过亏:公司内网不稳定,一个4GB的备份包传到一半网络闪断,scp直接抛出"Connection reset by peer",然后你发现目标端留下一个4GB的垃圾文件,还得手动清理。再传一次,又是从头开始。一次两次能忍,数据量大时真的能逼疯人。
另外,scp不会检测目标端是否已有相同文件。它就是一个无脑的流式传输,不计算差异、不校验完整性。-p参数也只是保留时间戳,并不是一致性校验。所以现在我的原则是:小于1GB的临时文件、跨机器随手拷个包,用scp没问题;任何需要认真对待的传输和备份,直接上rsync。
4. rsync命令:核心增量同步原理,以及那些让人抓狂的参数细节
rsync是我最依赖的命令,没有之一。它能在本地跑,也能走SSH远程跑,还能以daemon模式跑,功能非常强大。但正因为强大,参数也多,误用参数导致的故障很多。
4.1 增量同步算法是怎么工作:为什么它比scp快
快速解释一下:rsync会把文件切成固定大小的数据块,为每个块计算两个校验值(一个弱滚动校验和一个强MD5校验),然后发送给接收端。接收端在自己的文件里寻找匹配的块,只把不匹配的部分和文件元数据传过去。所以对于"两端已经基本一致、只有少量改动"的同步任务,rsync传输的数据量远小于文件本身,这是scp无法比拟的。
实际使用中要注意:这个算法只在处理"目标端已经存在同一个文件"时有效。如果目标端什么也没有,第一次全量同步时它还是得把整个文件内容传过去,此时速度不一定比scp快,但它支持压缩和断点续传,综合体验仍然更好。
4.2 必会参数组合:-avz 和 --delete、--exclude 的配合
最常用的组合是:
bash复制rsync -avz --progress /data/ user@host:/backup/
-a归档模式:递归 + 保留权限、时间戳、软链接等,作用和cp -a类似。-v输出详细过程。-z传输时压缩,适合文本文件,对已经压缩过的jpg、zip反而增加CPU负担,带宽够时可以不写。
--delete是个双刃剑。它让目标端删除那些在源端已经不存在的文件,使两端完全一致。做镜像同步时它非常好用,比如把构建产物同步到Web服务器目录,旧文件必须清掉。但如果源端路径写错、挂载失败,--delete会立刻把目标端清空。我的经验法则是:第一次使用--delete时先跑一次-n(dry-run)预览要删除的文件列表,确认无误后再真正执行。而且尽量不要在源端目录临时挂载或动态变化的场景中用--delete,很容易误判。
--exclude用来排除不需要同步的内容,典型如:
bash复制rsync -avz --delete --exclude='cache/' --exclude='*.tmp' /web/ root@server:/var/www/
注意--exclude的路径匹配规则跟gitignore类似,如果你写--exclude='cache/',它匹配的是任意层级下的cache目录,而不是只匹配根路径,细节很多容易踩坑。
4.3 那个让人玄学的斜杠:/source/ 和 /source 到底差在哪
这条太重要了,我单独拿出来讲。rsync -avz /data/ user@host:/backup/,源路径末尾有斜杠,表示"把/data目录下的内容同步到/backup里",结果会是/backup/file1。rsync -avz /data user@host:/backup/,源路径末尾没有斜杠,表示"把/data整个目录放进去",结果是/backup/data/file1。
我在给团队写部署脚本时,不止一次看到有人因为多写或少写一个斜杠,导致目标端目录结构和预期完全不同。判断方法很简单:记住"有斜杠是目录里的东西,没斜杠是目录本身"。目标端末尾的斜杠影响相对小,一般建议都加上,表示目标是目录。
4.4 卡死的真相和应对:--partial、--timeout、--bwlimit
热搜词里有个"rsync复制文件卡死",我看过很多现场,大部分不是真死,是大文件传输慢,或者网络中断后默认行为卡在等待重连上。解决方案:
- 加
--partial:保留部分传输的文件,下次同步可以续传。 - 加
--timeout=300:如果I/O阻塞超过300秒就断开,避免永远卡住。 - 加
--bwlimit=20000:限制带宽为20MB/s(单位是KB/s),防止同步任务吃光业务带宽,这个参数在做线上服务备份时几乎是必加的。
组合示例:
bash复制rsync -avz --partial --timeout=300 --bwlimit=20000 --progress /data/ user@host:/backup/
还有一个经常被忽略的:rsync默认会读取整个文件做校验,如果目标文件系统是NFS或者网盘挂载,性能会非常差。这时可以加--size-only,只比较文件大小,不比较内容,虽然可能漏掉大小相同但内容已变的文件,但在某些特定场景下能大幅提速。-c则强制做内容校验,更安全但更慢,按需选择。
4.5 通过SSH隧道使用rsync:加密与公网安全
远程同步时我喜欢明确指定协议外壳:
bash复制rsync -avz -e "ssh -p 2222 -i /path/key" /data/ user@host:/backup/
-e参数可以指定任何远程shell,默认是ssh。如果你用的端口和密钥比较特殊,一定要显式写-e。同时注意,rsync走SSH时,它会在两端分别执行一个rsync进程进行通信,所以两端都必须安装rsync。如果你在Windows上用Cygwin或WSL,还需要保证rsync版本兼容,太老的版本可能不支持某些参数。
5. 三兄弟综合对比与实际选型决策:什么场景用哪个
很多人的困惑不是不会用命令,而是不知道该用哪个。我整理了一张表,所有结论都是我实际压测和线上遇到过的:
| 场景 | 推荐命令 | 核心原因 |
|---|---|---|
| 本地目录快速复制,需保留完整属性 | cp -a --reflink=auto |
简单直接,保留软链接和元数据 |
| 同分区内做“伪复制”省空间 | cp -l |
硬链接不占额外数据块 |
| 随手传一个小文件到远程 | scp |
命令简短,不需要额外参数 |
| 传大文件到远程,担心断网 | rsync -avz --partial |
断点续传,防呆 |
| 线上服务器目录与本地保持一致 | rsync -avz --delete |
增量同步 + 删除多余文件 |
| 定时增量备份数据库文件 | rsync -avz --bwlimit --exclude |
带宽可控,只传差异 |
| 一次性迁移整台服务器数据 | rsync(或先tar再rsync) |
可断点续传,可压缩,可保留属性 |
选型时有一个大原则:scp处理"一次性、小体量、不敏感(其实加密了)的搬运";rsync处理"重复性、大体积、需要校验的同步";cp只处理单机内部的事情。三者的关系不是谁替代谁,而是各管一段。
补充一个很多人不知道的点:rsync也可以完全本地用,比如rsync -av /data/ /backup/。它的效果类似cp -a,但支持--delete、--exclude、--partial这些cp做不到的选项,所以在本地做增量备份时,rsync同样吊打cp。如果你在做备份脚本,我建议把cp限制在"手动应急复制",自动化任务一律用rsync。
6. 实战案例:一次服务器迁移中如何组合使用这三个命令
纸上谈兵到这里,我分享一个上周刚做的线上迁移实战。场景:一台源服务器上的网站目录和MySQL数据目录需要迁移到新机器,带宽有限,尽可能不停机。
6.1 准备与全量传输
先在源机器上确认目录情况,然后执行第一轮全量同步:
bash复制rsync -avz --partial --bwlimit=30000 \
--exclude='tmp/' --exclude='log/*.log' \
/var/www/html/ user@newserver:/var/www/html/
这里我用--bwlimit=30000,单位是KB/s,也就是限制在不超过30MB/s,避免同步时打满网卡影响线上业务。第一轮同步花了一段时间,但这无所谓,因为后面还有增量。
MySQL数据目录我选择先停库再同步,保证一致:
bash复制systemctl stop mysql
rsync -avz --partial --bwlimit=30000 /var/lib/mysql/ user@newserver:/var/lib/mysql/
systemctl start mysql
为什么不直接scp?因为MySQL数据目录里可能有几百MB甚至几十GB的ibd文件,一旦网络抖动,scp失败就得从头再来。rsync的--partial让我可以开安全模式。
6.2 增量同步和切换验证
网站和数据库都同步完后,我在低峰期做一个短暂的维护窗口,停掉服务,做最后一轮增量同步。此时因为大部分数据已经在目标机,rsync只需要传变化的部分,几十GB的目录基本一两分钟就能补完:
bash复制systemctl stop nginx mysql
rsync -avz --delete --partial --bwlimit=30000 \
/var/www/html/ user@newserver:/var/www/html/
rsync -avz --delete --partial --bwlimit=30000 \
/var/lib/mysql/ user@newserver:/var/lib/mysql/
systemctl start mysql nginx
之后在新服务器上验证文件数、目录权限、进程状态,确认无误后切DNS。整个迁移过程几乎没有明显停机窗口,全靠rsync的增量能力。
6.3 复盘:哪些地方原本可以用scp,哪些必须用rsync
如果只是迁移一个静态HTML首页,scp两三秒就搞定,用rsync反而显得杀鸡用牛刀。但在真实服务器迁移里,要同步的文件成千上万,目录层级复杂,还有各种软链接和权限要求,scp -r根本无法保证属性完整,也不可能断点续传。所以我的原则是:正式任务全用rsync,scp只做临时下载上传。
还有个小技巧:本地备份时,我经常把cp -a和rsync配合使用——先用cp -a把当前目录复制成一个带时间戳的快照,再用rsync --link-dest做增量备份的硬链接合并,效果远好于单纯用cp。这个偏高级,有兴趣可以查rsync --link-dest的用法,能实现类似Time Machine的备份目录结构。
7. 关于权限、安全和易错点的最后唠叨
写到这里,我觉得有必要把散落在各处的易错点集中划个重点。
scp和rsync都依赖SSH,所以SSH本身的安全配置会直接影响它们。我建议所有自动化脚本都使用密钥认证,不要用密码,并且把私钥权限设为600。公钥放进目标机的authorized_keys后,scp和rsync可以做到免交互,这在cron定时同步中是必须的。如果不想把主密钥放进自动化流程,可以用ssh-add配合ssh-agent,或者为同步单独生成一对受限密钥。
权限问题的坑不止在SSH层。rsync -a保留属主和权限时,如果目标端没有root权限或者rsync不是以root身份运行,它可能无法完整保留所有属性,导致同步后的文件属主漂移。遇到这种情况,要么在目标端用root跑rsync,要么把-a改成-rltD之类更宽松的组合,并配合--chown和--chmod调整属主权限。
目录路径里的空格和特殊字符也要警惕。新版scp和rsync都要求路径中的特殊字符正确转义,否则会报错。其实最稳妥的做法是避免在要同步的目录名里出现空格,如果历史遗留没法改,就用引号把完整路径包起来,并且加--protect-args参数让rsync正确处理。
卡死问题的预防再补充一句:除了--timeout,rsync还支持--contimeout,专门设置连接阶段的超时。在网络环境恶劣的跨地域同步中,我会同时设置--contimeout=30 --timeout=600,避免同步任务莫名其妙hang住不退出。
最后,如果你在Windows上使用Cygwin或WSL,安装rsync后需要注意路径转换问题。Cygwin的rsync在传输Windows路径时经常因为盘符号和反斜杠出错,我通常会在WSL里操作,或者用绝对路径加正斜杠,比如/cygdrive/d/data/,这样才能正常工作。
这三个命令用好了,日常文件管理和自动化备份会顺畅很多。我自己的习惯是:能用rsync的自动化任务绝不用scp,能用cp -a的本地复制绝不裸用cp -r。这套习惯救了我很多次,也希望你从今天开始少踩几个坑。
