来自subprocess命令的实时输出?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (77)

我使用Python脚本作为流体动力学代码的驱动程序。当运行模拟时,我使用subprocess.Popen运行代码,将stdout和stderr的输出收集到subprocess.PIPE---然后我可以打印(并保存到日志文件)输出信息,并检查是否有错误。问题是,我不知道代码是如何进步的。如果我直接从命令行运行它,它会给出关于它在什么时候迭代的输出,什么时候,什么是下一个时间步,等等。

有没有一种方法可以存储输出(用于记录和错误检查),还可以生成实时流输出?

我的代码的相关部分:

ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
    print "RUN failed\n\n%s\n\n" % (errors)
    success = False

if( errors ): log_file.write("\n\n%s\n\n" % errors)

最初我是run_command通过管道tee传输,直接复制到日志文件,而流仍然直接输出到终端 - 但这样我就不能存储任何错误(我知道)。

提问于
用户回答回答于

你有这样做的方式有两种:通过创建从一个迭代readreadline功能,并做到:

import subprocess
import sys
with open('test.log', 'w') as f:
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for c in iter(lambda: process.stdout.read(1), ''):
        sys.stdout.write(c)
        f.write(c)

要么

import subprocess
import sys
with open('test.log', 'w') as f:
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for line in iter(process.stdout.readline, ''):
        sys.stdout.write(line)
        f.write(line)

或者你可以创建一个reader和一个writer文件。传递writerPopen并从中读取reader

import io
import time
import subprocess
import sys

filename = 'test.log'
with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader:
    process = subprocess.Popen(command, stdout=writer)
    while process.poll() is None:
        sys.stdout.write(reader.read())
        time.sleep(0.5)
    # Read the remaining
    sys.stdout.write(reader.read())

这样你就可以将数据写入test.log标准输出中。

文件方法的唯一好处是你的代码不会被阻塞。所以你可以在这期间做任何你想做的事情,并且在你想要的时候reader以非阻塞的方式阅读。当使用PIPEreadreadline功能将阻塞,直到任一个字符被写入到管或线被分别写入到管道。

用户回答回答于

执行摘要(或“tl; dr”版本):最多只有一个时很容易subprocess.PIPE,否则很难。

它可能是时候解释一下subprocess.Popen它的事情。

(注意:这是为了Python 2.x,尽管3.x很相似,而且我对Windows的变体很模糊,我理解POSIX的东西要好得多。)

Popen功能需要同时处理零到三个I / O流。这些被表示stdinstdoutstderr像往常一样。

您可以提供:

  • None,表明你不想重定向流。它将像往常一样继承这些。请注意,至少在POSIX系统上,这并不意味着它会使用Python的sys.stdout,只是Python的实际标准输出; 最后看演示。
  • 一个int值。这是一个“原始”文件描述符(至少在POSIX中)。(旁注:PIPESTDOUT实际上int,内部S中,但都是“不可能”的描述,-1,-2)
  • 流 - 真的,任何带有fileno方法的对象。 Popen将找到该流的描述符stream.fileno(),并使用该int值,然后继续处理该值。
  • subprocess.PIPE,表明Python应该创建一个管道。
  • subprocess.STDOUTstderr仅限于):告诉Python使用与for相同的描述符stdout。这只有在你提供了(非None)值的时候才有意义stdout,甚至只有在你设置时才需要stdout=subprocess.PIPE。(否则,您可以提供您提供的相同参数stdout,例如Popen(..., stdout=stream, stderr=stream)。)

最简单的情况(无管道)

如果不重定向(将所有三个都作为默认None值或明确提供None),Pipe是否很容易。它只需要分离子流程并让它运行。或者,如果您重定向到非PIPE-an int或流fileno()- 它仍然很容易,因为操作系统完成所有工作。Python只需要分离子进程,将stdin,stdout和/或stderr连接到提供的文件描述符。

仍然简单的情况:一个管道

如果你只重定向一个流,Pipe仍然很容易。我们一次选择一个流并观看。

假设你想提供一些stdin,但是让stdoutstderr不去重定向,或者去一个文件描述符。作为父进程,您的Python程序只需要write()用来将数据发送到管道。你可以自己做这个,例如:

proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc

或者您可以将stdin数据传递给proc.communicate(),然后执行stdin.write上面显示的内容。没有输出回来,所以communicate()只有一个其他的实际工作:它也为你关闭管道。(如果你不打电话,proc.communicate()你必须调用proc.stdin.close()关闭管道,这样子进程就知道没有更多的数据通过了。)

假设你想捕捉,stdout但离开stdinstderr孤独。再次,这很简单:直接调用proc.stdout.read()(或等价)直到没有更多输出。既然proc.stdout()是一个普通的Python I / O流,你可以使用它的所有常规结构,比如:

