如何从subprocess.communicate()中捕获python中的流输出

jak*_*aka 4 python subprocess popen

目前,我有这样的事情:

self.process = subprocess.Popen(self.cmd, stdout=subprocess.PIPE)
out, err = self.process.communicate()
Run Code Online (Sandbox Code Playgroud)

我正在运行的命令将输出流式传输,在继续之前,我需要阻塞该过程。

如何做到这一点,以便可以捕获流输出并通过stdout打印流输出?设置时stdout=subprocess.PIPE,我可以捕获输出,但不会打印输出。如果我不进行操作stdout=subprocess.PIPE,它将打印输出,但communicate()将返回None

有没有一种解决方案可以满足我的要求,即在进程终止/完成之前提供阻塞,并避免此处提到的缓冲区问题和管道死锁问题?

谢谢!

aba*_*ert 5

我可以想到一些解决方案。

#1:您可以直接进入源代码以获取,复制和粘贴的代码communicate,并添加用于打印每行内容的代码以及对它们进行缓冲的代码。(如果您自己可能stdout由于死锁的父母而被阻止,则可以改用a threading.Queue或其他东西。)这显然有点棘手,但这很容易,而且很安全。

但实际上,这communicate很复杂,因为它需要完全通用,并处理您不需要的情况。您需要的只是中心技巧:在问题上抛出线程。read您只需要一个专用的读取器线程,它不会使任何操作变慢或在两次调用之间阻塞。

像这样:

self.process = subprocess.Popen(self.cmd, stdout=subprocess.PIPE)
lines = []
def reader():
    for line in self.process.stdout:
        lines.append(line)
        sys.stdout.write(line)
t = threading.Thread(target=reader)
t.start()
self.process.wait()
t.join()
Run Code Online (Sandbox Code Playgroud)

您可能需要在reader线程中进行一些错误处理。我不确定100%是否可以readline在这里安全使用。但这要么有效,要么接近。

#2:或者,您可以创建一个包装类,需要一个文件对象和三通到stdout/ stderr每一次有人read从中秒。然后,手动创建管道,然后传递包裹的管道,而不是使用automagic PIPE。这个问题与#1完全相同(意味着没有问题,或者您需要使用a Queue或其他内容(如果sys.stdout.write可以阻止))。

像这样:

class TeeReader(object):
    def __init__(self, input_file, tee_file):
        self.input_file = input_file
        self.tee_file = tee_file
    def read(self, size=-1):
        ret = self.input_file.read(size)
        if ret:
            self.tee_file.write(ret)
        return ret
Run Code Online (Sandbox Code Playgroud)

换句话说,它包装了一个文件对象(或类似对象的东西),并像一个文件对象一样工作。(当使用时PIPEprocess.stdout在Unix上是一个真实的文件对象,但在Windows上可能只是一个类似的对象。)您需要委派的任何其他方法input_file都可以直接委派,而无需进行任何额外包装。要么试试这个,看看有什么方法communicate得到AttributeException,正在寻找和代码的那些明确,或者做一般的__getattr__伎俩委托的一切。PS,如果您担心这种“文件对象”的想法,即磁盘存储,请阅读Wikipedia上的Everything是一个文件

#3:最后,您可以获取PyPI上的“异步子进程”模块之一,或将其包含在twisted其他异步框架中或使用它。(这使得它能够避免死锁问题,但它不保证 -你还是要确保服务管道正常。)