摘要
你是否在日常工作中遇到一下困惑:
- SSH 连接中的 PTY 是什么意思,与 TTY 有什么关系?
- Python 中使用 SSH 连接后,执行命令到底是使用 invoke_session 还是 exec_command,它们有什么区别?
- 为什么并发模式下使用 exec_command 会回显错乱?
- 如何拿到执行命令的 exit_status code?
- 为什么执行命令时设置 get_pty = True,拿到的 exit_status 始终是 0
这篇文章将会对上述的问题做一次完整的梳理。
一、PTY(Pseudo-TTY)
1.1 终端?
终端在计算机领域可以理解为键盘,显示器等设备。
日常使用到的终端则运维通过 console 登陆一台服务器后,便会在设备上创建 /dev/ttyX 文件,用于实现用户与服务器的交互;
网络设备上常见的 TTY 数量的配置,即是同时允许有多少个用户登陆。
1.2 伪终端?
如果不是通过物理方式与连接设备连接,而是通过远程 SSH 的方式登陆设备的话,设备便会需要一个“终端模拟器”来实现用户的输入输出与设备进行交互的目的。
实际工作中不管是 Mac 下的 Terminal、CRT 还是 Moba 等应用程序只不管是提供了一个图形化的界面,底层实际都是通过 SSH 远程连接与设备进行交互。
因此当远程登陆设备后,Linux 系统中对于终端模拟器的实现就叫做伪终端(Pseudo-Termianl)。
1.3 为什么一定需要(伪)终端?
不管是终端还是伪终端,本质上都是操作系统中虚拟化这一思想的很好体现,即允许多个用户同时控制操作系统。
1.4 SSH 中的实际应用
使用 ssh 命令远程控制设备时会有以下两种场景。
1.4.1 登陆模式
先通过 ssh username@ip 登陆设备,再执行命令,这种我们称为“登陆模式”;
登陆模式下会默认分配一个伪终端(加密传输),所以实际工作中肯定是可以多人同时 ssh 到设备对其进行操作的,此处可以停下来思考一下通过程序远程并发操作设备的场景。
Mac 中的 Terminal,或者 CRT,Moba 等远程登录工具均是登录模式,每开一个窗口就代表创建了一个伪终端。
除此之外登陆模式下还可以使用 vim,sudo 等交互式命令;
可以通过 -T 参数强制不分配终端,此时无法执行 vim,sudo 等交互命令。
1.4.2 命令模式
直接通过 ssh username@ip cmd 对设备远程执行命令,这种我们称为“命令模式”;
命令模式下默认不会分配伪终端,此时如果执行 ssh username@ip “vim test.txt”,则会如下错误提示
Vim: Warning: Output is not to a terminal
Vim: Warning: Input is not from a terminal
可以通过 -t 参数让远程服务器分配一个伪终端,此时可以正常执行 ssh username@ip -t “vim test.txt” 命令。
1.4.3 Exit Status
Linux 中执行完某个命令后会有一个返回值,该值表示执行程序的退出状态,退出状态用于检查执行的结果(成功/失败)。如果退出状态为0,则命令执行成功。如果命令失败,则退出状态为非零。
该值在调用 shell 脚本或者其他自定义程序时非常重要,可以通过该值直观的看出程序是否执行成功。
但在伪终端模式下,该值始终为成功,如下示例
不开启 PTY
$ ssh -T -v username@hostname 'exit 42'
...
debug1: Local version string SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.4
debug1: Remote protocol version 2.0, remote software version