1. 从日常痛点说起:为什么你需要掌握这两个命令
做服务器运维或者经常和云端机器打交道的人,一定经历过这样的场景:本地写好的脚本、打包好的代码、收集好的日志,要从自己电脑上塞到服务器里,或者反过来从服务器里拖回来。一开始我都是靠宝塔面板或者各种图形化SFTP工具,偶尔传几个小文件倒也够用,可一旦涉及一批文件、几十个G的压缩包、或者断网重连这种倒霉事,图形工具卡到怀疑人生,进度条半天不动,断了还得从头再来,那种感觉真的很抓狂。
后来我开始正儿八经地收拾 scp 和 rsync 这两条命令,才意识到这俩才是远程传文件最靠谱的方式。scp 走 SSH 协议,简单直接,适合小文件和一次性传输;rsync 则更适合大目录、需要增量同步、或者频繁重复传输的场景。可以说,凡是做过一段时间运维或服务器开发的人,这两条命令躲不开,也避不掉,早一点掌握,后面省下来的时间绝对可观。
这篇文章我会从两条命令最基础的操作讲起,配合实际踩坑记录和排查思路,把上传、下载、同步这些事拉通讲清楚。适合刚接触 Linux 服务器的新手,也适合已经在用但从来没有系统整理过细节的老手。全文不绕弯子,直接按我平时在终端里敲命令的思路来写,你照着敲就能用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心概念拆解:scp 和 rsync 到底差在哪
2.1 scp 的定位:一个简单可靠的搬运工
scp 全称是 secure copy,基于 SSH 协议做文件拷贝。它的最大特点就是简单,几乎零学习成本,命令形式跟本地 cp 特别像,只是多了一个远程主机名和路径的写法。
bash复制# 本地文件上传到远程
scp localfile.txt user@remote:/home/user/
# 远程文件下载到本地
scp user@remote:/home/user/remotefile.txt ./
你只要记得:scp 就是把你那条 cp 命令里的路径换成 user@host:/path 的形式,语法就通了。它会严格按照一次性把文件从头到尾传完的逻辑执行,传的过程中不做增量判断,也不对比远端是否已经存在同样内容,所以它在安全性上有天然优势——因为是全量拷贝,你对远端文件的内容有绝对把握。劣势也很明显:文件越多、越大,耗时越长,中途断网就白费。
2.2 rsync 的定位:增量同步才是它的看家本领
rsync 的设计思路跟 scp 完全不同。它不是简单地把文件搬过去,而是会先做一次“内容比对”,只传输真正有差异的数据块。这个设计最初是为了解决本地目录镜像同步的问题,后来配合 SSH 隧道就成了跨机器的传文件利器。
bash复制# 基础本地目录同步
rsync -avh source_dir/ dest_dir/
# 通过 SSH 远程同步
rsync -avh /local/path/ user@remote:/remote/path/
rsync 对比文件的逻辑比很多人想象的要聪明。它默认比对的是文件大小和修改时间,两者都一样就直接跳过,不一样才传输。如果你愿意给它加一条参数,它还能做增量校验,连内容都算一遍哈希,保证两端文件字节级一致。这种机制让它特别适合重复执行的同步任务,你可以在本地脚本里每天定时跑一次,反正没变化的内容不会占用带宽。
2.3 根据场景选工具,少走很多弯路
很多人纠结到底学哪个,其实这两条命令的定位非常清晰,选谁不选谁主要看你的场景。
| 场景 | 推荐工具 | 原因 |
|---|---|---|
| 临时传一两个文件 | scp | 简单直接,不需要额外参数 |
| 传单一大文件(如压缩包) | 二者均可 | 若完整传完且不再重复传,scp 更简单 |
| 频繁同步同一目录 | rsync | 增量传输,重复执行成本极低 |
| 超大目录、万级小文件 | rsync | 性能远超 scp,还能断点续传 |
| 传输后需保证两端完全一致 | rsync | 自带文件一致性校验能力 |
| 远程机器上没有 rsync 服务端 | scp | rsync 需要双方都安装,scp 只需 SSH |
我个人的习惯是:日常手工传一两个文件就磕 scp,处理目录、批量文件、或者需要反复同步的任务就一律交给 rsync。这不是我偏爱某条命令,而是这两者的底层设计本身就不是一个赛道,用了最合适的工具,效率才会上去。
3. scp 实操细节与完整命令拆解
3.1 scp 上传路径完整演示
先说最基础的上传。假设我本地有个 /home/localuser/project.tar.gz,要传到服务器 /data/backups/ 下面:
bash复制scp /home/localuser/project.tar.gz deploy@192.168.1.100:/data/backups/
敲下回车后会提示输入远程用户的密码,输完密码就开始传输。这个命令没有任何花哨的操作,就是老老实实干了两件事:建立 SSH 连接,然后把文件流式写入远端路径。
如果服务器 SSH 端口不是默认的 22,你需要用大写 -P 指定端口:
bash复制scp -P 2222 /home/localuser/project.tar.gz deploy@192.168.1.100:/data/backups/
这里的 -P 是大写,跟 ssh 命令里的 -p 小写不是一回事,这是个特别容易搞错的小细节。我身边已经有不止两个人因为这里大小写写错,卡在端口无法连接的问题上。
递归传目录的话加 -r:
bash复制scp -r /home/localuser/myfolder/ deploy@192.168.1.100:/data/backups/myfolder
注意这里目录结尾的斜杠非常关键。scp -r local_dir/ remote:/path 和 scp -r local_dir remote:/path 行为并不完全一致,前者倾向于把目录内容拷贝到目标目录下,后者会把目录本身放过去。为了减少心智负担,我建议你在远程路径上始终写清楚期望落地的完整目录名。
3.2 scp 下载路径完整演示
下载就是把上传命令里的来源和目标调换位置:
bash复制scp deploy@192.168.1.100:/data/backups/project.tar.gz ./
这条命令的意思是把远程机器上的文件拉到当前目录。需要注意,远程服务器上如果存在通配符 *,scp 默认不展开远程通配符,它是把 * 当字面路径去匹配的,这跟很多人预想的不一样。需要匹配远程文件名时,要提前处理好文件名列表,或者在远程端生成好文件清单再拉。
3.3 scp 的保活和加速技巧
大型文件从国外服务器拉取时,我踩过很多次连接中途断掉的坑。scp 的传输性能很大程度上取决于 SSH 连接本身的稳定性,如果你的网络环境一般,建议在命令前加上:
bash复制scp -o ConnectTimeout=30 -o ServerAliveInterval=15 deploy@192.168.1.100:/data/bigfile.tar.gz ./
ServerAliveInterval=15 表示每 15 秒发一个保活包,防止长时间无数据传输导致连接被中间设备静默杀掉。这在跨国传输或走某些不稳定的宽带时效果立竿见影。
另外,scp 本身不支持断点续传,一旦断了就要从头再来。如果你明确知道要传的是大文件,而且网络不稳定,那我建议你直接用 rsync 而不是 scp,后面讲到 rsync 时会有断点续传的处理方案。
4. rsync 实操细节与核心参数详解
4.1 rsync 的基本语法结构
rsync 的语法虽然表面上看起来复杂,核心骨架依然非常简单:
bash复制rsync [选项] 源路径 目标路径
源路径和目标路径可以同时是本地路径,也可以其中一个带 user@host: 前缀变成远程路径。当两端都不带远程主机前缀时,它就是一条本地同步命令,很多服务器上做目录镜像就是这种用法。
bash复制# 本地两个目录同步
rsync -avh /data/app/ /data/app_backup/
# 本地上传到远程
rsync -avh /data/app/ deploy@192.168.1.100:/data/app/
# 远程拉取到本地
rsync -avh deploy@192.168.1.100:/data/app/ /data/app/
无论哪种组合,它都能正常工作,这一点非常灵活。
4.2 最常用参数组合与含义
rsync 的参数特别多,但日常高频使用的并不算复杂。我的习惯是把几个核心参数固定记下来,按需组合,而不是背全篇手册。
| 参数 | 作用 | 说明 |
|---|---|---|
-a |
归档模式 | 递归传目录,保留权限、时间、软链接等属性 |
-v |
详细输出 | 显示每个文件传输状态,便于确认 |
-h |
人类可读 | 显示 K/M/G 单位,输出更直观 |
-z |
传输压缩 | 适合文本类文件,节省带宽,CPU 会有额外开销 |
--progress |
显示进度 | 传大文件时能实时看到进度和速率 |
--delete |
删除多余文件 | 让目标目录和源目录完全一致 |
--exclude |
排除规则 | 跳过不需要同步的文件或目录 |
--partial |
保留部分传输 | 断点续传的核心参数 |
最常用的简化组合是:
bash复制rsync -avh --progress /data/app/ deploy@192.168.1.100:/data/app/
这条命令对新人来说已经足够覆盖大多数场景。如果你是第一次用 rsync,什么都不懂也建议先记住这一条,以后有特殊需求再往里面加参数。
4.3 断点续传的正确打开方式
rsync 的断点续传是我最看重的功能。假设我传一个 20G 的数据库备份文件,网络闪断导致传输中断,scp 直接宣告失败,但 rsync 不一样。你只需要在命令中加上 --partial 参数,它会保留已经传输部分的数据文件,下次执行同一命令时,自动从断点位置继续传,而不是从头重来。
bash复制rsync -avh --partial --progress backup.sql.gz deploy@192.168.1.100:/data/backup/
我第一次遇到网络闪断时还在用 scp,文件传了 18G,断掉后只能重新来,那种挫败感差点让我把键盘砸了。后来换成 rsync 加 --partial,同样的情况最多损失几分钟的传输量。就这一条,已经足够让你心甘情愿放弃 scp 来传大文件。
4.4 双向同步与目录一致性的狠活
rsync 的 --delete 参数是一个需要格外注意的参数。它的作用是让目标目录跟源目录完全保持一致,目标目录里凡是源目录没有的、或者比源目录多余的文件,统统删除。
比如我想把本地 /data/uploads/ 下的内容完整镜像到远程:
bash复制rsync -avh --delete /data/uploads/ deploy@192.168.1.100:/data/uploads/
这个命令执行后,远程目录里所有本地不存在的内容都会被删掉。听起来很方便,但危险性也很高。一旦你源路径写漏了一层目录,或者源目录为空,--delete 会直接清空对方目录。我吃过的亏是:本地目录误写成 /data/,结果把服务器上别的重要文件一起删掉了。所以我的使用原则是:除非你明确要做镜像同步,否则不加 --delete。真要用,也先在测试目录上实验一遍再说。
4.5 过滤规则与排除不必要文件
同步项目代码时,本地的临时文件、缓存目录、日志文件通常不想传过去。这时候要加 --exclude:
bash复制rsync -avh --exclude='node_modules' --exclude='*.tmp' --exclude='.git/' /data/myapp/ deploy@192.168.1.100:/data/myapp/
--exclude 后面接的是路径模式,支持通配符,也支持目录名。需要注意,规则匹配的是传输路径的相对路径,而不是全路径。比如上面的 --exclude='node_modules',它会匹配任何层级下叫 node_modules 的目录,非常方便。如果你不想这么宽泛,可以写成 /node_modules/,表示仅匹配当前源目录根下的 node_modules。
4.6 压缩传输的取舍
-z 参数能在传输前压缩数据,适合纯文本或日志,但处理视频、图片、压缩包这类本身已经高度压缩的文件时,打开压缩反而浪费 CPU 资源,传输速率也没有明显提升。我的建议是:文本代码文件加 -z,媒体文件和压缩文件不加。虽然 rsync 默认压缩算法已经很不错,但这一点取舍在传输超大型媒体文件时还是能感知到区别的,尤其是服务器 CPU 本来就比较紧张的情况下更明显。
5. 进阶实战:密钥配置与批量脚本化操作
5.1 SSH 密钥登录:告别每次输入密码
scp 和 rsync 都需要走 SSH 认证,每次手动输密码不仅麻烦,而且脚本自动化时根本没有人工输密码的机会。解决这个问题的最好办法就是配置 SSH 密钥登录。
首先生成密钥对:
bash复制ssh-keygen -t ed25519 -C "deploy@localhost" -f ~/.ssh/deploy_ed25519
这条命令会用 ed25519 算法生成一对密钥:私钥 deploy_ed25519 留在本地,公钥 deploy_ed25519.pub 需要安装到远程服务器上。在远程服务器执行:
bash复制mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "公钥内容" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
配置好之后,scp 和 rsync 就都能直接免密执行,这对后面的脚本化至关重要。
5.2 使用 SSH 配置文件简化命令
经常连多台服务器的人,建议把主机信息写进 ~/.ssh/config,这样命令可以短很多,也不用每次敲完整用户名和 IP:
code复制Host web-server
HostName 192.168.1.100
Port 2222
User deploy
IdentityFile ~/.ssh/deploy_ed25519
配置完成后,原来这行命令:
bash复制scp -P 2222 /data/app.tar.gz deploy@192.168.1.100:/data/
直接缩短为:
bash复制scp /data/app.tar.gz web-server:/data/
rsync 同理:
bash复制rsync -avh /data/app/ web-server:/data/app/
这种管理方式的好处不仅仅是让命令短,更重要的是把服务器连接参数集中管理,不要在每次传文件时临时找地址和端口。我见过的生产事故里,有不少就是端口号记错或者 IP 地址抄串行导致文件传错了机器。
5.3 批量脚本示例:日志定时回收
实际运维中,把多台机器的日志可靠地汇聚到一台存储机是常见需求。写一个简单的循环脚本可以把这事做得非常轻松:
bash复制#!/bin/bash
SERVERS=("log-01" "log-02" "log-03")
DEST_HOST="storage-server"
DEST_DIR="/data/logs/"
for host in "${SERVERS[@]}"; do
rsync -avhz --partial --progress "$host:/var/log/app/*.log" "$DEST_HOST:$DEST_DIR$host/"
done
脚本会把每台机器的日志按主机名分目录拉取到存储服务器上。-z 对日志文件有很好的压缩效果,--partial 避免因某条日志文件较大而中途失败重来。这个脚本配合 crontab 定时执行,你就能实现基本的日志集中回收能力。
5.4 自动化传输时的隐藏坑
自动化的时候有几个隐藏的坑,我每次写脚本都会格外注意:
一是 rsync 的远程路径如果带空格或特殊字符,必须正确转义或加引号。SSH 到远程执行 rsync 时,路径解析规则和本地不太一样,远程路径中的空格会直接被拆成多个参数,不处理就会失败。稳妥做法是你加个双引号把远程路径包起来,比如:
bash复制rsync -avh local.txt "deploy@192.168.1.100:/data/my dir/"
二是 rsync 默认使用源文件的时间戳和权限去覆盖远端文件,如果写入端权限不够,会出现明明本地文件没问题,远端同步却报错的情况。这种情况多半要检查远端目录的写权限,而不是怀疑 rsync 本身。
三是脚本里最好加日志输出。我一般在 crontab 配置里把 rsync 输出重定向到一个日志文件,这样失败时能回溯原因,而不是干瞪眼:
bash复制0 2 * * * /usr/local/bin/sync_logs.sh >> /var/log/rsync_sync.log 2>&1
5.5 rsync 作为定时任务的退出码判断
脚本化时,rsync 的退出码是判断成功与否的关键。0 表示成功;11 表示文件传输时出现了输入输出错误;23 表示部分文件没能完整传输;24 表示源文件在传输过程中发生了变化(比如日志正在被写入)。这些退出码如果你不在脚本里判断,rsync 默认可能依然打印成功信息,误导你的告警策略。
我写脚本时习惯这样捕获异常:
bash复制if ! rsync -avh --partial /data/app/ deploy@192.168.1.100:/data/app/; then
echo "[ERROR] rsync failed for app sync" >> /var/log/rsync_error.log
exit 1
fi
这样只要同步有异常,日志里一定会有记录,不会出现“命令明明失败了但脚本状态还是正常”的尴尬。
6. 常见问题与排查技巧实录
6.1 连接超时或无法解析主机名
这个问题多出现在没有配置 DNS 或者网络不通的情况下。先检查远程主机是否可以 ping 通,再看端口能不能连上:
bash复制ping 192.168.1.100
nc -vz 192.168.1.100 22
如果 ping 通但 nc 连不上 22 端口,大概率是远程机器的 sshd 服务没有启动,或者安全组、云防火墙把端口拦了。云服务器尤其常见,控制台的安全组规则默认只放过某些端口,换了非标准 SSH 端口时需要同步修改安全组配置。
6.2 rsync 传输突然中断但远端已有部分文件
前面我提过用 --partial 解决,这里再详细展开一下。rsync 中部分传输的文件会带一个临时后缀,等完整传输完成后重命名。传输中断时,临时文件会留在远端目录里。下一次执行时不加 --partial,rsync 会认为这个文件是别人放进去的无关文件,要么重新传,要么可能被 --delete 删除。加了 --partial 后,它就能识别这个临时文件,并从中断的位置继续传输。
这也是很多人在生产上踩过的坑:第一次 rsync 传了一些文件,中断后第二次重新跑,发现居然没有断点续传,最后查下来是没加 --partial。记住这个参数,续传问题就彻底解决。
6.3 文件名乱码或路径带空格
中文文件名在 scp 和 rsync 下基本没问题,前提是两端系统字符集一致。通常建议服务器端 LANG 设置成统一 UTF-8,否则你传过去的中文文件名会显示乱码。曾在某个老系统上踩过类似的坑,本地是 UTF-8,服务器是 GBK,文件传过去后名字全是问号,最后只能靠重命名补救。
路径带空格时,要么在整个远程路径外面加引号,要么直接用 \ 转义。但要注意远程路径的空格和本地路径的空格处理方式不同,最保险的还是引号包住。
6.4 小文件特别多时 rsync 的优势
成千上万个小文件用 scp 传输时,每个文件都要单独建立 SSH 通道交互,速度极慢。而 rsync 是单连接内完成全部文件比对和传输,性能差距非常悬殊。我曾经同步过一个几万个小文件组成的代码目录,scp 跑了一个下午都没完,换成 rsync 后不到二十分钟就结束了。这个案例几乎是我每次安利 rsync 必讲的例子。
6.5 传输性能调优经验
带宽充足但传输慢,极大概率是瓶颈在单线程 SSH 加密上。scp 和 rsync 默认都是单连接单线程,对多核机器来说有点浪费。rsync 加 -z 压缩能显著提升带宽利用率,尤其在文本文件占比高的情况下。但加密本身还是消耗 CPU 的,服务器性能较弱时反而可能更慢。
如果条件允许,考虑把文件先打成一个 tar 包再传,往往效果更好。打个比方,你有一万个 10KB 的小文件,逐个传和打包成一个大文件传,两者在底层 IO 上的开销完全不同。打包后 rsync 只需要处理一次文件流,调度开销和对磁盘的随机读写请求都会大幅下降。
6.6 scp 提示权限错误但本地权限没问题
这种问题常常是远程目标目录的权限不允许写入。比如目标目录的属主是 root,而你是普通用户,即使你有 SSH 登录权限,也无法在目录里写入文件。需要换用有权限的用户,或者提前把远端目录的属主改掉。用 ls -ld /data/backups 可以快速确认目录权限。
6.7 rsync 同步后文件权限变了怎么办
用 -a 参数同步时,rsync 会连文件权限一起同步到目标端。如果目标端运行服务需要使用不同用户读取文件,同步后权限不符会导致服务异常。这种情况可以不用 -a,改用 -rlpt之类的手工组合,只保留需要的属性,或者同步后手动执行一次 chown 修正属主。
7. 实际操作中的心得体会与扩展建议
7.1 我最常用的一组命令快照
从最开始频繁翻手册,到后来肌肉记忆直接敲出来,我现在最常用的命令基本固定成下面几个:
bash复制# 传单个文件
scp file.tar.gz web-server:/data/
# 同步项目目录(排除垃圾目录)
rsync -avh --progress --exclude='node_modules' --exclude='.git' /data/project/ web-server:/data/project/
# 大文件断电续传
rsync -avh --partial --progress bigfile.sql.gz web-server:/data/
# 完整镜像同步
rsync -avh --delete /data/site/ web-server:/data/site/
这几条覆盖了我在日常运维里九成以上的文件传输需求。每次遇到不确定的命令,打开终端先 man rsync 查阅参数细节,也比去搜索引擎翻二手信息高效得多。
7.2 对新手的一点建议:先吃透 -avh 再扩展
如果你是刚接触 rsync 的新手,我强烈建议先别碰那些花哨参数,老老实实把 -avh 组合的意思吃透,再逐步往上加 --delete、--exclude、--partial 这些进阶特性。我记得自己刚学时一次性把所有参数背下来,结果真遇到问题时反而不知道哪个参数出了错,排查起来一团浆糊。反而是从最简单的 -avh 开始,踩过的坑多了,对每个参数的理解才会真正内化。
7.3 后续可以怎么延伸
掌握了 scp 和 rsync 之后,下一步可以延伸到 rsyncd 服务模式,它可以不依赖 SSH,通过专用端口直接对外提供同步服务,适合做内部服务器之间的高速同步。再往下深入,inotify 配合 rsync 能实现目录变更后的准实时同步,这也是很多轻量级发布系统的基础原理。从一个简单的文件上传需求出发,你能逐步走到自动化发布、容灾备份这些更高阶的运维场景,这也是命令行工具最大的魅力所在:每一条命令背后,都藏着一条通往更深领域的小路。
