从 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)
我有一个脚本,如果它在 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)