从pty读取没有无尽的悬挂

时间:2017-01-29 12:52:40

标签: python subprocess pty

我有一个脚本,如果它在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看起来很奇怪。我认为输出可能很安静,任何数字都可能不够。我从堆栈溢出中尝试了很多解决方案,但它们都没有工作(它什么都不打印或永久挂起)。

我对文件描述符及其所有内容都不是很熟悉,所以如果我做错了任何澄清,我将不胜感激。

谢谢!

1 个答案:

答案 0 :(得分:4)

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

标准/最简单的解决方案是使用外部模块pexpect,它在内部使用PTY:例如,

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

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

如果您想坚持subprocess,那么您必须使用subprocess.Popen,原因如上所述。您的假设是正确的,通过传递1000,您最多可以读取1000个字节,因此您必须使用循环。如果没有要读取的内容,os.read将阻止并等待数据显示。问题是如何识别进程何时终止:在这种情况下,您知道不再有数据到达。对os.read的下一次调用将永久阻止。幸运的是,操作系统可以帮助您检测这种情况:如果伪终端的所有文件描述符都可以用于写入,那么os.read将返回空字符串或返回错误,具体取决于操作系统。您可以检查此情况并在发生这种情况时退出循环。现在理解以下代码的最后一部分是了解打开文件描述符和subprocess如何组合在一起:subprocess.Popen内部调用fork(),它复制当前进程,包括所有打开的文件描述符,以及然后在两个执行路径之一中调用exec(),它终止当前进程以支持新进程。在另一个执行路径中,控件返回到Python脚本。所以在调用subprocess.Popen之后,对于slave的slave端有两个有效的文件描述符:一个属于生成的进程,一个属于你的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)