提问者:小点点

子流程命令的实时输出


我使用python脚本作为流体动力学代码的驱动程序。当需要运行模拟时,我使用子流程。Popen要运行代码,请将stdoutstderr的输出收集到子进程中。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_命令传递到tee,这样一个副本直接发送到日志文件,流仍然直接输出到终端——但这样我就无法存储任何错误(据我所知)。

我目前的临时解决办法是:

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(s.t.log\u文件='log.txt')。


共3个答案

匿名用户

Python 3的TLDR:

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

您有两种方法可以做到这一点,要么从readreadline函数中创建迭代器,然后这样做:

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

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

或者您可以创建读取器写入器文件。将writer传递到Popen并从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())

这样,您将在测试中写入数据。记录标准输出上的日志。

文件方法的唯一优点是代码不会阻塞。因此,在此期间,您可以做任何您想做的事情,并以非阻塞方式随时从读取器中读取。使用PIPE时,readreadline函数将阻塞,直到分别向管道写入一个字符或一行。

匿名用户

也许是时候解释一下子流程是如何进行的了。Popen做它自己的事。

(注意:这是针对Python 2. x的,尽管3. x是相似的;我对视窗变体相当模糊。我更了解POSIX的东西。)

Popen函数需要同时处理零到三个I/O流。它们通常表示为stdinstdout、和stderr

您可以提供:

  • None,表示您不想重定向流。它将一如既往地继承这些。请注意,至少在POSIX系统上,这并不意味着它将使用Python的sys。stdout,只是Python的实际stdout;见最后的演示

如果您不重定向任何内容(将所有三个都保留为默认值或提供显式值),则管道非常容易。它只需要分离子进程并让它运行。或者,如果您重定向到一个非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 data\n') # etc

或者您可以将stdin数据传递到proc。communicate(),然后执行stdin。写入上面显示的。没有输出返回,因此communicate()只有一个真正的任务:它还为您关闭管道。(如果不调用proc.communicate()则必须调用proc。斯丁。close()关闭管道,以便子流程知道不再有数据通过。)

假设您想要捕获stdout,但不要使用stdinstderr。同样,这很容易:只需调用proc.stdout.read()(或等效),直到没有更多的输出。因为proc.stdout()是一个普通的Python I/O流,你可以在它上面使用所有的普通结构,比如:

for line in proc.stdout:

或者,您也可以使用proc。communicate(),它只为您执行读取()。

如果您只想捕获stderr,它的工作原理与stdout相同。

在事情变得困难之前还有一个技巧。假设您想要捕获stdout,并且还捕获stderr,但与stdout在同一个管道上:

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

在这种情况下,子流程“作弊”!好吧,它必须这样做,所以它不是真正的欺骗:它启动子进程时,将其stdout和stderr都定向到(单个)管道描述符中,该描述符将反馈给其父进程(Python)。在父端,同样只有一个管道描述符用于读取输出。所有“stderr”输出都显示在proc中。stdout,如果调用proc。communicate(),stderr结果(元组中的第二个值)将是None,而不是字符串。

当您想使用至少两个管道时,所有问题都会出现。实际上,子流程代码本身有以下位:

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上,这使用线程。线程来累积self.stdoutself.stderr的结果,并让父线程传递self.stdin输入数据(然后关闭管道)。

在POSIX上,如果可用,则使用轮询,否则使用选择来累积输出并传递stdin输入。所有这些都运行在(单)父进程/线程中。

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

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

同时,假设子流程决定打印友好的“您好!不要惊慌!”招呼H进入其标准输出管道,但e导致其挂起,等待其父级读取该H,清空标准输出管道。

现在我们被卡住了:Python进程睡着了,等着说完“go”,子进程也睡着了,等着说完“Hello!不要惊慌!"。

子流程。Popencode通过线程或选择/轮询避免了此问题。当字节可以通过管道时,它们就消失了。如果不能,则只有一个线程(而不是整个进程)必须Hibernate,或者在select/poll的情况下,Python进程同时等待“可以写入”或“数据可用”,只有在有空间时才写入进程的stdin,只有在数据就绪时才读取其stdout和/或stderr。proc。communicate()code(实际上是\u communicate在处理毛茸茸的案例时)在发送了所有stdin数据(如果有)并且积累了所有stdout和/或stderr数据后返回。

如果您想在两个不同的管道上同时读取stdoutstderr(不管任何stdin重定向),您也需要避免死锁。这里的死锁场景是不同的,当您从stdout提取数据时,子流程将一些长的内容写入stderr时,就会发生死锁,反之亦然,但它仍然存在。

我promise要演示,在未重定向的情况下,Python子进程将写入底层stdout,而不是sys。标准输出。下面是一些代码:

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,则第二个将省略hello。标准输出自系统。标准输出已重定向到操作系统。德夫努尔

(如果重定向Python的file-descriptor-1,子流程将遵循该重定向。调用open(os.devnull,'w')将生成一个fileno()大于2的流。)

匿名用户

我们还可以使用默认文件迭代器来读取stdout,而不是使用iter构造和readline()。

import subprocess
import sys
process = subprocess.Popen(your_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in process.stdout:
    sys.stdout.write(line)