TL; DR
不要使用-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
其标准输出重定向到管道的情况下运行。ssh
主机上的服务器读取管道的另一端,并通过加密通道将其发送到ssh
客户端,然后ssh
客户端将其写入其stdout中(在您的情况下是伪终端设备),将LF
s转换为此值以CRLF
进行显示。
当标准输出不是终端时,许多交互式应用程序的行为会有所不同。例如,如果您运行:
ssh host vi
vi
不喜欢它,不喜欢它的输出到管道。它认为它不是在与能够理解例如光标定位转义序列的设备通信。
因此ssh
可以-t
选择。使用该选项,主机上的ssh服务器将创建一个伪终端设备,并使stdout(以及stdin和stderr)成为vi
。什么vi
写道,终端装置通过该远程伪终端线路规则并且由所读出的ssh
服务器和发送过来的加密的信道的ssh
客户机。除了使用管道代替之外,ssh
服务器使用伪终端,与之前相同。
另一个区别是,在客户端,ssh
客户端将终端设置为raw
模式。这意味着在那里没有完成翻译(opost
被禁用以及其他输入端行为)。例如,当您键入时,该字符被发送到远程端Ctrl-C,而不是打断ssh
该^C
字符,远程伪终端的线路规则将中断发送到远程命令。
当您这样做时:
ssh -t host seq 3
seq 3
写入1\n2\n3\n
其stdout,这是一个伪终端设备。由于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
是,您在host
(~/.bashrc
,~/.ssh/rc
...)上的启动文件将内容写入其stderr,因为-t
远程外壳程序的stdout和stderr最终合并到了ssh
stdout中(它们都进入了伪指令)。 -终端设备)。
您不希望遥控器cat
输出到那里的终端设备。
你要:
ssh host cat remote-file > local-file
您可以这样做:
ssh -t host 'stty -opost; cat remote-file` > local-file
这将起作用(除了上面讨论的stderr损坏案例中的写法之外),但是即使那样也将是次优的,因为您将在上运行不必要的伪终端层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
它从客户端读取到远程伪终端的主侧。并且该行的规则将其回显(在stty opost
运行之前,这就是为什么我们看到a CRLF
和not的原因LF
)。这与远程应用程序是否从stdin读取任何内容无关。
$ (sleep 1; printf '\03') | ssh -tt localhost 'trap "echo ouch" INT; sleep 2'
^Couch
的0x3
字符被回送为^C
(^
和C
),因为stty echoctl
和壳和睡眠接收SIGINT因为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
不会EOF看到,当年底local-file
达到,只有当^D
被后发送\r
,\n
或其他^D
喜欢做的时候cat > file
在你的终端。
-t
选项,它会中断传输。除非出于特殊原因需要它们,否则不要使用-t
或-T
。默认值在绝大多数情况下都适用,因此很少需要这些选项。