for line in proc.stdout:

或者,再次,您可以使用proc.communicate(),它只是read()为您做。

如果你只想捕捉stderr,它的工作原理与之相同stdout

事情变得困难之前还有一个窍门。假设你想捕获stdout,并且捕获,stderr在与stdout相同的管道上:

proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

在这种情况下,subprocess“欺骗”!嗯,它必须这样做,所以它并不是真正的作弊:它将stdout和stderr都引导到(反馈)父级(P​​ython)进程的(单个)管道描述符中来启动子进程。在父级方面,再次只有一个管道描述符用于读取输出。所有的“stderr”输出都会显示出来proc.stdout,如果您调用proc.communicate(),stderr结果(元组中的第二个值)将是None,而不是字符串。

困难的情况:两个或更多的管道

当你想使用至少两个管道时,所有问题都会出现。事实上,subprocess代码本身有这样的一点:

def communicate(self, input=None):
    ...
    # Optimization: If we are only using one pipe, or no pipe at
    # all, using select() or threads is unnecessary.
    if [self.stdin, self.stdout, self.stderr].count(None) >= 2:

但是,唉,在这里我们至少制造了两个,也许是三个不同的管道,所以count(None)回报率为1或0.我们必须以艰难的方式做事。

在Windows上,这用于threading.Threadself.stdoutand 累加结果self.stderr,并且父线程提供self.stdin输入数据(然后关闭管道)。

在POSIX上,这将使用(poll如果可用),否则select,将累积输出并提供stdin输入。所有这些都在(单个)父进程/线程中运行。

此处需要线程或轮询/选择以避免死锁。例如,假设我们已将所有三个流重定向到三个单独的管道。进一步假设在写入过程暂停之前可以将多少数据填充到管道中有一个小的限制,等待读取过程从另一端“清除”管道。我们将这个小限制设置为单个字节,仅用于说明。(事实上​​这是事情的方式,除了限制比一个字节大得多。)

如果父(Python)的进程尝试写几个字节,比如说,'go\n'proc.stdin,第一个字节进去,然后第二个使Python进程暂停,等待子进程读取第一个字节,排空管道。

同时,假设子进程决定打印友好的“你好!不要惊慌!” 问候。在H进入它的标准输出管道,但e导致其暂停,等待其家长阅读H,排空stdout管道。

现在我们陷入了困境:Python进程处于睡眠状态,等待完成说“去”,并且子进程也在睡觉,等待完成说“Hello!Do not Panic!”。

subprocess.Popen代码避免了线程或选择/轮询的这个问题。当字节可以通过管道时,他们走。当它们不能时,只有一个线程(不是整个进程)必须睡眠 - 或者在select / poll的情况下,Python进程同时等待“可写入”或“可用数据”,写入进程的stdin只有当有空间时,并且只有在数据准备就绪时才读取它的stdout和/或stderr。一旦所有stdin数据(如果有)被发送并且所有stdout和/或stderr数据已经累积,proc.communicate()代码(实际上_communicate处理毛囊的情况)会返回。

如果你想同时读取stdoutstderr在两个不同的管道(无论任何的stdin重定向),则需要避免死锁了。这里的死锁场景是不同的 - 当子进程stderr在你从中提取数据时写入很长的内容时发生stdout,反之亦然 - 但它仍然存在。

演示

我承诺说明,未重定向的Python subprocess将其写入底层的stdout,而不是sys.stdout。所以,这是一些代码:

from cStringIO import StringIO
import os
import subprocess
import sys

def show1():
    print 'start show1'
    save = sys.stdout
    sys.stdout = StringIO()
    print 'sys.stdout being buffered'
    proc = subprocess.Popen(['echo', 'hello'])
    proc.wait()
    in_stdout = sys.stdout.getvalue()
    sys.stdout = save
    print 'in buffer:', in_stdout

def show2():
    print 'start show2'
    save = sys.stdout
    sys.stdout = open(os.devnull, 'w')
    print 'after redirect sys.stdout'
    proc = subprocess.Popen(['echo', 'hello'])
    proc.wait()
    sys.stdout = save

show1()
show2()

运行时:

$ python out.py
start show1
hello
in buffer: sys.stdout being buffered

start show2
hello

请注意,如果添加stdout=sys.stdout,第一个例程将失败,因为StringIO对象没有fileno。第二个将省略,hello如果你添加,stdout=sys.stdout因为sys.stdout已被重定向到os.devnull

(如果重定向Python的文件描述符-1,则子进程遵循该重定向,该open(os.devnull, 'w')调用会生成一个fileno()大于2 的流。)

扫码关注云+社区