为什么通过“ssh -t”传输的二进制文件被更改?

为什么通过“ssh -t”传输的二进制文件被更改?

我在尝试着通过 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。也就是说,换行符已转换为回车符(此时终端将光标移回屏幕左侧)和换行符。

这是由终端设备驱动程序完成的。更准确地说,根据终端(或伪终端)设备的线路规则,驻留在内核中的软件模块。

您可以使用命令控制该线路规程的行为sttyLF->的翻译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(在您的情况下为伪终端设备),其中LFs 被转换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都被翻译CRLFsome-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-tssh

您不希望遥控器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^Cstty echoctlstty 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^Dcat > 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

相关内容