我正在使用python脚本作为水动力代码的驱动程序。是时候运行模拟了,我subprocess.Popen用来运行代码,将stdout和stderr的输出收集到subprocess.PIPE---中,然后我可以打印(并保存到日志文件中)输出信息,并检查是否有错误。问题是,我不知道代码是如何进行的。如果我直接从命令行运行它,它将为我提供有关其迭代次数,时间,下一时间步长等的输出。
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文件,以便将副本直接发送到日志文件,并且流仍直接输出到终端-但是那样,我无法存储任何错误(据我所知)。
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')。
tail -f log.txt(st log_file = 'log.txt')
你可以通过两种方法执行此操作,或者通过从read或readline函数创建迭代器,然后执行以下操作:
read
readline
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)
或者,你可以创建reader和writer文件。将传递writer到Popen并从中读取reader
reader
writer
Popen
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和标准输出中。
test.log
文件方法的唯一优点是你的代码不会被阻塞。因此,你可以同时做任何你想做的事,并reader以不阻塞的方式随时阅读。当使用PIPE,read和readline功能将阻塞,直到任一个字符被写入到管或线被分别写入到管道。
PIPE,read
执行摘要(或“ tl; dr”版本):最多有一个很容易subprocess.PIPE,否则很难。 现在可能是时候解释一下它是如何subprocess.Popen工作的了。
“ tl; dr”
subprocess.PIPE
(注意:这是针对Python 2.x的,尽管3.x相似;并且我对Windows变体很模糊。我对POSIX的了解要好得多。)
该Popen功能需要同时处理零到三个I / O流。分别以stdin,stdout和表示stderr。
I / O流
stdin
你可以提供:
Python
sys.stdout
int
stream.fileno()
subprocess.STDOUT
Popen(..., stdout=stream, stderr=stream)
仍然很容易的情况:一根烟斗 如果仅重定向一个流,那么Pipe事情仍然很简单。让我们一次选择一个流并观看。
假设你想提供一些stdin,但让stdout和stderr去未重定向,或去文件描述符。作为父进程,你的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()以关闭管道,以便子进程知道不再有数据通过。)
proc.communicate()
stdin.write
communicate()
proc.stdin.close()
假设你想捕捉stdout,但休假stdin和stderr孤独。同样,这很容易:只需调用proc.stdout.read()(或等效命令),直到没有更多输出为止。由于proc.stdout()是普通的Python I / O流,因此可以在其上使用所有普通的构造,例如:
for line in proc.stdout:
或者,你也可以使用proc.communicate(),它可以read()为你轻松完成。
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
proc.stdout
proc.communicate(),stderr
困难的情况:两个或多个管道 当你要使用至少两个管道时,所有问题都会出现。实际上,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.stdout和的结果self.stderr,并让父线程传递self.stdin输入数据(然后关闭管道)。
threading.Thread
self.stdout
self.stderr
self.stdin
在POSIX上,poll如果可用,则使用,否则select,使用累加输出并传递标准输入。所有这些都在(单个)父进程/线程中运行。
select
这里需要线程或轮询/选择以避免死锁。例如,假设我们已将所有三个流重定向到三个单独的管道。进一步假设在写入过程被挂起之前,等待读取过程从另一端“清除”管道之前,可以在管道中填充多少数据有一个很小的限制。让我们为单个字节设置一个小的限制。(实际上,这是工作原理,但限制远大于一个字节。)
如果父(Python)进程尝试写入多个字节,例如'go\n'到proc.stdin,则第一个字节进入,然后第二个字节导致Python进程挂起,等待子进程读取第一个字节,从而清空管道。
'go\n'到proc.stdin
同时,假设子流程决定打印一个友好的“ Hello!Do n't Panic!”。问候。在H进入它的标准输出管道,但e导致其暂停,等待其家长阅读H,排空stdout管道。
“ Hello!Do n't Panic!”
现在我们陷入困境:Python进程正在睡眠,等待说完“ go”,而子进程也处于睡眠状态,等待说完“ Hello!Don Panic!”。
Hello!Don Panic
该subprocess.Popen代码避免了线程化或选择/轮询的问题。当字节可以通过管道时,它们就会通过。如果不能,则只需要一个线程(而不是整个进程)就可以进入睡眠状态;或者,在进行选择/轮询的情况下,Python进程同时等待“可以写入”或“可用数据”,然后写入该进程的stdin仅在有空间时,并且仅在数据准备好时读取其stdout和/或stderr。一旦发送完所有stdin数据(如果有的话)并且所有stdout和/或stderr数据都已存储,该proc.communicate()代码(实际上_communicate是处理多毛案件的地方)将返回。
stdout和/或stderr
如果你想同时读取stdout并stderr在两个不同的管道(无论任何的stdin重定向),则需要避免死锁了。此处的死锁情况有所不同-发生在子进程stderr从中提取数据时写入了很长时间stdout,反之亦然,但是这种情况仍然存在。
演示 我答应演示未经重定向的python subprocess写入底层标准输出,而不是sys.stdout。所以,这是一些代码:
python subprocess
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的file-descriptor-1,则子进程将遵循该重定向。该open(os.devnull, ‘w’)调用将产生一个fileno()大于2 的流。)