我在尝试着通过 SSH 复制文件scp
,但由于不知道我需要的确切文件名而无法使用。尽管小型二进制文件和文本文件传输良好,但大型二进制文件会被更改。这是服务器上的文件:
remote$ ls -la
-rw-rw-r-- 1 user user 244970907 Aug 24 11:11 foo.gz
remote$ md5sum foo.gz
9b5a44dad9d129bab52cbc6d806e7fda foo.gz
这是我移动后的文件:
local$ time ssh [email protected] -t 'cat /path/to/foo.gz' > latest.gz
real 1m52.098s
user 0m2.608s
sys 0m4.370s
local$ md5sum latest.gz
76fae9d6a4711bad1560092b539d034b latest.gz
local$ ls -la
-rw-rw-r-- 1 dotancohen dotancohen 245849912 Aug 24 18:26 latest.gz
请注意,下载的文件是大比服务器上的还好!但是,如果我对一个非常小的文件执行相同的操作,那么一切都会按预期进行:
remote$ echo "Hello" | gzip -c > hello.txt.gz
remote$ md5sum hello.txt.gz
08bf5080733d46a47d339520176b9211 hello.txt.gz
local$ time ssh [email protected] -t 'cat /path/to/hello.txt.gz' > hi.txt.gz
真实 0m3.041s 用户 0m0.013s 系统 0m0.005s
local$ md5sum hi.txt.gz
08bf5080733d46a47d339520176b9211 hi.txt.gz
在本例中,两个文件大小均为 26 字节。
为什么小文件可以很好地传输,但大文件会添加一些字节?
答案1
长话短说
不要使用-t
.-t
涉及远程主机上的伪终端,并且只能用于从终端运行可视化应用程序。
解释
换行符(也称为换行符或\n
)是发送到终端时告诉终端向下移动光标的字符。
seq 3
然而,当您在终端中运行时,即seq
写入1\n2\n3\n
类似 的内容时/dev/pts/0
,您看不到:
1
2
3
但
1
2
3
这是为什么?
实际上,当seq 3
(或ssh host seq 3
就此而言)写入时1\n2\n3\n
,终端会看到1\r\n2\r\n3\r\n
。也就是说,换行符已转换为回车符(此时终端将光标移回屏幕左侧)和换行符。
这是由终端设备驱动程序完成的。更准确地说,根据终端(或伪终端)设备的线路规则,驻留在内核中的软件模块。
您可以使用命令控制该线路规程的行为stty
。LF
->的翻译CRLF
通过
stty onlcr
(通常默认启用)。您可以通过以下方式将其关闭:
stty -onlcr
或者您可以使用以下命令关闭所有输出处理:
stty -opost
如果您这样做并运行seq 3
,您将看到:
$ stty -onlcr; seq 3
1
2
3
正如预期的那样。
现在,当你这样做时:
seq 3 > some-file
seq
不再写入终端设备,而是写入常规文件,没有进行任何翻译。some-file
包含也如此1\n2\n3\n
。仅当写入终端设备时才会进行转换。而且这只是为了展示而做的。
同样,当你这样做时:
ssh host seq 3
ssh
正在写入,1\n2\n3\n
无论ssh
输出是什么。
实际情况是,seq 3
命令运行host
时其 stdout 被重定向到管道。ssh
主机上的服务器读取管道的另一端并通过加密通道将其发送到您的ssh
客户端,ssh
客户端将其写入其 stdout(在您的情况下为伪终端设备),其中LF
s 被转换CRLF
为显示。
当许多交互式应用程序的标准输出不是终端时,其行为会有所不同。例如,如果您运行:
ssh host vi
vi
不喜欢它,它不喜欢它的输出进入管道。例如,它认为它不是在与能够理解光标定位转义序列的设备进行通信。
所以ssh
有-t
这样的选择。使用该选项,主机上的 ssh 服务器会创建一个伪终端设备,并将其作为vi
.在该终端设备上写入的内容vi
会经过远程伪终端线路规则,由ssh
服务器读取并通过加密通道发送到ssh
客户端。它与以前相同,只是不再使用管道,ssh
服务器使用伪终端。
另一个区别是,在客户端,ssh
客户端将终端设置为raw
模式(并禁用本地回声)。这意味着那里没有进行任何翻译(opost
被禁用以及其他输入端行为)。例如,当您键入 时,该字符将被发送到远程端,Ctrl-C而不是中断,远程伪终端的线路规则将ssh
^C
打断到远程命令。
当你这样做时:
ssh -t host seq 3
seq 3
写入1\n2\n3\n
其标准输出,这是一个伪终端设备。因为onlcr
,它被翻译了在主机上并1\r\n2\r\n3\r\n
通过加密通道发送给您。在您这边没有翻译(onlcr
已禁用),因此在终端模拟器的屏幕上1\r\n2\r\n3\r\n
显示不变(由于模式)并正确显示。raw
现在,如果你这样做:
ssh -t host seq 3 > some-file
和上面没有什么区别。ssh
会写同样的东西:1\r\n2\r\n3\r\n
,但这次写成some-file
.
所以基本上所有的LF
输出seq
都被翻译CRLF
成some-file
.
如果你这样做,结果是一样的:
ssh -t host cat remote-file > local-file
所有LF
字符(0x0a 字节)都被转换为 CRLF (0x0d 0x0a)。
这可能是您的文件损坏的原因。对于第二个较小的文件,碰巧该文件不包含 0x0a 字节,因此没有损坏。
请注意,不同的 tty 设置可能会导致不同类型的损坏。与此相关的另一种潜在的损坏类型是,如果( , ...)-t
上的启动文件将内容写入其 stderr,因为远程 shell 的 stdout 和 stderr 最终会合并到的 stdout 中(它们都转到伪-终端设备)。host
~/.bashrc
~/.ssh/rc
-t
ssh
您不希望遥控器cat
输出到那里的终端设备。
你要:
ssh host cat remote-file > local-file
你可以这样做:
ssh -t host 'stty -opost; cat remote-file' > local-file
那会起作用(除了在写入标准错误上面讨论的损坏情况),但即使这样也不是最优的,因为您将在host
.
还有一些更好玩的:
$ ssh localhost echo | od -tx1
0000000 0a
0000001
好的。
$ ssh -t localhost echo | od -tx1
0000000 0d 0a
0000002
LF
翻译成CRLF
$ ssh -t localhost 'stty -opost; echo' | od -tx1
0000000 0a
0000001
再次确定。
$ ssh -t localhost 'stty olcuc; echo x'
X
这是另一种形式的输出后处理,可以通过终端线路规则来完成。
$ echo x | ssh -t localhost 'stty -opost; echo' | od -tx1
Pseudo-terminal will not be allocated because stdin is not a terminal.
stty: standard input: Inappropriate ioctl for device
0000000 0a
0000001
ssh
当服务器自己的输入不是终端时,拒绝告诉服务器使用伪终端。-tt
不过你可以强制它:
$ echo x | ssh -tt localhost 'stty -opost; echo' | od -tx1
0000000 x \r \n \n
0000004
线路规则在输入方面做了更多的事情。
在这里,echo
不读取它的输入,也没有被要求输出它,x\r\n\n
那么它从哪里来呢?这是echo
远程伪终端 ( ) 的本地终端stty echo
。服务器将从客户端读取的数据ssh
传送x\n
到远程伪终端的主控端。其行规则与之相呼应( beforestty opost
是 run ,这就是为什么我们看到 aCRLF
而不是LF
)。这与远程应用程序是否从 stdin 读取任何内容无关。
$ (sleep 1; printf '\03') | ssh -tt localhost 'trap "echo ouch" INT; sleep 2'
^Couch
由于 ,该字符会以(和)0x3
的形式回显,并且 shell 和 sleep 会收到一个 SIGINT ,因为。^C
^
C
stty echoctl
stty isig
所以同时:
ssh -t host cat remote-file > local-file
已经够糟糕了,但是
ssh -tt host 'cat > remote-file' < local-file
以相反的方式传输文件要糟糕得多。您会得到一些 CR -> LF 转换,但也会出现所有特殊字符(^C
, ^Z
, ^D
, ^?
, ^S
...)的问题,并且cat
当到达 的末尾时local-file
,只有在,^D
之后发送时,远程设备才会看到 eof ,或类似在终端中执行的操作。\r
\n
^D
cat > file
答案2
当使用该方法复制文件时,文件看起来不同。
远程服务器
ls -l | grep vim_cfg
-rw-rw-r--. 1 slm slm 9783257 Aug 5 16:51 vim_cfg.tgz
本地服务器
运行你的ssh ... cat
命令:
$ ssh dufresne -t 'cat ~/vim_cfg.tgz' > vim_cfg.tgz
在本地服务器上生成此文件的结果:
$ ls -l | grep vim_cfg.tgz
-rw-rw-r--. 1 saml saml 9820481 Aug 24 12:13 vim_cfg.tgz
调查原因?
在本地调查生成的文件表明它已损坏。如果您将-t
开关从ssh
命令中取出,那么它会按预期工作。
$ ssh dufresne 'cat ~/vim_cfg.tgz' > vim_cfg.tgz
$ ls -l | grep vim_cfg.tgz
-rw-rw-r--. 1 saml saml 9783257 Aug 24 12:17 vim_cfg.tgz
校验和现在也可以工作:
# remote server
$ ssh dufresne "md5sum ~/vim_cfg.tgz"
9e70b036836dfdf2871e76b3636a72c6 /home/slm/vim_cfg.tgz
# local server
$ md5sum vim_cfg.tgz
9e70b036836dfdf2871e76b3636a72c6 vim_cfg.tgz