scp 命令详解与参数实测(Ubuntu 22.04)
以 Ubuntu 22.04 OpenSSH 8.9p1 为实测环境,逐参数验证 scp:本地与远程互传、-r 递归、-p 时间戳保留、-P 端口与大小写陷阱、-i 密钥免密、-3 远程对传、-O/-s 协议选择、-l 限速与进度条解读、-C 压缩、-c 加密算法与错误排障,每项附真实回显。
scp 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 scp 的每一个常用参数——本地与远程互传、远程路径语法、递归复制、时间戳保留、压缩传输、端口指定、密钥免密、远程对传、协议选择、限速与进度条解读、加密算法指定与错误排障,全部命令与回显已在 Ubuntu 22.04(OpenSSH 8.9p1)实测通过。
一、环境信息
本节列出实测环境的完整信息,文中所有命令与回显均来自该环境,读者在同版本系统上可复现同样的结果。
- 操作系统:Ubuntu 22.04.5 LTS(jammy),架构 x86_64,内核
6.8.0-138-generic - OpenSSH 套件:openssh-client / openssh-server 均已安装,版本
OpenSSH_8.9p1 Ubuntu-3ubuntu0.18,加密库OpenSSL 3.0.2——scp 与 ssh 同属 OpenSSH 套件,版本一致 - sshd 监听:默认实例监听
22端口;为实测-P参数,另启动了一个监听2222端口的测试实例 - 测试账号:普通用户
tester(密码tester123)、tester1(密码tester1pass)——两个账号均为公开演示环境专用的测试凭据 - 密码自动化工具:
sshpass 1.09(apt 安装),用于在非交互脚本中自动填充密码 - 相关路径:
/root/.ssh/(客户端私钥与 known_hosts)、/root/demo/scp/(演示数据目录) - 网络方式:传输目标全部使用本机回环地址
127.0.0.1自连自收,本地与"远程"同机,全程不依赖公网
二、命令概览
2.1 语法与测试数据
scp(secure copy)是 OpenSSH 套件中基于 SSH 加密通道的文件复制工具,语法与传统的 cp 类似,但源或目标可以是远程路径。其基本语法为:
scp [选项] 源路径 ... 目标路径
路径写法是 scp 的核心规则:
- 本地路径:直接写,如
/root/demo/scp/note.txt或./note.txt; - 远程路径:
[user@]host:路径冒号形式,冒号前是主机(可带用户名),冒号后是远端文件系统路径,如tester@127.0.0.1:~/note.txt; - 远程到远程:源与目标都是远程路径,如
scp user1@host1:/a.txt user2@host2:/b.txt。
由此构成三种最常用的形态:
scp 本地文件 user@host:路径——本地推送到远程;scp user@host:远程文件 本地路径——从远程拉取到本地;scp user1@host1:文件 user2@host2:文件——两台远程主机之间复制。
⚠️ 注意:远程路径的冒号是硬规则——
tester@127.0.0.1~/note.txt少了冒号会被当成一个奇怪的本地文件名;而 scp 的端口参数是大写-P,与 ssh 的小写-p恰好相反(因为 scp 的小写-p已经被「保留时间戳」占用,见 3.5 与 3.7 节)。
本文的测试数据约定:
- 目标主机:
127.0.0.1(回环自连),默认端口 22,测试实例端口 2222; - 登录账号:
tester,密码tester123,用sshpass -p tester123前缀实现非交互传输(仅演示环境如此使用,生产环境推荐密钥认证,见 3.8 节); - 演示文件:
/root/demo/scp/src/note.txt(内容三行、35 字节)等,每个实验块自带数据准备与清理,可独立复现。
2.2 usage 用法总览
不带任何参数直接执行 scp,会打印完整的 usage 用法说明(逐行照抄如下):
$ scp
usage: scp [-346ABCOpqRrsTv] [-c cipher] [-D sftp_server_path] [-F ssh_config]
[-i identity_file] [-J destination] [-l limit]
[-o ssh_option] [-P port] [-S program] source ... target
从中可以看到 scp 的参数全集,本文逐项实测其中最常用的一批,对应关系如下:
| 参数 | 作用 | 实测小节 |
|---|---|---|
| (无参数) | 本地↔远程互传 | 3.1 |
| (远程路径写法) | 相对路径 / 绝对路径 / 复制到目录 | 3.2 |
| (同名目标) | 静默覆盖行为 | 3.3 |
-r | 递归复制目录 | 3.4 |
-p | 保留文件时间戳 | 3.5 |
-C | 压缩传输 | 3.6 |
-P | 指定连接端口 | 3.7 |
-i | 指定私钥文件 | 3.8 |
-3 | 远程→远程经本地中转 | 3.9 |
-O / -s | 传统 SCP 协议 / SFTP 协议 | 3.10 |
-l | 限速传输(含进度条解读) | 3.11 |
-q / -v | 静默 / 调试输出 | 3.12 |
-c | 指定加密算法 | 3.13 |
-o | 向底层 ssh 透传参数 | 3.14 |
💡 说明:usage 中还有
-A、-B(批处理模式)、-D(指定 sftp-server 路径)、-F(指定 ssh 配置文件)、-J(跳板机)、-R、-S(指定 ssh 程序)、-T(禁用严格文件名检查)、-4/-6(强制 IPv4/IPv6)等参数,使用频率较低,本文不逐项展开。
2.3 版本信息
scp 自身没有独立的版本查询参数(usage 中无 -V),它与 ssh 共用同一套程序库,版本通过 ssh -V 查询:
ssh -V
OpenSSH_8.9p1 Ubuntu-3ubuntu0.18, OpenSSL 3.0.2 15 Mar 2022
💡 说明:
ssh -V的输出打印在标准错误上(用ssh -V 2>&1才能重定向捕获)。排障时报版本号报这一个就够——scp 与 ssh、sftp 同版本发布。特别注意:OpenSSH 9.0 起 scp 的默认传输协议从传统 SCP 协议切换为 SFTP 协议(8.9 仍默认传统协议,可用-s/-O显式指定,见 3.10 节),跨版本迁移脚本前先确认两端版本。
三、参数逐项实测
3.1 基础形态:本地→远程与远程→本地
先验证 scp 最基础的推、拉两个方向。一个重要观察:传输成功时 scp 完全静默,不输出任何内容,只有退出码可以确认成败。
准备一个三行的演示文件,并清理远端旧数据:
mkdir -p /root/demo/scp/src /root/demo/scp/dst && printf "hello from scp\nline two\nline three\n" > /root/demo/scp/src/note.txt && chmod 644 /root/demo/scp/src/note.txt && cat /root/demo/scp/src/note.txt
hello from scp
line two
line three
sshpass -p tester123 ssh tester@127.0.0.1 "rm -f ~/note.txt; echo remote-cleaned"
remote-cleaned
本地→远程推送(源是本地路径,目标是 tester@127.0.0.1:~/):
sshpass -p tester123 scp /root/demo/scp/src/note.txt tester@127.0.0.1:~/; echo rc=$?
rc=0
登录远端核对文件内容与大小:
sshpass -p tester123 ssh tester@127.0.0.1 "ls -l ~/note.txt; cat ~/note.txt"
-rw-r--r-- 1 tester tester 35 Oct 15 20:09 /home/tester/note.txt
hello from scp
line two
line three
远程→本地拉取(源是 tester@127.0.0.1:~/note.txt,目标是本地路径):
rm -f /root/demo/scp/dst/note.txt; sshpass -p tester123 scp tester@127.0.0.1:~/note.txt /root/demo/scp/dst/note.txt; echo rc=$?
rc=0
cat /root/demo/scp/dst/note.txt
hello from scp
line two
line three
验证结论:
- 推、拉只是源与目标的位置互换,命令结构完全对称;
- 成功时无任何输出,
rc=0是唯一的成功信号;出错的回显与退出码见 3.15 节; - 目标以
/结尾时表示"复制到该目录下"(保持源文件名),~/即远端家目录。
💡 说明:
sshpass -p tester123是演示环境的自动化辅助手段(把密码喂给 scp 底层的 ssh 认证)。生产环境不建议在命令行明文传密码(会留在 Shell 历史与进程列表中),推荐 3.8 节的密钥认证。
3.2 远程路径语法:相对路径、绝对路径与复制到目录
远程冒号后的路径支持三种写法:相对远端家目录的相对路径、绝对路径、以斜杠结尾的目录。先在远端准备一个子目录文件:
sshpass -p tester123 ssh tester@127.0.0.1 "mkdir -p ~/docs && printf deep > ~/docs/deep.txt && echo prepared"
prepared
写法一:相对路径(docs/deep.txt 相对远端家目录 /home/tester):
sshpass -p tester123 scp tester@127.0.0.1:docs/deep.txt /root/demo/scp/dst/deep.txt; echo rc=$?; cat /root/demo/scp/dst/deep.txt
rc=0
deep
写法二:绝对路径:
sshpass -p tester123 scp tester@127.0.0.1:/home/tester/docs/deep.txt /root/demo/scp/dst/deep2.txt; echo rc=$?; cat /root/demo/scp/dst/deep2.txt
rc=0
deep
写法三:目标以 / 结尾,复制到远端目录下(文件名保持不变):
sshpass -p tester123 scp /root/demo/scp/src/note.txt tester@127.0.0.1:/home/tester/docs/; echo rc=$?
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "ls -l ~/docs/"
total 8
-rw-rw-r-- 1 tester tester 4 Oct 15 20:09 deep.txt
-rw-r--r-- 1 tester tester 35 Oct 15 20:09 note.txt
💡 说明:注意
ls -l里两个文件的权限不同——deep.txt是-rw-rw-r--(远端 shell 用 printf 创建,受远端账号 umask 影响得到 664),而传输过来的note.txt是-rw-r--r--。这是因为传统 SCP 协议在传输时会把源文件的权限位一并携带(3.10 节-v输出中的Sink: C0644 35 note.txt就是在传递「权限 0644、35 字节」这一信息),远端按该权限落盘。
3.3 覆盖行为:静默覆盖,无任何确认
scp 没有交互确认,也没有"跳过已存在文件"的选项——同名文件直接覆盖。用两版内容做对照:
printf "version-1\n" > /tmp/over.txt; sshpass -p tester123 scp /tmp/over.txt tester@127.0.0.1:~/over.txt; sshpass -p tester123 ssh tester@127.0.0.1 "cat ~/over.txt"
version-1
printf "version-2-overwritten\n" > /tmp/over.txt; sshpass -p tester123 scp /tmp/over.txt tester@127.0.0.1:~/over.txt; echo rc=$?
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "cat ~/over.txt"
version-2-overwritten
⚠️ 注意:第二次传输零提示覆盖了远端文件——没有 overwrite? 提问,没有备份。脚本化批量部署时要意识到 scp 是"无条件覆盖"语义;需要保护旧文件时应先在远端改名或备份,或改用支持
--backup的 rsync。
3.4 -r:递归复制目录
复制目录必须加 -r,否则直接报错。准备一个两级目录并先清空远端:
mkdir -p /root/demo/scp/tree/sub && printf A > /root/demo/scp/tree/a.txt && printf B > /root/demo/scp/tree/sub/b.txt && find /root/demo/scp/tree | sort
/root/demo/scp/tree
/root/demo/scp/tree/a.txt
/root/demo/scp/tree/sub
/root/demo/scp/tree/sub/b.txt
sshpass -p tester123 ssh tester@127.0.0.1 "rm -rf ~/tree; echo remote-tree-cleaned"
remote-tree-cleaned
不带 -r 复制目录,报错形态:
sshpass -p tester123 scp /root/demo/scp/tree tester@127.0.0.1:~/ 2>&1; echo rc=$?
/root/demo/scp/tree: not a regular file
rc=1
加 -r 后整棵目录树落盘:
sshpass -p tester123 scp -r /root/demo/scp/tree tester@127.0.0.1:~/; echo rc=$?
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "find ~/tree | sort"
/home/tester/tree
/home/tester/tree/a.txt
/home/tester/tree/sub
/home/tester/tree/sub/b.txt
💡 说明:报错信息是
not a regular file而不是 cp 的omitting directory,脚本里判断这类错误要按 scp 的措辞来。另注意目标写法对结果结构的影响——scp -r tree tester@127.0.0.1:~/会把 tree 目录本身复制到家目录下;若目标写成~/tree2/则远端出现tree2/目录,内容与源目录对齐。
3.5 -p:保留文件修改时间
-p(小写)保留源文件的修改时间(mtime)与访问时间(atime)。准备一个时间戳被改到 2026-01-01、权限 640 的文件:
printf preserve > /root/demo/scp/keep.txt && touch -d "2026-01-01 10:00:00" /root/demo/scp/keep.txt && chmod 640 /root/demo/scp/keep.txt && stat -c "%a %y %n" /root/demo/scp/keep.txt
640 2026-01-01 10:00:00.000000000 +0000 /root/demo/scp/keep.txt
对照组:不加 -p 传输,远端权限与时间:
sshpass -p tester123 scp /root/demo/scp/keep.txt tester@127.0.0.1:~/keep.txt; echo rc=$?
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "stat -c %a ~/keep.txt; stat -c %y ~/keep.txt"
640
2026-10-15 20:09:51.718524651 +0000
实验组:清理远端后加 -p 传输:
sshpass -p tester123 scp -p /root/demo/scp/keep.txt tester@127.0.0.1:~/keep.txt; echo rc=$?
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "stat -c %a ~/keep.txt; stat -c %y ~/keep.txt"
640
2026-01-01 10:00:00.000000000 +0000
验证结论:
- 不加
-p:权限位640依然保留(权限随传统 SCP 协议的C0640指令传递),但 mtime 变成了传输时刻2026-10-15 20:09:51; - 加
-p:mtime 精确保留为源文件的2026-01-01 10:00:00.000000000。
⚠️ 注意:
-p与端口的-P只差大小写,两个参数经常在同一命令中出现(scp -P 2222 -p file host:~/),务必区分。备份归档场景建议始终带-p,否则按 mtime 做增量判断的下游工具会全部失准。
💡 技术拓展:SFTP 模式下的属性行为与传统模式一致(3.10 节实测
-s模式同样默认落盘权限 640、加-s -p后 mtime 精确保留为 2026-05-05 08:00:00),两个协议在这一点上无差异。需要更完整的属性保留(如属主、符号链接、设备文件)请看 rsync 的-a参数,它在 scp 之上做了更细的拆分。
3.6 -C:压缩传输
-C 让底层的 SSH 连接启用压缩,对高冗余数据(日志、重复文本)能显著减少网络字节量。本机环回无法直接测量网络字节,但压缩是否生效可以在 -v 调试输出的密钥交换行里看到证据。准备 500KB 高重复文本:
yes "the quick brown fox jumps over the lazy dog" | head -c 500000 > /root/demo/scp/pack.txt && chmod 644 /root/demo/scp/pack.txt && wc -c /root/demo/scp/pack.txt
500000 /root/demo/scp/pack.txt
不加 -C,抓底层加密协商行:
sshpass -p tester123 scp -v /root/demo/scp/pack.txt tester@127.0.0.1:~/pack.txt 2>&1 | grep "kex: client->server cipher"
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
加 -C 再抓一次:
sshpass -p tester123 scp -C -v /root/demo/scp/pack.txt tester@127.0.0.1:~/pack.txt 2>&1 | grep "kex: client->server cipher"
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: zlib@openssh.com
💡 说明:
compression: none与compression: zlib@openssh.com就是-C生效的直接证据——它等价于给底层 ssh 加了-o Compression=yes。已压缩数据(zip、jpg、视频)再开-C几乎无收益还白耗 CPU;WAN 慢链路上传日志类文本时收益最大。
3.7 -P:指定端口(大写!与 ssh 的 -p 相反)
scp 连接非默认端口用大写 -P。用监听 2222 的 sshd 测试实例验证:
sshpass -p tester123 scp -P 2222 /root/demo/scp/src/note.txt tester@127.0.0.1:~/port2222.txt; echo rc=$?
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "cat ~/port2222.txt; rm -f ~/port2222.txt"
hello from scp
line two
line three
写成小写 -p 2222 会发生什么——2222 被当成了源文件名(小写 -p 是"保留时间戳"参数,见 3.5 节):
sshpass -p tester123 scp -p 2222 /root/demo/scp/src/note.txt tester@127.0.0.1:~/ 2>&1; echo rc=$?
2222: No such file or directory
rc=1
指定一个没有服务监听的端口,报错形态:
sshpass -p tester123 scp -P 2221 /root/demo/scp/src/note.txt tester@127.0.0.1:~/ 2>&1; echo rc=$?
ssh: connect to host 127.0.0.1 port 2221: Connection refused
lost connection
rc=1
⚠️ 注意:
Connection refused表示主机可达但端口无人监听,排查方向是服务端ss -tln;Connection timed out则是网络层不通或防火墙丢包。lost connection是 scp 层的收尾报错,真正的原因在它上面一行。
3.8 -i:指定私钥文件(ed25519 密钥免密传输)
与 ssh 一样,scp 支持用 -i 指定私钥做公钥认证,实现无密码自动传输。本节从零实测:生成密钥 → ssh-copy-id 分发公钥 → -i 免密推拉。
第一步,生成一把专用 ed25519 密钥(-N "" 表示不设口令):
ssh-keygen -t ed25519 -f /root/.ssh/demo_scp_ed25519 -N "" -C "scp-demo"
Generating public/private ed25519 key pair.
Your identification has been saved in /root/.ssh/demo_scp_ed25519
Your public key has been saved in /root/.ssh/demo_scp_ed25519.pub
The key fingerprint is:
SHA256:JsK2p87MOjUIrdYg4t7zrZ2dAg4aq5Lavesl0t8U8XU scp-demo
The key's randomart image is:
+--[ED25519 256]--+
| |
| |
| . . . E |
|+... o . . |
|+oo.+ . S . |
|.+.=o+ o . |
|o.*.*.+ . |
|o=.O Bo+o . |
|* o=&=o+oo |
+----[SHA256]-----+
第二步,把公钥装进远端账号(此后该账号接受这把私钥的认证):
sshpass -p tester123 ssh-copy-id -i /root/.ssh/demo_scp_ed25519.pub tester@127.0.0.1 2>&1 | tail -2
and check to make sure that only the key(s) you wanted were added.
第三步,-i 免密推送(不再需要 sshpass):
scp -i /root/.ssh/demo_scp_ed25519 /root/demo/scp/src/note.txt tester@127.0.0.1:~/key-sent.txt; echo rc=$?
rc=0
sshpass -p tester123 ssh tester@127.0.0.1 "cat ~/key-sent.txt"
hello from scp
line two
line three
-i 拉取同样免密:
scp -i /root/.ssh/demo_scp_ed25519 tester@127.0.0.1:~/key-sent.txt /root/demo/scp/dst/key-pulled.txt; echo rc=$?; cat /root/demo/scp/dst/key-pulled.txt
rc=0
hello from scp
line two
line three
错误形态:-i 误指向公钥文件(.pub),ssh 拒绝加载过于开放的"私钥":
scp -i /root/.ssh/demo_scp_ed25519.pub /root/demo/scp/src/note.txt tester@127.0.0.1:~/x.txt 2>&1; echo rc=$?
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/root/.ssh/demo_scp_ed25519.pub' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/root/.ssh/demo_scp_ed25519.pub": bad permissions
Permission denied, please try again.
Permission denied, please try again.
tester@127.0.0.1: Permission denied (publickey,password).
lost connection
rc=1
💡 说明:私钥文件权限必须是 600(仅属主可读),否则 ssh 会拒绝使用并报
WARNING: UNPROTECTED PRIVATE KEY FILE。密钥认证配好后,scp 自动化脚本、定时任务都不再依赖 sshpass,这是生产环境的标准做法。
3.9 -3:两台远程主机之间经本地中转
源与目标都是远程主机时,-3 指定数据经由本地主机中转(先从源主机收到本地,再发往目标主机)。前提是本地对两台主机都有认证凭据——实测中两个账号都装了同一把公钥(3.8 节方法对 tester1 再执行一次即可)。
把 tester 家目录的文件复制到 tester1 家目录:
scp -3 -i /root/.ssh/demo_scp_ed25519 tester@127.0.0.1:~/key-sent.txt tester1@127.0.0.1:~/three.txt; echo rc=$?
rc=0
sshpass -p tester1pass ssh tester1@127.0.0.1 "cat ~/three.txt"
hello from scp
line two
line three
用 -v 观察 -3 的连接结构——本地发起了两条独立连接:
scp -3 -v -i /root/.ssh/demo_scp_ed25519 tester@127.0.0.1:~/key-sent.txt tester1@127.0.0.1:~/three.txt 2>&1 | grep -c "debug1: Connecting to 127.0.0.1"
2
再看两条连接各自的用途(Executing 行显示本地让两个远端分别执行的程序):
scp -3 -v -i /root/.ssh/demo_scp_ed25519 tester@127.0.0.1:~/key-sent.txt tester1@127.0.0.1:~/three.txt 2>&1 | grep -E "Executing|Connecting to"
Executing: program /usr/bin/ssh host 127.0.0.1, user tester, command scp -v -f ~/key-sent.txt
Executing: 2nd program /usr/bin/ssh host 127.0.0.1, user tester1, command scp -v -t ~/three.txt
debug1: Connecting to 127.0.0.1 [127.0.0.1] port 22.
debug1: Connecting to 127.0.0.1 [127.0.0.1] port 22.
不加 -3 的对照(8.9 的传统协议同样通过本地两条连接完成,远端命令相同):
scp -v -i /root/.ssh/demo_scp_ed25519 tester@127.0.0.1:~/key-sent.txt tester1@127.0.0.1:~/three.txt 2>&1 | grep -E "Executing|Connecting to"
Executing: program /usr/bin/ssh host 127.0.0.1, user tester, command scp -v -f ~/key-sent.txt
Executing: 2nd program /usr/bin/ssh host 127.0.0.1, user tester1, command scp -v -t ~/three.txt
debug1: Connecting to 127.0.0.1 [127.0.0.1] port 22.
debug1: Connecting to 127.0.0.1 [127.0.0.1] port 22.
💡 说明:
-f(from)是源端的读进程,-t(to)是目标端的写进程——两条连接分别跑一个 scp,数据流经本地中转。在 OpenSSH 8.9 的传统协议下,-3与不带的连接结构实测相同;-3的差异主要在语义与报错定位(两端进度与错误都回到本地展示,且按手册说明第二台主机进入批处理模式、密码只提示一次)。更老版本的 OpenSSH 中,不带-3的远程对传是"源主机直接连目标主机",此时本地并不需要目标主机的凭据——跨版本使用时要核对实际行为。
3.10 -O 与 -s:传统 SCP 协议与 SFTP 协议
scp 的数据传输有两种底层协议,可用 -O(大写字母 O)与 -s 显式选择:
- 传统 SCP 协议(
-O):OpenSSH 8.9 及之前的默认,远端执行scp程序收发数据; - SFTP 协议(
-s):OpenSSH 9.0 起的默认,远端走 sftp-server 子系统。
传统协议的 -v 特征——远端执行 scp -v -t,且有 Sink: 行(接收进程打印的文件属性指令,含权限与字节数):
sshpass -p tester123 scp -O -v /root/demo/scp/src/note.txt tester@127.0.0.1:~/o-mode.txt 2>&1 | grep -E "Executing|Sink:"
Executing: program /usr/bin/ssh host 127.0.0.1, user tester, command scp -v -t ~/o-mode.txt
Sink: C0644 35 note.txt
SFTP 协议的 -v 特征——远端执行的程序变成 sftp,走子系统通道:
sshpass -p tester123 scp -s -v /root/demo/scp/src/note.txt tester@127.0.0.1:~/s-mode.txt 2>&1 | grep -E "Sending file|sftp|debug1: Sending subsystem"
Executing: program /usr/bin/ssh host 127.0.0.1, user tester, command sftp
debug1: Sending subsystem: sftp
两种模式传输结果一致(均 rc=0),SFTP 模式下属性行为也一致(-s 不加 -p 时 mtime 为传输时刻、权限 640 保留;-s -p 后 mtime 精确保留,实测过程与 3.5 节相同)。
💡 说明:
Sink: C0644 35 note.txt正是 3.5 节"权限默认随源传递"的协议层证据——C指令携带了权限(0644)与字节数(35)。协议切换的影响主要在脚本兼容性:SFTP 模式对远端 shell 通配符、引号的处理与传统模式不同,且要求服务端启用 sftp 子系统;迁移到 9.0+ 后老脚本行为异常时,可先用-O回退旧协议过渡。
3.11 -l:限速传输(单位是 Kbit/s)与进度条解读
-l 限制传输带宽,单位是 Kbit/s(千比特每秒)而不是 KB/s——这是最容易误判的一点。生成 5MB 测试文件,限速 1000 Kbit/s 理论速率约 125 KB/s:
dd if=/dev/zero of=/root/demo/scp/five.bin bs=1M count=5 2>/dev/null && chmod 644 /root/demo/scp/five.bin && ls -l /root/demo/scp/five.bin
-rw-r--r-- 1 root root 5242880 Oct 15 20:09 /root/demo/scp/five.bin
限速传输耗时(time 计时,5MB ÷ 125KB/s ≈ 41 秒):
time script -qec "sshpass -p tester123 scp -l 1000 /root/demo/scp/five.bin tester@127.0.0.1:~/five.bin" /dev/null
real 0m41.165s
user 0m0.029s
sys 0m0.034s
不限速对照,仅 0.195 秒:
time script -qec "sshpass -p tester123 scp /root/demo/scp/five.bin tester@127.0.0.1:~/five.bin" /dev/null
real 0m0.195s
user 0m0.015s
sys 0m0.008s
进度条解读:进度条只在标准错误连接到终端时显示(脚本重定向时看不到),用 script 命令分配伪终端模拟真实终端场景(进度条用回车符原地刷新,这里把回车符转换为换行以便逐帧阅读;认证改用 ControlMaster 复用连接,避免 sshpass 包装层吞掉进度条输出)。截取限速传输的前三帧:
script -qec "scp -o ControlPath=/tmp/cm-scp -l 1000 /root/demo/scp/five.bin tester@127.0.0.1:~/five.bin" /dev/null | tr "\r" "\n" | grep -a "%" | head -3
five.bin 0% 0 0.0KB/s --:-- ETA
five.bin 2% 144KB 140.5KB/s 00:35 ETA
five.bin 5% 272KB 138.9KB/s 00:34 ETA
最后两帧:
script -qec "scp -o ControlPath=/tmp/cm-scp -l 1000 /root/demo/scp/five.bin tester@127.0.0.1:~/five.bin" /dev/null | tr "\r" "\n" | grep -a "%" | tail -2
five.bin 98% 5024KB 125.1KB/s 00:00 ETA
five.bin 100% 5120KB 124.9KB/s 00:40
不限速的完成帧,速率回到 77.5MB/s:
script -qec "scp -o ControlPath=/tmp/cm-scp /root/demo/scp/five.bin tester@127.0.0.1:~/five.bin" /dev/null | tr "\r" "\n" | grep -a "%" | tail -1
five.bin 100% 5120KB 77.5MB/s 00:00
进度条各列含义:
| 列 | 含义 |
|---|---|
five.bin | 正在传输的文件名 |
2% / 100% | 完成百分比 |
144KB / 5120KB | 已传输字节数 |
140.5KB/s / 124.9KB/s | 瞬时速率 |
00:35 ETA / 00:40 | 预计剩余时间(传输中);完成后变为已用总时长 |
💡 说明:限速完成帧的
124.9KB/s与-l 1000(1000 Kbit/s ÷ 8 ≈ 125 KB/s)几乎完全吻合——单位实锤。另外实测确认:用 sshpass 包装 scp 时进度条会被其认证包装层吞掉(伪终端下输出字节数为 0,而不经 sshpass 的 ControlMaster 方式能正常显示),自动化场景想看进度只能换认证方式或改用-v。
3.12 -q 与 -v:静默与调试
-q 关闭进度条与诊断信息(脚本里防止进度输出污染日志);-v 打开底层 ssh 的调试信息(排障用)。-q 的效果——伪终端下本应有进度条,加了 -q 后输出字节数为 0:
script -qec "scp -q -o ControlPath=/tmp/cm-scp /root/demo/scp/five.bin tester@127.0.0.1:~/five.bin" /dev/null | wc -c
0
-v 的调试信息会经底层 ssh 全量打印,常见关键行如下(分别来自前几节的实测):
Executing: program /usr/bin/ssh host 127.0.0.1, user tester, command scp -v -t ~/o-mode.txt
debug1: Connecting to 127.0.0.1 [127.0.0.1] port 22.
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: zlib@openssh.com
Sink: C0644 35 note.txt
💡 说明:
-v输出里最值得先看的行是Connecting to(连没连上、连的哪个端口)、kex:行(加密与压缩协商结果)和Executing:行(远端实际执行的程序与参数)。-v可叠加到-vvv,但日常排障一级足够。
3.13 -c:指定加密算法
-c(小写)指定会话加密算法,算法名必须是服务端支持的列表之一。用 aes128-gcm@openssh.com 传输:
sshpass -p tester123 scp -c aes128-gcm@openssh.com /root/demo/scp/src/note.txt tester@127.0.0.1:~/cipher.txt; echo rc=$?
rc=0
-v 核对协商结果:
sshpass -p tester123 scp -c aes128-gcm@openssh.com -v /root/demo/scp/src/note.txt tester@127.0.0.1:~/cipher.txt 2>&1 | grep "kex: client->server cipher"
debug1: kex: client->server cipher: aes128-gcm@openssh.com MAC: <implicit> compression: none
不加 -c 的默认值对照:
sshpass -p tester123 scp -v /root/demo/scp/src/note.txt tester@127.0.0.1:~/cipher.txt 2>&1 | grep "kex: client->server cipher"
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
💡 说明:默认的
chacha20-poly1305@openssh.com在无 AES 硬件加速的机器上通常更快;老 CPU 上aes128-gcm配合 AES-NI 反而占优。写错算法名会直接被拒绝(报No matching cipher found类错误)。
3.14 -o:向底层 ssh 透传参数
-o 后面直接写 ssh_config 格式的键值对,可出现多次,全部转给底层的 ssh 进程。实测透传 StrictHostKeyChecking 与 ConnectTimeout:
sshpass -p tester123 scp -o StrictHostKeyChecking=no -o ConnectTimeout=5 /root/demo/scp/src/note.txt tester@127.0.0.1:~/opt-pass.txt; echo rc=$?
rc=0
💡 说明:scp 自身参数有限,主机密钥策略、超时、压缩、认证方式等全部通过
-o透传(3.11 节进度条实验里的ControlPath复用连接也是用它实现的)。常用透传项:StrictHostKeyChecking(首次连接策略)、ConnectTimeout(连接超时)、Compression(等价-C)、IdentityFile(等价-i)。
3.15 错误形态汇总
把本节各实验触发的错误集中对比,便于排障时对号入座:
sshpass -p tester123 scp /root/no-such.txt tester@127.0.0.1:~/ 2>&1; echo rc=$?
/root/no-such.txt: No such file or directory
rc=1
sshpass -p tester123 scp /root/demo/scp/tree tester@127.0.0.1:~/dir-no-r 2>&1; echo rc=$?
/root/demo/scp/tree: not a regular file
rc=1
sshpass -p tester123 scp /root/demo/scp/src/note.txt tester@127.0.0.1:/no/such/dir/ 2>&1; echo rc=$?
scp: /no/such/dir/: No such file or directory
rc=1
sshpass -p tester123 scp /root/demo/scp/src/note.txt tester@127.0.0.1:/root/forbidden.txt 2>&1; echo rc=$?
scp: /root/forbidden.txt: Permission denied
rc=1
sshpass -p wrongpass scp /root/demo/scp/src/note.txt tester@127.0.0.1:~/ 2>&1; echo rc=$?
Permission denied, please try again.
rc=5
| 报错 | rc | 原因与排查方向 |
|---|---|---|
源路径: No such file or directory | 1 | 本地源文件不存在,检查路径拼写 |
源路径: not a regular file | 1 | 源是目录,忘加 -r |
scp: /远端路径/: No such file or directory | 1 | 远端目标目录不存在,先在远端创建 |
scp: /远端路径: Permission denied | 1 | 远端账号对目标目录无写权限 |
Permission denied, please try again.(rc=5) | 5 | 认证失败;rc=5 是 sshpass 的错误码(密码错误),不是 scp 的 |
ssh: connect ... Connection refused + lost connection | 1 | 端口无人监听,核对 -P 与服务端端口 |
WARNING: UNPROTECTED PRIVATE KEY FILE | 1 | 私钥权限过松(非 600),chmod 600 修复 |
⚠️ 注意:scp 的本地侧错误 rc=1,而认证失败时 sshpass 返回 rc=5;远端错误信息带
scp:前缀,本地错误不带——看前缀就能判断错误发生在哪一侧。
四、实战示例
4.1 批量部署配置文件到多台服务器
场景:一份应用配置要推到多台机器(用 tester、tester1 两个账号模拟两台服务器),密钥免密是前提(3.8 节方法分发公钥)。
准备配置文件并在两台"服务器"上创建目标目录:
mkdir -p /root/demo/scp/app && printf "server.port=8080\napp.name=demo\n" > /root/demo/scp/app/app.properties && chmod 644 /root/demo/scp/app/app.properties
sshpass -p tester123 ssh tester@127.0.0.1 "mkdir -p ~/app"; sshpass -p tester1pass ssh tester1@127.0.0.1 "mkdir -p ~/app"; echo dirs-ready
dirs-ready
循环推送(密钥认证下无任何交互):
scp -i /root/.ssh/demo_scp_ed25519 /root/demo/scp/app/app.properties tester@127.0.0.1:app/; scp -i /root/.ssh/demo_scp_ed25519 /root/demo/scp/app/app.properties tester1@127.0.0.1:app/; echo rc=$?
rc=0
两端分别核对内容:
sshpass -p tester123 ssh tester@127.0.0.1 "cat ~/app/app.properties"
server.port=8080
app.name=demo
sshpass -p tester1pass ssh tester1@127.0.0.1 "cat ~/app/app.properties"
server.port=8080
app.name=demo
💡 说明:目标写
app/(目录加斜杠)保持源文件名;结合 Shell 的 for 循环可以一行推进任意多台主机。多机循环时建议加-o ConnectTimeout=5,避免单台失联拖死整个部署。
4.2 通配符拉取远端日志
场景:从远端按日期批量拉取日志到本地归档。远端准备三天的日志:
sshpass -p tester123 ssh tester@127.0.0.1 "mkdir -p ~/app/logs && for d in 01 02 03; do printf \"log-day-\$d\n\" > ~/app/logs/app-2026-02-\$d.log; done && ls -l ~/app/logs/"
total 12
-rw-rw-r-- 1 tester tester 11 Oct 15 20:14 app-2026-02-01.log
-rw-rw-r-- 1 tester tester 11 Oct 15 20:14 app-2026-02-02.log
-rw-rw-r-- 1 tester tester 11 Oct 15 20:14 app-2026-02-03.log
通配符拉取——远程路径整体加引号,让 * 交给远端展开:
rm -f /root/demo/scp/dst/app-*.log; sshpass -p tester123 scp "tester@127.0.0.1:app/logs/*.log" /root/demo/scp/dst/; echo rc=$?
rc=0
ls -l /root/demo/scp/dst/
total 12
-rw-r--r-- 1 root root 11 Oct 15 20:14 app-2026-02-01.log
-rw-r--r-- 1 root root 11 Oct 15 20:14 app-2026-02-02.log
-rw-r--r-- 1 root root 11 Oct 15 20:14 app-2026-02-03.log
cat /root/demo/scp/dst/app-2026-02-02.log
log-day-02
💡 说明:加不加引号决定了通配符在哪一侧展开——加引号
"host:app/logs/*.log"由远端 shell 展开(拉日志的正确姿势);不加引号时本地 shell 先尝试展开,本地无匹配文件则原样传给远端,结果相同但依赖"本地恰好无同名文件"这一巧合,写脚本时务必加引号消除歧义。
4.3 整目录迁移并保留时间戳
场景:项目目录整体搬到另一台机器,要求层级、权限、修改时间都原样保留——-r 加 -p 组合,-i 指定密钥。
准备一个两级项目目录(main.conf 的 mtime 指到 2026-03-15、权限 640):
mkdir -p /root/demo/scp/project/conf && printf "key=value\n" > /root/demo/scp/project/conf/main.conf && touch -d "2026-03-15 09:30:00" /root/demo/scp/project/conf/main.conf && chmod 640 /root/demo/scp/project/conf/main.conf && find /root/demo/scp/project | sort
/root/demo/scp/project
/root/demo/scp/project/conf
/root/demo/scp/project/conf/main.conf
整目录迁移:
sshpass -p tester123 ssh tester@127.0.0.1 "rm -rf ~/project"
scp -i /root/.ssh/demo_scp_ed25519 -rp /root/demo/scp/project tester@127.0.0.1:~/; echo rc=$?
rc=0
远端核对结构、权限与时间戳:
sshpass -p tester123 ssh tester@127.0.0.1 "find ~/project | sort; stat -c %a ~/project/conf/main.conf; stat -c %y ~/project/conf/main.conf"
/home/tester/project
/home/tester/project/conf
/home/tester/project/conf/main.conf
640
2026-03-15 09:30:00.000000000 +0000
💡 说明:目录层级、
640权限与2026-03-15 09:30:00时间戳全部原样落地。-rp是 scp 迁移场景的固定搭配;符号链接在-r下会按 scp 的默认策略处理,链接指向的内容与链接本身如何取舍若要求严格,应改用 rsync-a。
4.4 密钥免密前提下的两台主机中转(-3 全流程)
场景:A 机数据要转到 B 机,本地只是中转站。完整流程四步:生成专用密钥 → 公钥分发到两端 → -3 中转 → 验证。
第一步生成密钥并分发公钥到两台主机(ssh-copy-id 需要各输一次密码,此处用 sshpass 演示):
ssh-keygen -t ed25519 -f /root/.ssh/demo_scp_ed25519 -N "" -C "scp-demo"
Generating public/private ed25519 key pair.
Your identification has been saved in /root/.ssh/demo_scp_ed25519
Your public key has been saved in /root/.ssh/demo_scp_ed25519.pub
The key fingerprint is:
SHA256:JsK2p87MOjUIrdYg4t7zrZ2dAg4aq5Lavesl0t8U8XU scp-demo
The key's randomart image is:
+--[ED25519 256]--+
| |
| |
| . . . E |
|+... o . . |
|+oo.+ . S . |
|.+.=o+ o . |
|o.*.*.+ . |
|o=.O Bo+o . |
|* o=&=o+oo |
+----[SHA256]-----+
sshpass -p tester123 ssh-copy-id -i /root/.ssh/demo_scp_ed25519.pub tester@127.0.0.1 2>&1 | tail -2
and check to make sure that only the key(s) you wanted were added.
sshpass -p tester1pass ssh-copy-id -i /root/.ssh/demo_scp_ed25519.pub tester1@127.0.0.1 2>&1 | tail -2
and check to make sure that only the key(s) you wanted were added.
第二步把一份文件先推到源主机(tester),随后从源主机中转到目标主机(tester1):
scp -i /root/.ssh/demo_scp_ed25519 /root/demo/scp/src/note.txt tester@127.0.0.1:~/key-sent.txt; echo rc=$?
rc=0
scp -3 -i /root/.ssh/demo_scp_ed25519 tester@127.0.0.1:~/key-sent.txt tester1@127.0.0.1:~/three.txt; echo rc=$?
rc=0
第三步登录目标主机验证:
sshpass -p tester1pass ssh tester1@127.0.0.1 "cat ~/three.txt"
hello from scp
line two
line three
💡 说明:
-3中转的两条连接都从本地发起(3.9 节-v证据),所以本地必须同时持有 A、B 两端的凭据——这正是第一步把同一把公钥分发到两端的原因。数据量很大且两台远程主机之间网络更好时,应登录 A 机直接scp 文件 B机:路径,让数据走 A→B 直连而不绕行本地。
五、注意事项与常见问题
Q1:scp 报 not a regular file 是什么错?
源是目录但没加 -r。scp 对目录不递归,必须显式 -r(见 3.4 节实测);cp 的同类错误措辞是 omitting directory,两者报错不同,脚本匹配要区分。
Q2:为什么 scp 的端口参数是大写 -P,ssh 却是小写 -p?
scp 的小写 -p 在 OpenSSH 时代之前(rcp 血统)就被"保留时间戳"占用了,端口只能用 -P;ssh 没有时间戳需求,用小写 -p。实测把 -p 2222 写进 scp,2222 会被当成源文件名报 2222: No such file or directory(见 3.7 节)——同一条命令里 -P 2222 -p 同时出现是合法且常见的(指定端口 + 保留时间戳)。
Q3:-l 1000 限速后为什么远比预期慢?
-l 的单位是 Kbit/s 不是 KB/s:1000 Kbit/s ≈ 125 KB/s,实测 5MB 文件用时 41 秒、完成帧速率 124.9KB/s(见 3.11 节)。想限 1MB/s 应写 -l 8192。
Q4:传输成功后没有任何输出,怎么确认文件真的传过去了?
三个手段:看退出码(echo $? 为 0);远端 ls -l 核对大小与时间;自动化脚本里加 -q 静默、出错才有输出。进度条只在标准错误连接终端时显示——重定向到文件或被 sshpass 包装时都看不到(3.11 节实测)。
Q5:权限和时间戳到底会保留哪些?
权限位默认随传统 SCP 协议的 C0644 指令传递(3.5、3.10 节实测),-p 的增量是 mtime/atime;不加 -p 时远端 mtime 是传输时刻,按时间做增量判断的工具会失准。属主与属组 scp 不处理(以目标账号落盘)。
Q6:OpenSSH 9.0 起 scp 默认协议变了,老脚本会受影响吗?
可能。9.0 前默认传统 SCP 协议(远端执行 scp 程序),9.0 起默认 SFTP 协议(远端走 sftp-server 子系统,见 3.10 节)。差异点:远端 shell 通配符与引号的处理、要求服务端启用 sftp 子系统、错误信息措辞变化。过渡期用 -O 强制旧协议、-s 显式新协议,先 -s -v 验证再全量切换。
Q7:脚本里如何安全地自动填密码?
推荐密钥认证(3.8 节),一次配置永久免密。临时演示可用 sshpass -p 密码 scp ...,但注意:命令行密码会进入 Shell 历史;认证失败时 sshpass 返回 rc=5(与 scp 自身的 rc=1 区分);sshpass 还会吞掉 scp 的进度条输出(3.11 节实测),需要看进度时改用 ControlMaster 复用连接等免交互认证方式。
Q8:通配符应该展开在哪一侧?
远程路径里的通配符加引号交给远端展开(4.2 节 "host:app/logs/*.log");不加引号本地 shell 会先尝试展开,可能产生意外匹配或不展开原样传递。多文件本地源则相反,直接写 *.log 由本地展开即可。
六、参考链接
- scp 手册页(Linux man-pages):https://man7.org/linux/man-pages/man1/scp.1.html
- OpenSSH 官方 scp 手册(最新版):https://man.openbsd.org/scp
- OpenSSH 发行注记(9.0 默认协议切换说明):https://www.openssh.com/releasenotes.html
- OpenSSH 官网:https://www.openssh.com/
- 专栏相关篇目:《ssh 命令详解与参数实测(Ubuntu 22.04)》(认证、端口与连接参数同源)