subprocess.Popen:分离stdout/stderr但保持顺序

5

如何使用subprocess.Popen获取命令的输出,并为标准输出和标准错误设置单独的回调函数,但确保这些回调函数按从进程中获取的行的顺序调用?

如果我不关心将STDOUT和STDERR分开,则可以执行以下操作:

fd = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
line = fd.stdout.readline()
while line :
    callback( line )
    line = fd.stdout.readline()

然而,如果我有stdoutCallbackstderrCallback,并且希望它们在适当的输出上被调用,但顺序与上述代码调用callback的顺序相同,我该如何做?


我在考虑通过生成一对线程来自己完成这个任务,一个用于 STDOUT,另一个用于 STDERR。它们将把每行输出插入到共享列表中,并标识出该行来自哪个管道。主线程可以监视此列表并调用适当的回调函数。互斥锁真是太棒了! - Hugh
3个回答

1
我认为我已经用了一些线程来自己实现。
对于下面的示例,test.py 是这样的:
#!/usr/bin/python -u

import sys
import time

sys.stdout.write("stdout 1\n")
time.sleep(1)
sys.stderr.write("stderr 2\n")
time.sleep(1)
sys.stdout.write("stdout 3\n")
time.sleep(1)
sys.stderr.write("stderr 4\n")
time.sleep(1)

获取正确输出的代码如下:

#!/usr/bin/env python

import subprocess
from threading import Thread, Lock

cmdOutput = []
cmdOutputLock = Lock()
STDOUT = 1
STDERR = 2

def _outputLoop( fd, identifier ) :
    line = fd.readline()
    while line :
        cmdOutputLock.acquire()
        cmdOutput.append( ( line, identifier ) )
        cmdOutputLock.release()
        line = fd.readline()

p = subprocess.Popen( "test.py",
                      stdout = subprocess.PIPE,
                      stderr = subprocess.PIPE )

Thread( target=_outputLoop, args=( p.stdout, STDOUT ) ).start()
Thread( target=_outputLoop, args=( p.stderr, STDERR ) ).start()

while fd.poll() is None or cmdOutput :
    output = None
    cmdOutputLock.acquire()
    if cmdOutput :
        output = cmdOutput[0]
        del cmdOutput[0]
    cmdOutputLock.release()

    if output :
        if output[1] == STDOUT :
            print "STDOUT : {}".format( output[0].rstrip() )
        elif output[1] == STDERR :
            print "STDERR : {}".format( output[0].rstrip() )

我可以想象在某个时刻,stderr一行可能会与stdout一行混淆,但就我所需而言,它肯定有效。 (我将其作为日志模块的一部分放置,该模块将运行命令并对stdout和stderr使用不同的日志级别。)

(1) 在一般情况下,它仍然无法保持顺序。 (2) 它过于复杂。(与此进行比较:https://dev59.com/Q1wZ5IYBdhLWcg3wINLQ#31867499) (3) 它可能会在结尾处丢失数据。 - jfs

1
这是不可能的。如果将write操作执行到不同的文件中,则不会定义顺序。
如果write操作到stdout、stderr都指向相同的位置(比如在你的stdout=PIPE, stderr=STDOUT情况下),则可以获得正确的顺序。
如果“近似”顺序就足够了;这里有一个使用线程的简单代码示例,这里有一个使用select循环的单线程版本

0
fd = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
output,error = fd.communicate()

使用 communicate

1
谢谢。但这不是我想要的。它似乎等待进程完成,然后给我一个完整的元组(stdout,stderr)元组。我编写了一个测试小Python脚本,打印“stdout 1”,然后是“stderr 2”,然后是“stdout 3”,然后是“stderr 4”,每个之间有1秒的延迟。使用fd.communicate()会给我("stdout 1\nstdout 3\n", "stderr 2\nstderr 4"),但没有指示相对顺序是什么。 - Hugh
不保留顺序。 - jfs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接