与 python 中子进程打开的 `/dev/tty` 交互

Interact with `/dev/tty` opened by subprocess in python

我有一个 python 脚本,它使用 subprocess.Popen 启动目标程序:网络服务器。我想与目标交互,所以我将 stdinstdoutstderr 分配给管道,现在我可以读写它们了。

现在,网络服务器链接 OpenSSL 库以进行 TLS 处理。作为 TLS 处理的一部分,它会打开证书并提示输入密码。通常,在输入密码并按下回车键后,网络服务器就会开始服务。现在,您可能已经猜到了,我想从 python 脚本中输入此密码。通常,我需要做的是写入服务器的 stdin。但是,有一个问题。当 OpenSSL 提示并读取密码时,它不使用 stdin/stdout。它打开 /dev/tty 并使用它。结果,我不得不输入密码并手动输入。

下图演示了这种情况。

在下面,您可以看到服务器 lsof 的输出片段:

memcached 25279 USER    0r  FIFO   0,13      0t0  7771739 pipe
memcached 25279 USER    1w  FIFO   0,13      0t0  7771740 pipe
memcached 25279 USER    2w  FIFO   0,13      0t0  7771740 pipe
memcached 25279 USER    3r   REG    8,1     3414  3414276 ....key.pem
memcached 25279 USER    4r   CHR    5,0      0t0       13 /dev/tty
memcached 25279 USER    5w   CHR    5,0      0t0       13 /dev/tty

有没有办法拦截服务器对 /dev/tty 的调用,以便我可以直接从脚本写入它?

通过使用 pty(伪终端)实现您所描述的内容的标准方法。 pty 看起来与普通 tty 完全一样,只是它是由软件而不是实际终端或终端仿真器控制的。

在您的情况下,您可以轻松使用 python 标准库中的 pty 模块,它提供了一些使用 pty 的实用程序。具体看pty.spawn()函数:

Spawn a process, and connect its controlling terminal with the current process’s standard io. This is often used to baffle programs which insist on reading from the controlling terminal. It is expected that the process spawned behind the pty will eventually terminate, and when it does spawn will return.

用法示例:

import subprocess, sys

openssl_cmd = ['openssl', 'foo', 'bar']
p = subprocess.Popen([sys.executable, '-c', 'import pty, sys; pty.spawn(sys.argv[1:])', *openssl_cmd])
p.stdin.write('password\n')
print(p.wait())

在这个例子中,我们 运行 使用 spawn,而不是直接 运行ning openssl,这意味着我们写入进程标准输入的任何内容都将由 openssl 从它的 /dev/tty.