来自subprocess命令的实时输出

我使用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 failednn%snn" % (errors)
    success = False

if( errors ): log_file.write("nn%snn" % errors)

本来我是通过tee管道run_command ,以便副本直接进入日志文件,并且流仍然直接输出到终端 - 但这样我就不能存储任何错误(以我的知识)。


编辑:

临时解决方案:

ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
    log_file.flush()

然后在另一个终端中运行tail -f log.txt (st log_file = 'log.txt' )。


有两种方法可以做到这一点,无论是通过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文件。 将writer传给Popen并从reader那里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 ,但是“不可能”描述符,-1和-2。)
  • 流 - 真的,任何带有fileno方法的对象。 Popen将使用stream.fileno()找到该流的描述符,然后继续处理int值。
  • subprocess.PIPE ,表明Python应该创建一个管道。
  • subprocess.STDOUT (仅适用于stderr ):告诉Python使用与stdout相同的描述符。 如果您为stdout提供了(非None )值,则这是唯一有意义的,即使如此,只有在您设置stdout=subprocess.PIPE时才需要。 (否则,你可以只提供你提供给stdout参数,例如, Popen(..., stdout=stream, stderr=stream) 。)
  • 最简单的情况(无管道)

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

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

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

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

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

    或者您可以将stdin数据传递给proc.communicate() ,然后执行stdin.write显示的stdin.write 。 没有输出回来所以communicate()只有一个其他真正的工作:它也关闭你的管道。 (如果你不调用proc.communicate()你必须调用proc.stdin.close()来关闭管道,这样子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都引导到(单个)管道描述符(它反馈到其父进程(Python)进程)的子进程中。 在父级方面,再次只有一个管道描述符用于读取输出。 所有“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.Thread来累积self.stdoutself.stderr结果,并且父线程传递self.stdin输入数据(然后关闭管道)。

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

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

    如果父(Python)进程尝试写入几个字节 - 比如说'gon'proc.stdin ,则第一个字节进入,然后第二个字节导致Python进程挂起,等待子进程读取第一个字节,清空管道。

    同时,假设子进程决定打印友好的“你好!不要惊慌!” 问候。 H进入它的stdout管道,但是e导致它暂停,等待它的父节点读取H ,清空stdout管道。

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

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

    如果你想在两个不同的管道上读取stdoutstderr (不管任何stdin重定向),你也需要避免死锁。 这里的死锁情况是不同的 - 当你从stdout提取数据时,子进程写入长的stderr ,反之亦然 - 但它仍然存在。


    演示

    我承诺说明,未重定向的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 。 如果添加stdout=sys.stdout那么第二个会省略hello ,因为sys.stdout已被重定向到os.devnull

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


    如果你能够使用第三方库,你可能可以使用sarge东西(披露:我是它的维护者)。 该库允许从子进程输出流的非阻塞访问 - 它在subprocess模块上分层。

    链接地址: http://www.djcxy.com/p/77147.html

    上一篇: live output from subprocess command

    下一篇: Catching and outputting stderr at the same time with python's subprocess