Appearance
文件传输
远程传输文件在运维里几乎每天都会遇到——上传配置文件、下载日志、同步发布包、备份拉取。场景不同,工具选择也不同。这篇文章梳理 scp、sftp、rsync、lrzsz 各自的适用场景和容易踩的坑。
一、三个工具,各管一摊
| 工具 | 擅长什么 | 为什么不适合别的事 |
|---|---|---|
scp | 临时传一两个文件 | 不支持增量,重复传很多不变文件也全量 |
sftp | 交互式登录后浏览和取文件 | 交互式,不好写脚本自动化 |
rsync | 增量同步、保留属性、断点续传 | 只传一个几 KB 小文件没必要用它 |
lrzsz | 终端里临时拖一个小文件 | 依赖终端客户端支持 ZMODEM,生产批量传输不适合 |
选择的关键区别在于:scp 和 sftp 每次都是全量传完,rsync 会两边比对后只传差异。所以 scp 第一次传和第五次传代价一样,rsync 第二次以后的传输量可以小很多。
远程路径的写法:
text
user@host:/path/to/file本地传到远端:
text
本地路径 -> user@host:远端路径远端下载到本地:
text
user@host:远端路径 -> 本地路径传文件之前确认三件事可以避免一半以上的低级错误:
| 检查项 | 如果不检查 |
|---|---|
| 目标目录是否存在 | 想传到 /etc/nginx/conf.d/,结果目录不存在就变成了把文件内容写进 /etc/nginx/conf.d 这个文件 |
| 目标用户是否有写权限 | 权限不够直接 Permission denied,中间过程白费 |
| 会不会静默覆盖已有文件 | scp 默认直接覆盖,没有任何提示 |
二、scp
scp 走 SSH 加密,用法和 cp 很像,只是路径可以跨机器。
上传单个文件:
bash
scp ./app.conf root@192.168.10.129:/etc/myapp/app.conf下载文件:
bash
scp root@192.168.10.129:/var/log/messages ./messages-192.168.10.129指定端口和私钥:
bash
scp -P 2222 -i ~/.ssh/id_ed25519_ops ./app.conf root@192.168.10.129:/tmp/scp 的端口参数是大写 -P,ssh 是小写 -p。混到一起时容易敲错——scp -p 是保留文件属性,不是指定端口。
几个常用参数
复制整个目录:
bash
scp -r ./dist root@192.168.10.129:/opt/myapp/-r 递归复制。但 scp 传大目录时不比较两边的差异——源端 500 个文件,目标端已经有 499 个完全一样的,scp 还是全部再传一遍。重复同步的场景用 rsync 比 scp -r 合理。
保留文件的时间戳和权限:
bash
scp -p ./backup.tar.gz root@192.168.10.129:/backup/这里的 -p(小写)是 preserve,保留修改时间、访问时间和权限模式。
限速:
bash
scp -l 20480 ./bigfile.tar.gz root@192.168.10.129:/data/-l 的单位是 Kbit/s,20480 约等于 20 Mbit/s。跨机房传大文件或走生产网段时,不加限速可能打满链路影响线上服务。
启用 SSH 传输压缩:
bash
scp -C ./logs.tar root@192.168.10.129:/tmp/-C 让 SSH 在传输时压缩数据。对日志、文本、配置文件有效;对已经压缩过的 .tar.gz、.zip、镜像层文件,效果不大还会白白消耗 CPU。
如果 ~/.ssh/config 里有别名,scp 可以省掉很多参数:
Host test-rocky
HostName 192.168.10.129
User root
IdentityFile ~/.ssh/id_ed25519_ops
IdentitiesOnly yesbash
scp ./app.conf test-rocky:/etc/myapp/三、sftp
sftp 也是走 SSH,但它不一次性执行完退出,而是一个交互式客户端——连接进去后可以浏览、切换目录、上传、下载。
连接:
bash
sftp root@192.168.10.129进去后的常用命令:
| 命令 | 作用 | 对应的本地操作 |
|---|---|---|
pwd | 显示远端当前目录 | |
lpwd | 显示本地当前目录 | |
ls | 列出远端文件 | ssh host ls |
lls | 列出本地文件 | ls 本地 |
cd | 切换远端目录 | |
lcd | 切换本地目录 | cd 本地 |
put | 上传文件 | |
get | 下载文件 | |
mkdir | 创建远端目录 | |
rm | 删除远端文件 | |
bye | 退出 |
一次完整会话:
text
sftp> lpwd
Local working directory: /home/ops/pkg
sftp> pwd
Remote working directory: /root
sftp> cd /tmp
sftp> put app.tar.gz
sftp> get remote.log
sftp> bye批处理模式用 -b 传入命令列表:
bash
cat > sftp-batch.txt <<'EOF'
cd /tmp
put app.tar.gz
put app.conf
bye
EOF
sftp -b sftp-batch.txt root@192.168.10.129sftp 有断点续传命令 reput 和 reget:
text
sftp> reput bigfile.tar.gz # 续传上传
sftp> reget remote-bigfile.tar.gz # 续传下载但需要目标端已有部分文件且文件内容没被改过,不然续出来的文件是坏的。
sftp 的批处理模式适合简单固定的上传下载步骤。逻辑一复杂(条件判断、错误处理、增量判断)就吃力了,这时候直接用 rsync 比较好。
四、rsync 基础
rsync 的核心差异在增量:对比源端和目标端,只传变化的内容。文件没变就不传,文件变了只传差异部分。重复同步的场景下这个能力很重要。
安装:
bash
yum install rsync -y # RHEL 系
apt install rsync -y # Debian 系本地同步到远端:
bash
rsync -av ./dist/ root@192.168.10.129:/opt/myapp/dist/常用参数:
| 参数 | 做什么 |
|---|---|
-a | archive 模式:递归 + 保留权限、时间戳、软链接、属主属组(权限允许时) |
-v | 显示正在传输的文件列表 |
-z | 传输时压缩,适合文本类流量 |
-P | 等于 --partial --progress:保留未传完的文件(续传) + 显示进度 |
--delete | 删除目标端有但源端没有的文件 |
--dry-run | 预演,打印将要发生的变化,不实际执行 |
带进度和断点续传:
bash
rsync -avP ./large-dir/ root@192.168.10.129:/data/large-dir/-P 里的 --partial 会保留已经传完的部分文件块。网络断了下次再跑可以接着传,不用从头开始。大文件跨网传输时这个能力比 scp 更实用。
远端拉回本地:
bash
rsync -avP root@192.168.10.129:/var/log/nginx/ ./nginx-logs/通过非标准端口或指定私钥:
bash
rsync -avP -e 'ssh -p 2222' ./dist/ root@192.168.10.129:/opt/myapp/dist/-e 指定底层用的远程 shell 程序。这里传的是 ssh -p 2222,rsync 会用这个命令来建立连接。
目录末尾斜杠的差别
rsync 的源路径末尾有没有斜杠,结果完全不一样:
| 命令 | 结果 |
|---|---|
rsync -av ./dist/ host:/opt/app/ | 把 dist 里面的内容同步到 /opt/app/ 下 |
rsync -av ./dist host:/opt/app/ | 把 dist 这个目录本身放到 /opt/app/ 下,变成 /opt/app/dist/ |
不带斜杠 = 复制目录本身。带斜杠 = 复制目录里的内容。这个细节容易搞混,发布目录时先用 --dry-run 看一眼输出确认层级没错:
bash
rsync -avP --dry-run ./dist/ root@192.168.10.129:/opt/myapp/dist/--dry-run 不会实际修改目标端,只列出将要发生的变化。带 --delete 的同步命令尤其需要先预演——错误删除的代价远大于多花 10 秒看一眼输出。
五、文件属性:权限、时间戳、属主
传输文件不是单纯的"把数据拷过去"。权限、时间戳、属主、软链接这些属性,对服务能不能正常运行有直接影响。
| 属性 | 如果不正确处理 |
|---|---|
| 属主/属组 | 服务进程可能读不了配置,或者执行不了脚本 |
| 权限模式 | chmod +x 脚本变成不可执行;私钥权限过宽 sshd 拒绝 |
| 修改时间 | 增量同步的比对依据,乱了以后每次都是全量 |
| 软链接 | 丢失后变成拷贝链接目标的内容,或者变成断链 |
查看详细属性:
bash
ls -l deploy.sh
stat deploy.sh # 比 ls -l 更细:精确时间、inode、权限八进制rsync -a 尽量保留属性,但属主和属组能否保留取决于执行用户有没有权限:
bash
rsync -av ./scripts/ root@192.168.10.129:/opt/scripts/如果不是 root 登录,同步过去的文件属主通常会变成登录用户自己。这不一定有问题,但要确认目标服务进程的用户确实能读写这些文件。
只想保留权限和时间戳、不改属主:
bash
rsync -rltD --chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r ./dist/ app@192.168.10.129:/opt/myapp/dist/各参数拆开看:
| 参数 | 含义 |
|---|---|
-r | 递归目录 |
-l | 保留软链接(拷贝链接本身,不是链接指向的文件) |
-t | 保留文件修改时间 |
-D | 保留设备文件和特殊文件 |
--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r | 目标端强制设权限——目录所有人可读可进入,文件属主可写其他人只读 |
--chmod 的格式:D 代表目录,F 代表文件,u=属主,g=属组,o=其他人。Web 静态文件发布时这种显式权限声明比依赖源端权限更可靠。
另外一个常见问题:Windows 编辑的脚本传到 Linux 后,第一行的 #!/bin/bash 末尾带了 \r(CR),执行时报 /bin/bash^M: bad interpreter:
bash
file deploy.sh # 看是不是 CRLF
dos2unix deploy.sh # 转换换行符六、覆盖和删除是最危险的操作
scp 覆盖同名文件没有任何提示:
bash
scp ./nginx.conf root@192.168.10.129:/etc/nginx/nginx.conf如果传错文件或者传错路径,覆盖之后想要还原就只能靠备份。所以覆盖配置前,先备份远端文件:
bash
ssh root@192.168.10.129 \
'cp -a /etc/nginx/nginx.conf /etc/nginx/nginx.conf.$(date +%F-%H%M%S).bak'
scp ./nginx.conf root@192.168.10.129:/etc/nginx/nginx.confcp -a 保留原文件的权限和时间戳,回滚时和原来一致。
rsync --delete 更危险——它不只是覆盖,还删除目标端多出来的文件:
bash
rsync -avP --delete ./dist/ root@192.168.10.129:/opt/myapp/dist/这条命令的意思不是"把源端拷过去",而是"让目标端变得和源端一模一样"。目标端多出来的文件会被删掉。
可以先预演:
bash
rsync -avP --delete --dry-run ./dist/ root@192.168.10.129:/opt/myapp/dist/另外,路径写得越具体,误删范围越小。/opt/myapp/dist/ 比 /opt/myapp/ 安全得多——万一目录写错,删的是子目录,而不会把整个应用目录清掉。
rsync 的 --delete 是根据源端来删除目标端。如果命令把源和目标写反了,或者源目录是空的(比如挂载失败),那目的地就可能被清空。这种事故偶尔还能见到——所以 --dry-run 的目的不光是确认目录层级,也是为了确认源端内容确实是你想同步的那个。
七、传完之后校验
关键文件传完之后做校验,确认两边是一致的。源端生成哈希:
bash
sha256sum app.tar.gz远端计算:
bash
ssh root@192.168.10.129 'sha256sum /tmp/app.tar.gz'或者把校验文件一起传过去自动比较:
bash
sha256sum app.tar.gz > app.tar.gz.sha256
scp app.tar.gz app.tar.gz.sha256 root@192.168.10.129:/tmp/
ssh root@192.168.10.129 '
cd /tmp
sha256sum -c app.tar.gz.sha256
'sha256sum -c 读取校验文件,按里面的路径和哈希值比对。如果匹配,输出 app.tar.gz: OK;不匹配输出 app.tar.gz: FAILED。发布包、备份包、离线安装包,都适合加上这一步。
如果发现哈希不一致,说明文件在传输过程中损坏了——可能网络丢包、磁盘故障。TCP 有校验和但比较弱,应用层再做一次强校验(SHA-256)是对抗静默数据损坏的一个简单手段。
八、通过堡垒机传文件
有堡垒机之后,文件传输的方式取决于堡垒机的授权策略。常见方式:
| 方式 | 说明 |
|---|---|
| Web SFTP | 在浏览器里上传下载,走堡垒机审计 |
| 客户端 SFTP | 通过堡垒机代理连接目标资产 |
| 命令行 rsync/scp | 需要堡垒机或 ProxyJump 允许转发 |
| 工单附件 | 先走审批,由堡垒机侧分发文件到目标机器 |
如果堡垒机只允许 Web 文件管理,自己再单独开 SCP 通道直接连目标机器,等于绕过了审计。数据离开的时候没有记录,出了事反而解释不清楚,甚至被发现后本身就是一个违规操作的证据。
通过 JumpServer 这类堡垒机时,文件传输也会进审计。授权规则里要看二件事:
| 检查 | 为什么 |
|---|---|
| 授权是否允许文件传输 | 有些授权只给了"连接"动作,不包含"文件上传/下载" |
| 下载是否有审计记录 | 带出数据的操作必须能追溯 |
九、lrzsz:终端里临时拖文件
lrzsz 是 rz(接收/上传)和 sz(发送/下载)这一对命令,走 ZMODEM 协议,在终端里可以直接弹出文件选择对话框。
bash
yum install lrzsz -y
apt install lrzsz -y上传:
bash
rz # 客户端弹出文件选择框,选中后上传到当前目录下载:
bash
sz /tmp/debug.log # 客户端弹出保存路径对话框它能工作依赖终端客户端支持 ZMODEM。Xshell、SecureCRT、iTerm2 一般支持。很多 Web Terminal、Windows Terminal、VS Code 内置终端、浏览器终端不支持。
因为这个工具只在特定终端客户端上能用,而且操作过程不进入审计记录,所以只适合临时传一个很小的文件——比如排查问题时下载一段日志、上传一个临时用的脚本。正式发布包、备份包、大批量文件同步,还是要走 rsync 或堡垒机文件管理。
十、常见传输问题的排查
Permission denied
出现这个提示时,先区分是哪一层的权限问题。
SSH 认证层的拒绝:
text
Permission denied (publickey,password).这是没连上——key 不对、用户不对、或者认证组合不对。
文件系统层的拒绝:
text
scp: dest open "/etc/myapp/app.conf": Permission denied这个说明已经连上服务器了,但是目标用户没有写 /etc/myapp/ 目录的权限。可以先传到 /tmp,再在服务器上用 sudo 移动:
bash
scp ./app.conf ops@192.168.10.129:/tmp/app.conf
ssh ops@192.168.10.129 '
sudo install -m 0644 -o root -g root /tmp/app.conf /etc/myapp/app.conf
'install 命令比 mv 更适合部署单个配置文件——它可以在移动的同时设置权限、属主、属组,一步到位。
传输中断
scp 中断了只能重头传。大文件用 rsync -P:
bash
rsync -avP ./bigfile.tar.gz root@192.168.10.129:/data/-P 在中断后保留了已经接收到的文件片段,重跑时会自动续传。
文件名包含空格
本机路径加引号:
bash
scp "./app config.conf" root@192.168.10.129:/tmp/远端路径包含空格时,转义会更麻烦。服务器上文件和目录的命名最好用连字符 - 或下划线 _ 替代空格,可以避免很多不必要的引号和转义问题。