我有一个脚本,如果它在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
看起来很奇怪。我认为输出可能很安静,任何数字都可能不够。我从堆栈溢出中尝试了很多解决方案,但它们都没有工作(它什么都不打印或永久挂起)。
我对文件描述符及其所有内容都不是很熟悉,所以如果我做错了任何澄清,我将不胜感激。
谢谢!
答案 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)