每隔一段时间我就会做一些类似的事情
ssh user@host sudo thing
并且我想起 ssh 默认不分配伪终端。为什么不呢?如果我使用别名,我会失去什么ssh
好处ssh -t
?
答案1
主要的区别在于互动性。这类似于在脚本内部本地运行命令,而不是自己输入命令。不同之处在于远程命令必须选择默认值,而非交互式是最安全的。(通常也是最诚实的)
标准输入
- 如果分配了 PTY,应用程序可以检测到这一点,并知道提示用户进行额外输入是安全的,而不会破坏程序。如果没有终端,许多程序会跳过提示用户输入的步骤,这是一件好事。否则会导致脚本不必要地挂起。
- 您的输入将在命令执行期间发送到远程服务器。这包括控制序列。虽然
Ctrl-c
中断通常会导致 ssh 命令上的循环立即中断,但您的控制序列将发送到远程服务器。这导致需要“敲击”按键以确保它在控制到达时到达树叶ssh 命令,但在下一个 ssh 命令开始之前。
我要提醒大家,不要在 cron 等无人值守的脚本中使用ssh -t
。非交互式 shell 要求远程命令以交互方式输入会产生各种麻烦。
您还可以在自己的 shell 脚本中测试终端是否存在。要使用较新版本的 bash 测试 STDIN:
# fd 0 is STDIN
[ -t 0 ]; echo $?
标准输出
- 当将 别名
ssh
为时ssh -t
,您可能会在行尾得到一个额外的回车符。您可能看不到它,但它确实存在;^M
当通过管道传输到 时,它会显示为cat -e
。然后,您必须付出额外的努力来确保此控制代码不会分配给您的变量,特别是如果您要将该输出插入数据库。 - 还存在一个风险,即程序会认为它们可以呈现不适合文件重定向的输出。通常,如果您将 STDOUT 重定向到文件,程序会识别出您的 STDOUT 不是终端,并忽略任何颜色代码。如果 STDOUT 重定向来自ssh 客户端并且有一个与客户端远程端关联的 PTY,远程程序无法做出这样的区分,最终会在输出文件中出现终端垃圾。将输出重定向到远端连接仍应按预期工作。
以下是与之前相同的 bash 测试,但针对的是 STDOUT:
# fd 1 is STDOUT
[ -t 1 ]; echo $?
虽然可以解决这些问题,但您不可避免地会忘记围绕这些问题设计脚本。我们所有人都会在某个时候忘记。您的团队成员可能也没有意识到/记得这个别名的存在,这反过来会给您带来问题,因为他们编写使用您的别名的脚本。
混叠ssh
在ssh -t
很大程度上违反了设计原则最不意外;人们将会遇到意想不到的问题,而且可能不明白问题的原因是什么。
答案2
SSH 转义/控制字符和二进制文件的传输
其他答案中没有提到的一个优点是,在操作时没有伪终端,SSH转义字符例如~C
不支持;这使得程序可以安全地传输可能包含这些和其他控制字符的二进制文件。
如果没有伪终端,字符将“按原样”传输到远程主机:
$ printf "one\ntwo\n~Cthree\n" | ssh user@host tee 1
one
two
~Cthree
如果您尝试强制分配伪终端,则包含的内容~C
会导致ssh>
打印提示以允许用户输入 SSH 命令,从而中断传输。
$ printf "one\ntwo\n~Cthree\n" | ssh -tt user@host tee 2
ssh>
one
two
three
one
two
three
另一个~.
序列更糟糕,因为它导致没有数据传输:
$ printf "one\ntwo\n~.three\n" | ssh -tt user@host tee 2
Connection to host closed.
软件流控制
当分配伪终端时,软件流控制字符(XON/XOFF)也可能被特殊处理。
$ printf "one\ntwo\n^Sthree\n" | ssh user@host tee 1
one
two
three
$ ssh user@host cat -vet 1
one$
two$
^Sthree$
使用伪终端,该Ctrl-S
字符被解释为暂停输入流的信号,因此遇到此字符后不能再发送任何数据(除非该Ctrl-Q
字符后面跟着输入流中的其他字符)。
在这种情况下,强制分配伪终端会导致远端的文件为空。
$ printf "one\ntwo\n^Sthree\n" | ssh -tt user@host tee 2
one
two
在这种情况下,强制分配伪终端会导致远程端的文件包含所有三行但不包含控制字符:
$ printf "one\ntwo\n^Sthree^Q\n" | ssh -tt user@host tee 2
one
two
three
one
two
three
$ ssh user@host cat -vet 2
one$
two$
three$
行结束符的转换
使用伪终端,换行符(十进制 10,0A
ASCII 中的十六进制)也被转换为CRLF
序列(0D0A
ASCII 中的十六进制)。
从前面的例子来看:
$ ssh -t user@host cat 1 | cat -vet
one^M$
two^M$
^Sthree^M$
Connection to host closed.
使用伪终端复制二进制文件
$ ssh -t anthony@remote_host 'cat /usr/bin/free' > ~/free
Connection to remote_host closed.
不使用伪终端复制二进制文件:
$ ssh anthony@remote_host 'cat /usr/bin/free' > ~/free2
这两个文件并不相同:
$ diff ~/free*
Binary files /home/anthony/free and /home/anthony/free2 differ
使用伪终端复制的文件已损坏:
$ chmod +x ~/free*
$ ./free
Segmentation fault
而另一个则不是:
$ ./free2
total used free shared buffers cached
Mem: 2065496 1980876 84620 0 48264 1502444
-/+ buffers/cache: 430168 1635328
Swap: 4128760 112 4128648
通过 SSH 传输文件
这对于使用 SSH 进行数据传输的程序(例如scp
或)尤其重要。这rsync
SCP 协议工作原理的详细描述解释 SCP 协议如何由文本协议消息和二进制文件数据混合组成。
OpenSSH 帮助保护你免受自身
值得注意的是,即使-t
使用了该标志,如果 OpenSSH客户端检测到其流不是终端,ssh
它也会拒绝分配伪终端:stdin
$ echo testing | ssh -t anthony@remote_host 'echo $TERM'
Pseudo-terminal will not be allocated because stdin is not a terminal.
dumb
您仍然可以强制 OpenSSH 客户端使用以下命令分配伪终端-tt
:
$ echo testing | ssh -tt anthony@remote_host 'echo $TERM'
xterm
无论哪种情况,它(明智地)并不关心是否stdout
被stderr
重定向:
$ ssh -t anthony@remote_host 'echo $TERM' >| ssh_output
Connection to remote_host closed.
答案3
在远程主机上我们必须进行以下设置:
/etc/sudoers
...
Defaults requiretty
没有 sudo
$ ssh -T user@host echo -e 'foo\\nbar' | cat -e
foo$
bar$
使用 sudo
$ ssh -T user@host sudo echo -e 'foo\\nbar' | cat -e
sudo: sorry, you must have a tty to run sudo
使用 sudo 我们得到额外的回车符
$ ssh -t user@host sudo echo -e 'foo\\nbar' | cat -e
foo^M$
bar^M$
Connection to localhost closed.
解决方案是禁用将换行符转换为回车符-换行符和stty -onlcr
$ ssh -t user@host stty -onlcr\; sudo echo -e 'foo\\nbar' | cat -e
foo$
bar$
Connection to localhost closed.
答案4
从man ssh
:
-t Force pseudo-tty allocation. This can be used to execute arbi-
trary screen-based programs on a remote machine, which can be
very useful, e.g. when implementing menu services. Multiple -t
options force tty allocation, even if ssh has no local tty.
这允许您获取远程服务器的某种“shell”。对于执行以下任一操作的服务器不是授予 shell 访问权限但允许 SSH(即 Github 是 SFTP 访问的已知示例),使用此标志将导致服务器拒绝您的连接。
shell 还具有所有环境变量(如$PATH
),因此执行脚本通常需要 tty 才能工作。