导航
导航
文章目录󰁋
  1. 一、scp 其实就是跑在 ssh 上的 cp
  2. 二、两个方向的基本写法
    1. 2.1 从服务器下载文件
    2. 2.2 递归拷贝目录
    3. 2.3 传目录到服务器
  3. 三、大小写和参数顺序上的几个坑
    1. 3.1 -P 和 -p
    2. 3.2 通配符是本地 shell 展开的
    3. 3.3 -C 压缩和 -l 限速
  4. 四、五类常见报错怎么排
  5. 五、免密配置,以及 ssh config 能省掉多少事
  6. 六、什么时候该换成 rsync
  7. 七、sftp 的位置,以及 scp 这些年的变化
  8. 总结
  9. 参考

Linux之scp传输文件,从基础用法到rsync取舍

构建产物打好了,要往测试机上放一份。你敲下 scp -p 36000 -r dist user@1.2.3.4:/home/data/,回车之后终端不动了,几十秒后报一个 ssh: connect to host 1.2.3.4 port 22: Connection refused。端口明明写了啊。

这个错我踩过不止一次,问题出在 p 的大小写上。scp 指定端口用的是大写 -P,小写 -p 是保留文件的修改时间和权限位,跟端口一点关系没有。而 ssh 命令恰恰相反,它用的是小写 -p。两个天天一起用的命令,在同一个参数上约定相反。

这篇把 scp 的用法从头理一遍,顺带把免密配置、常见报错的排查路径,以及什么时候该换成 rsync 说清楚。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • scp 底层到底走的什么通道,为什么它天然是加密的
  • 上传和下载两个方向的写法,路径顺序怎么记
  • -P-p 的大小写坑,以及 -r 递归的边界
  • 通配符在 scp 里由谁展开,为什么 .next 这类隐藏目录传不上去
  • 免密配置怎么做,~/.ssh/config 能帮你省掉多少参数
  • 五类常见报错的排查顺序
  • 传大目录、断点续传、增量同步这些场景为什么该换 rsync
  • sftp 的取舍,以及新版 OpenSSH 对 scp 的态度变化

一、scp 其实就是跑在 ssh 上的 cp

scp 的全称是 secure copy,名字里的 secure 不是它自己实现的。它做的事情很简单,建立一条 ssh 连接,然后在这条连接上把文件内容送过去。

所以你能 ssh 上去的机器,基本就能 scpssh 连不上的机器,scp 也一定连不上。这个关系很重要,排查问题的时候先用 ssh 试一次,能把一半的可能性直接排除掉。

同样的道理,ssh 那套认证机制 scp 全部复用。你配了密钥登录,scp 就不用输密码;服务器改了 SSH 端口,scp 也得跟着改。传输过程是加密的,这一点跟 FTP 那种明文协议完全不同,往公网传东西的时候这是个硬性优势。

理解了这层关系,scp 的参数就不用死记了,它无非是「ssh 的连接参数」加上「cp 的复制参数」拼起来的。

二、两个方向的基本写法

scp 的参数顺序和 cp 一样,永远是「源在前,目标在后」。区别只是源和目标里可以有一个带上 用户@主机: 前缀。

2.1 从服务器下载文件

在本地电脑上执行,注意最后要写清楚本地的落地路径。

scp username@servername:/path/filename /tmp/local_destination

比如把 192.168.0.101 上的 /home/poetry/test.txt 拉到本地的 ~/Downloads/

scp root@192.168.0.101:/home/poetry/test.txt ~/Downloads/

原文这条命令只写了远端路径,把本地目标路径漏了。漏掉的话 scp 会报 usage 错误,不会默认下到当前目录,这点和 wget 不一样。想下到当前目录得显式写个 .

2.2 递归拷贝目录

传目录必须加 -r,不加的话 scp 会告诉你 not a regular file 然后什么都不做。

scp -P 36000 -r user_00@192.168.1.201:/usr/local/services/nginx/conf ~/Downloads/

这里的 -P 36000 是远端 SSH 服务监听的端口,不是 22 的时候必须显式指定。

2.3 传目录到服务器

方向反过来,把带 用户@主机: 的那一侧挪到后面就行。

scp -r /tmp/local_dir username@servername:remote_dir

远端路径不以 / 开头的话,是相对于该用户家目录的相对路径。生产脚本里我一律写绝对路径,相对路径在换了个部署账号之后会悄悄传到别的地方去,这种错很难发现。

三、大小写和参数顺序上的几个坑

3.1 -P 和 -p

前面提过一次,这里单独拎出来说,因为它是最高频的翻车点。

scp-P 大写才是端口,小写 -p 是 preserve,保留文件的 mtime、atime 和权限位。ssh 的端口参数则是小写 -p。真要说原因,scp 的小写 -p 是从 cp -p 继承过来的,端口只好让给大写。

