在Python中,如何检查subprocess.Popen对象的stdout是否有可读内容?

5 投票
3 回答
4978 浏览
提问于 2025-04-16 23:08

在Python中,我该如何检查subprocess.Popen对象的标准输出(stdout)是否有内容可以读取?我正在为一个工具写一个包装器,这个工具有时会运行几个小时。使用.readline()来读取子进程的标准输出会严重影响脚本的速度,特别是当运行超过几分钟的时候。我需要一种更高效的方法来检查标准输出,看是否有内容可以读取。顺便提一下,这个工具一次只写完整的一行。脚本大致是这样的:

    #!/usr/bin/python -u
    #thiswrap.py

    import sys, time
    from subprocess import *

    chldp = Popen(sys.argv[1], bufsize=0, stdout=PIPE, close_fds=True)
    chstdin,chstdout=chldp.stdin,chldp.stdout
    startnoti=False

    while not chldp.poll():
        rrl=chstdout.readline() # <--- this is where the problem is
        if rrl[-8:]=='REDACTED TEXT':
            sys.stdout.write(rrl[:-1]+'   \r')
            if not startnoti: startnoti=True
        else:
            if startnoti: sys.stdout.write('\n')
            sys.stdout.write(rrl)
            if startnoti: # REDACTED
            time.sleep(0.1)
        time.sleep(0.1)

有什么好主意吗?

3 个回答

0

第一个评论里提到的解决方案几乎是对的。你只需要把一个整数类型的文件描述符作为第一个参数传给 fcntl.fcntl,而不是传递Python的文件对象。这是从 另一个回答 中得到的。

下面是需要修改的代码:

chstdout = chldp.stdout
fd = chstdout.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
1

很遗憾,目前没有现成的方法来检查“管道中有足够的数据并且有换行符,这样readline()就能立即返回”。

如果你想一行一行地读取数据,并且不想让程序停下来,你可以选择:

要么自己通过一个类或生成器来实现缓冲,然后通过这个来检查,比如:

def linereader():
    data = ""
    while True:
        if poll(f.fd):
            data += f.read(100)
        lines = data.split("\n")
        data = lines[-1]
        for line in lines[:-1]:
            yield line

# use
for line in linereader():
    if line:
       print line
    else:
       time.sleep(...)

要么使用线程(这个留给读者自己去练习,注意旧版本的Python在从非主线程启动子进程时会有bug)。

4

你需要把文件描述符设置为非阻塞模式,可以通过使用 fcntl 来实现:

import sys, time, fcntl, os
from subprocess import *

chldp = Popen(sys.argv[1], bufsize=0, stdout=PIPE, close_fds=True)
chstdin, chstdout = chldp.stdin, chldp.stdout
fl = fcntl.fcntl(chstdout, fcntl.F_GETFL)
fcntl.fcntl(chstdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)

while chldp.poll() is not None:
    try:
        rrl = chstdout.readline()
    except IOError:
        time.sleep(0.1)
        continue
    # use rrl

当没有数据可用时,调用 readline() 会抛出一个 IOError 错误。

需要注意的是,由于 chldp.poll() 在子进程结束时可能会返回 0,所以在你的 while 循环中,最好使用 childp.poll() is not None,而不是 not childp.poll()

撰写回答