我使用python脚本作为流体动力学代码的驱动程序。当需要运行模拟时,我使用子流程。Popen
要运行代码,请将stdout
和stderr
的输出收集到子进程中。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'
)。
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)
您有两种方法可以做到这一点,要么从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)
或者您可以创建读取器
和写入器
文件。将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
时,read
和readline
函数将阻塞,直到分别向管道写入一个字符或一行。
也许是时候解释一下子流程是如何进行的了。Popen做它自己的事。
(注意:这是针对Python 2. x的,尽管3. x是相似的;我对视窗变体相当模糊。我更了解POSIX的东西。)
Popen
函数需要同时处理零到三个I/O流。它们通常表示为stdin
、stdout
、和stderr
。
您可以提供:
None
,表示您不想重定向流。它将一如既往地继承这些。请注意,至少在POSIX系统上,这并不意味着它将使用Python的sys。stdout
,只是Python的实际stdout;见最后的演示李>如果您不重定向任何内容(将所有三个都保留为默认值或提供显式值),则管道非常容易。它只需要分离子进程并让它运行。或者,如果您重定向到一个非PIPE
-一个int
或一个流的fileno()
-这仍然很容易,因为操作系统完成了所有的工作。Python只需要分离子进程,将其stdin、stdout和/或stderr连接到提供的文件描述符。
如果只重定向一个流,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。写入上面显示的
。没有输出返回,因此communicate()
只有一个真正的任务:它还为您关闭管道。(如果不调用proc.communicate()
则必须调用proc。斯丁。close()
关闭管道,以便子流程知道不再有数据通过。)
假设您想要捕获stdout
,但不要使用stdin
和stderr
。同样,这很容易:只需调用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.stdout
和self.stderr
的结果,并让父线程传递self.stdin
输入数据(然后关闭管道)。
在POSIX上,如果可用,则使用轮询
,否则使用选择
来累积输出并传递stdin输入。所有这些都运行在(单)父进程/线程中。
这里需要线程或轮询/选择以避免死锁。例如,假设我们已将所有三个流重定向到三个单独的管道。进一步假设在写入过程暂停之前,在等待读取过程从另一端“清除”管道之前,可以向管道中填充多少数据有一个小的限制。让我们将该小限制设置为单个字节,仅用于说明。(事实上,这就是事情的工作原理,只是限制远远大于一个字节。)
如果父进程(Python)试图写入几个字节,比如说,'go\n'
到proc。stdin
,第一个字节进入,然后第二个字节导致Python进程挂起,等待子进程读取第一个字节,清空管道。
同时,假设子流程决定打印友好的“您好!不要惊慌!”招呼H
进入其标准输出管道,但e
导致其挂起,等待其父级读取该H
,清空标准输出管道。
现在我们被卡住了:Python进程睡着了,等着说完“go”,子进程也睡着了,等着说完“Hello!不要惊慌!"。
子流程。Popen
code通过线程或选择/轮询避免了此问题。当字节可以通过管道时,它们就消失了。如果不能,则只有一个线程(而不是整个进程)必须Hibernate,或者在select/poll的情况下,Python进程同时等待“可以写入”或“数据可用”,只有在有空间时才写入进程的stdin,只有在数据就绪时才读取其stdout和/或stderr。proc。communicate()
code(实际上是\u communicate
在处理毛茸茸的案例时)在发送了所有stdin数据(如果有)并且积累了所有stdout和/或stderr数据后返回。
如果您想在两个不同的管道上同时读取stdout
和stderr
(不管任何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)