记不住的话有个笨办法,把端口写进 ~/.ssh/config 里,之后两个命令都不用带端口参数了。第五节会讲怎么配。

3.2 通配符是本地 shell 展开的

原文里有这么一条命令,注释说 deployBuildFiles 下的 .next 默认不上传到服务器。

scp -P36000 -r deployBuildFiles/** user_00@192.168.1.201:/home/data/services/goods-prev.yesdat.com/

这个现象值得展开讲讲,因为它不是 scp 的行为,是 shell 的行为。

deployBuildFiles/** 这串东西在 scp 拿到之前,就已经被本地 shell 替换成一堆具体的路径了。而 bash 默认不启用 globstar*** 的效果完全一样,只匹配一层;更关键的是,glob 默认不匹配以 . 开头的文件名。所以 .next.env 这类隐藏文件根本没进入参数列表,scp 压根不知道它们的存在。

所以「.next 默认不上传」这句话,准确的说法是「.next 没被通配符匹配到」。

想连隐藏文件一起传,最省事的做法是直接传目录本身而不是它下面的内容。

scp -P 36000 -r deployBuildFiles user_00@192.168.1.201:/home/data/services/goods-prev.yesdat.com/

这样远端会多出一层 deployBuildFiles 目录,可以接受的话这是最不容易出错的写法。要是必须打平,bash 里可以 shopt -s dotglob 打开隐藏文件匹配,但这个开关会影响整个脚本的后续行为,我一般不推荐在部署脚本里随手开。

顺着这块聊,通配符如果写在远端那一侧,展开的就不是本地 shell 而是远端 shell 了,路径里带空格和特殊字符的时候还得考虑两层引号转义。这也是 scp 在复杂场景下容易失控的地方之一。

3.3 -C 压缩和 -l 限速

传文本类的文件(日志、SQL 备份、前端产物里的 JS)加个 -C 开压缩,跨机房传的时候能省不少时间。传已经压缩过的东西(图片、视频、tar.gz)加 -C 是白费 CPU,甚至更慢。

-l 可以限带宽,单位是 Kbit/s。白天在办公网传几个 G 的备份,不限速的话整个办公室的网都会被你吃掉。

scp -C -l 8000 -P 36000 backup.sql user_00@192.168.1.201:/data/backup/

四、五类常见报错怎么排

scp 的报错信息普遍比较简短,但对应的原因其实很好区分。

Permission denied (publickey,password),这是认证没过。先单独 ssh 一次确认账号密码或者密钥对不对,如果 ssh 能上去 scp 却不行,看看是不是密钥有 passphrase 而脚本环境里没有 ssh-agent。

Permission denied 但发生在传输开始之后,那是远端目录的写权限问题,跟登录无关。ssh 上去 ls -ld 看一眼目标目录属主。

No such file or directoryscp 不会帮你创建中间目录,目标路径的父目录必须已经存在。这跟 mkdir -p 的习惯不一样,很多人在这卡住。

Host key verification failed,服务器重装或者 IP 复用之后,本机 ~/.ssh/known_hosts 里存的指纹对不上了。正确做法是确认这台机器确实是你自己的,然后 ssh-keygen -R 主机名 把旧记录删掉,不要图省事全局关掉 host key 检查。

卡住不动没有任何输出,多半是网络层被防火墙 DROP 了(不是 REJECT,所以不返回 Connection refused,只能等超时)。加 -v 能看到卡在哪一步,是 TCP 建连还是 SSH 握手,一眼就分得清。

排查顺序我一般是这样:先 ssh 一次看连通性和认证,再 ls -ld 看目标目录权限,最后才去看 scp 的参数写没写对。

五、免密配置,以及 ssh config 能省掉多少事

每次传文件都输一遍密码,写进脚本更是没法自动化。密钥登录配一次就一劳永逸。

本地生成密钥对,已经有 ~/.ssh/id_rsa.pub 的话跳过这步。

ssh-keygen -t ed25519 -C "deploy@laptop"

然后把公钥推到服务器上。

ssh-copy-id -p 36000 user_00@192.168.1.201

ssh-copy-id 做的事情就是把你的公钥追加到远端的 ~/.ssh/authorized_keys 里,顺便把权限位设对。手工做也行,但权限位这块很容易错,~/.ssh 必须是 700,authorized_keys 必须是 600,权限太宽 sshd 会直接拒绝这个公钥,而且日志里不一定给你写清楚原因。这个我排查过一次,最后是看 /var/log/secure 才发现的。

配好之后再进一步,把连接参数写进 ~/.ssh/config

Host prev
HostName 192.168.1.201
User user_00
Port 36000
IdentityFile ~/.ssh/id_ed25519

之后所有命令都可以只写 prev 这个别名。

scp -r dist prev:/home/data/services/goods-prev.yesdat.com/
ssh prev
rsync -avz dist/ prev:/home/data/services/goods-prev.yesdat.com/

端口、用户名、密钥路径全都不用带了,前面说的 -P 大小写问题也就自然消失了。我自己的感受是,这个配置文件是提升 SSH 相关操作体验最划算的一件事,五分钟配好,后面几年都受益。

六、什么时候该换成 rsync

scp 的模型是「把源文件从头到尾读一遍,全量写到目标」。这个模型简单可靠,但有两个硬伤。

一是没有增量。前端项目 dist 目录几百个文件,你只改了一个 JS,scp 还是把几百个文件重传一遍。

二是没有断点续传。传到 90% 网断了,重来一次还是从 0 开始。传几百兆的时候这很折磨。

rsync 就是来解决这两件事的。

rsync -avz --partial --progress dist/ prev:/home/data/services/goods-prev.yesdat.com/

拆开看这几个参数。-a 归档模式,等价于保留权限、时间戳、软链接、递归这一整套;-v 打印传了哪些文件;-z 传输时压缩;--partial 保留传了一半的临时文件,下次接着传;--progress 显示进度。

rsync 的增量判断默认看文件大小和修改时间,不一致才传。真正厉害的是跨机器传输时它会用滚动校验算法,只把文件里变化的那些块发过去,大文件小改动的场景能省掉绝大部分流量。

这里有个坑要注意,rsync 源路径末尾的斜杠是有语义的。dist/ 表示「把 dist 里的内容传过去」,dist 表示「把 dist 这个目录本身传过去」。少一个斜杠就会多套一层目录,这个我踩过,部署完页面 404,找了半天才发现产物躺在 .../dist/dist/ 里。

另外两个常用的参数。--delete 让目标端删掉源端已经不存在的文件,做「完全镜像」的时候需要,但它是把双刃剑,路径写错就是删库级别的事故,加之前务必先用 -n(dry-run)跑一遍看输出。--exclude 排除不想传的东西,比如 --exclude 'node_modules' --exclude '.git'

我的经验是这样分工:一次性传单个文件,或者临时看一眼某个配置,用 scp 手快;只要是重复执行的部署流程、目录同步、大文件传输,一律上 rsync。前提是两端都装了 rsync,这是它唯一的门槛,容器镜像里经常没有。

七、sftp 的位置,以及 scp 这些年的变化

sftp 是第三个选项,它跑在同一条 SSH 通道上,但提供的是一个交互式会话,可以 lscdgetput,像个命令行版的 FTP 客户端。

什么时候它比 scp 顺手?你不确定远端目录结构,需要边看边传的时候。scp 得先 ssh 上去看一眼路径,退出来再拼命令,sftp 一个会话里就搞定了。它也支持非交互式的批处理模式,可以把一串命令写进文件里用 -b 执行。

还有一点值得提。这些年 OpenSSH 对 scp 的态度有变化,scp 用的那个古老协议存在一些设计上的问题(比较有名的是远端可以影响本地写入哪些文件),较新的 OpenSSH 版本里 scp 底层已经改成走 SFTP 协议,官方也建议优先使用 sftprsync

这个变化带来的实际影响是,某些依赖旧协议行为的写法(比如远端通配符展开的一些细节)在新老版本上表现可能不一致。具体你手上这台机器是什么行为,以 man scp 和 OpenSSH 官方公告为准,我不建议按某个记忆里的版本号去推断。

说实话我自己日常还是在敲 scp,肌肉记忆改不过来。但新写的部署脚本我已经全部换成 rsync 了,一方面是增量和断点续传确实需要,另一方面也是省得去纠结不同机器上 scp 的行为差异。

服务器上的其他日常操作,我另外整理过一篇 日常频繁使用的Linux命令,传完文件之后的验证和排查可以接着那篇看。想把这些传输动作包成可复用的部署脚本,shell入门 那篇里的参数处理和错误处理部分正好配套。

总结

scp 值得记的东西不多,核心就三件。

它跑在 ssh 上,所以 ssh 连不上的机器 scp 一定也连不上,排查从 ssh 开始最快。参数顺序永远是源在前目标在后,传目录必须 -r,指定端口是大写 -P(小写 -p 是保留时间戳权限,那是 cp 的遗产)。目标路径的父目录必须已经存在,scp 不会帮你 mkdir -p

通配符是本地 shell 展开的,隐藏文件默认匹配不到,所以传整个目录比传 目录/** 更不容易出错。

真正拉开效率差距的是后面两步。把连接参数写进 ~/.ssh/config,端口用户密钥全都不用再敲;重复执行的同步换成 rsync -avz --partial,增量加断点续传,改一个文件不用重传整个目录,但记住源路径末尾那个斜杠的语义,还有 --delete 前先 dry-run。

参考