【问题标题】:subprocess.Popen : Separating stdout/stderr but keeping ordersubprocess.Popen :分离标准输出/标准错误但保持秩序
【发布时间】:2016-01-12 16:30:17
【问题描述】:

如何使用 subprocess.Popen 从命令中获取输出,并为 stdout 和 stderr 提供单独的回调,但要确保按照行来自进程的顺序调用这些回调?

如果我不关心分离 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,它们会将每一行输出插入到共享列表中,并带有一个标识符管道来自。然后主线程可以观察这个列表并调用适当的回调。互斥锁快乐!

标签: python subprocess


【解决方案1】:
fd = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
output,error = fd.communicate()

使用communicate

【讨论】:

  • 谢谢。这不是我所追求的。这似乎要等到该过程完成,然后给我一个完整的(stdout,stderr)元组的元组。我写了一个测试小python脚本,打印“stdout 1”然后是“stderr 2”,然后是“stdout 3”然后是“stderr 4”,每个之间有1秒的延迟。使用 fd.communicate() 给我 ("stdout 1\nstdout 3\n", "stderr 2\nstderr 4") 没有指示相对顺序是什么。
  • 它不保留顺序。
【解决方案2】:

所以我想我已经推出了几个线程。

对于下面的示例,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) 它不必要地复杂 (compare with this) (3) 最后可能会丢失数据
【解决方案3】:

这是不可能的。如果对不同的文件执行writes,则没有定义顺序。

如果writes 到stdout,stderr 去同一个地方,你可以得到正确的顺序(就像你的stdout=PIPE, stderr=STDOUT case)。

如果“近似”顺序就足够了;这是simple code example with threads,这是single-threaded version with a select-loop

【讨论】: