从 pty 中读取而不会无休止地挂起

Read from pty without endless hanging

我有一个脚本,如果它在 tty 上,它会打印彩色输出。他们中的一堆并行执行,所以我不能把他们的标准输出到 tty。我也无法控制脚本代码(强制着色),所以我想通过 pty 伪造它。我的代码:

invocation = get_invocation()
master, slave = pty.openpty()
subprocess.call(invocation, stdout=slave)
print string_from_fd(master)

而且我不知道 string_from_fd 中应该有什么。现在,我有类似

def string_from_fd(fd):
  return os.read(fd, 1000)

有效,但那个数字 1000 看起来很奇怪。我认为输出可以非常大,任何数量都不够。我尝试了很多来自堆栈溢出的解决方案,但 none 的解决方案有效(它什么都不打印或永远挂起)。

我对文件描述符之类的不是很熟悉,所以如果我做错了什么,任何澄清都将不胜感激。

谢谢!

这不适用于长输出:subprocess.call 一旦 PTY 的缓冲区已满就会阻塞。这就是 subprocess.communicate 存在的原因,但这不适用于 PTY。

standard/easiest解决方案是使用外部模块pexpect,它在内部使用PTYs:例如,

pexpect.spawn("/bin/ls --color=auto").read()

将为您提供带有颜色代码的 ls 输出。

如果您想坚持使用 subprocess,则必须使用 subprocess.Popen,原因如上所述。您的假设是正确的,通过传递 1000,您最多读取 1000 个字节,因此您必须使用循环。 os.read 如果没有可读取的内容则阻塞并等待数据出现。问题在于如何识别进程何时终止:在这种情况下,您知道不会再有数据到达。下一次调用 os.read 将永远阻塞。幸运的是,操作系统可以帮助您检测到这种情况:如果可以用于写入的伪终端的所有文件描述符都已关闭,那么 os.read 将 return 一个空字符串或 return一个错误,取决于 OS。您可以检查这种情况并在这种情况发生时退出循环。现在理解以下代码的最后一部分是理解打开的文件描述符和 subprocess 是如何结合在一起的:subprocess.Popen 内部调用 fork(),它复制当前进程,包括所有打开的文件描述符,并且然后在两个执行路径之一中调用 exec(),它终止当前进程以支持新进程。在另一个执行路径中,控制 returns 到您的 Python 脚本。因此在调用 subprocess.Popen 之后,有 两个 有效文件描述符用于 PTY 的从属端:一个属于派生进程,一个属于您的 Python 脚本。如果你关闭你的,那么唯一可用于将数据发送到主端的文件描述符属于衍生进程。终止后,它会关闭,PTY 进入主端调用 read 失败的状态。

代码如下:

import os
import pty
import subprocess

master, slave = pty.openpty()
process = subprocess.Popen("/bin/ls --color", shell=True, stdout=slave,
                           stdin=slave, stderr=slave, close_fds=True)
os.close(slave)

output = []
while True:
    try:
        data = os.read(master, 1024)
    except OSError:
        break
    if not data:
        break
    output.append(data) # In Python 3, append ".decode()" to os.read()
output = "".join(output)