你可以做到这一点与subprocess
,但它不是微不足道的。如果您查看文档中的Frequently Used Arguments,您会发现可以通过PIPE
作为stderr
参数,该参数创建一个新管道,将管道的一端传递给子进程,并使另一端可用作stderr
属性。*
因此,您将需要服务该管道,写入屏幕和文件。一般情况下,为此获取详细信息非常棘手。**在您的情况下,只有一个管道,并且您计划同步进行维护,所以没有那么糟糕。
import subprocess
proc = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=file_out, stderr=subprocess.PIPE)
for line in proc.stderr:
sys.stdout.write(line)
log_file.write(line)
proc.wait()
(注意有使用for line in proc.stderr:
一些问题-basically,如果你正在读证明不是要行缓冲以任何理由,你可以坐在那里等待换行符,即使实际上有一半需要处理一行数据,如果需要,你可以一次读取数据块,例如read(128)
,甚至可以使用read(1)
来获得数据更加平滑的数据,如果你需要一到达每一个字节就可以得到数据,并且可以承担read(1)
的费用,您需要将管道置于非阻塞模式并异步读取。)
但是,如果您使用的是Unix,使用tee
命令可能会更简单。
对于快速的&肮脏的解决方案,您可以使用shell来穿过它。这样的事情:
subprocess.call('path_to_tool -option1 option2 2|tee log_file 1>2', shell=True,
stdout=file_out)
但我不想调试壳管道;让我们做它在Python,如图in the docs:
tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=file_out, stderr=subprocess.PIPE)
tee = subprocess.Popen(['tee', 'log_file'], stdin=tool.stderr)
tool.stderr.close()
tee.communicate()
最后,有十几个或更多的围绕子流程和/或PyPI- sh
,shell
,shell_command
,shellout
外壳更高层次的包装,搜索“shell”,“subprocess”,“process”,“command line”等,找到一个你喜欢的,这使得问题变得微不足道。
如果您需要收集stderr和stdout,该怎么办?
最简单的方法就是将其中一个重定向到另一个,就像Sven Marnach在评论中提出的那样。只要改变Popen
参数是这样的:
tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
然后到处使用tool.stderr
,使用tool.stdout
代替-e.g,对于最后一个例子:
tee = subprocess.Popen(['tee', 'log_file'], stdin=tool.stdout)
tool.stdout.close()
tee.communicate()
但是这有一定的权衡。最明显的是,将两个流混合在一起意味着您不能将stdout记录到file_out和stderr以log_file,或将stdout复制到stdout和stderr到您的stderr。但是这也意味着排序可能是非确定性的 - 如果在将任何内容写入stdout之前,子进程总是写两行到stderr,那么一旦混合了这些流,您可能会在这两行之间得到一堆stdout。这意味着他们必须共享stdout的缓冲模式,所以如果你依赖的是linux/glibc保证stderr被行缓冲(除非子进程明确地改变它),这可能不再是真实的。
如果您需要分开处理这两个过程,则会变得更加困难。早些时候,我说过,只要你只有一根管道并且可以同步维修,就可以轻松地维修管道。如果你有两个管道,那显然不再是真的。想象一下,你正在等待tool.stdout.read()
,新数据来自tool.stderr
。如果数据太多,可能会导致管道溢出并阻塞子进程。但即使这种情况没有发生,您显然将无法读取并记录stderr数据,直到从stdout中输入内容为止。
如果您使用pipe-through-tee
解决方案,那就避免了最初的问题......但只能通过创建一个同样糟糕的新项目。你有两个tee
实例,当你打电话给communicate
时,另一个坐在一旁等待。
因此,无论哪种方式,都需要某种异步机制。你可以做到这一点是与线程,select
反应堆,如gevent
,等等。
这里有一个快速和肮脏的例子:
proc = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def tee_pipe(pipe, f1, f2):
for line in pipe:
f1.write(line)
f2.write(line)
t1 = threading.Thread(target=tee_pipe, args=(proc.stdout, file_out, sys.stdout))
t2 = threading.Thread(target=tee_pipe, args=(proc.stderr, log_file, sys.stderr))
t3 = threading.Thread(proc.wait)
t1.start(); t2.start(); t3.start()
t1.join(); t2.join(); t3.join()
然而,也有一些边缘情况下,这是行不通的。 (问题在于SIGCHLD和SIGPIPE/EPIPE/EOF到达的顺序,我不认为这会影响到我们,因为我们没有发送任何输入信息......但是不要相信我通过和/或测试)。从3.3+的subprocess.communicate
函数获得所有的细节。但是您可能会发现使用PyPI和ActiveState上可以找到的一个异步子进程包装器实现,或者甚至是像Twisted这样的完整异步框架中的子进程内容,都会更加简单。
*的文档并不真正说明什么管道是,仿佛他们希望你是一个老Unix下C手......但一些例子,特别是在Replacing Older Functions with the subprocess
Module部分,展示他们如何是使用,而且非常简单。
**困难的部分是正确排序两个或多个管道。如果你等待一个管道,另一个可能会溢出并阻塞,从而阻止你等待另一个管道完成。解决这个问题的唯一简单方法是创建一个线程来服务每个管道。 (在大多数* nix平台上,您可以使用select
或poll
电抗器,但是使该跨平台非常困难。)The source模块,特别是communicate
及其帮助程序显示如何执行此操作。 (我链接到3.3,因为在早期版本中,communicate
本身有一些重要的错误...)这就是为什么,只要有可能,如果您需要多个管道,则要使用communicate
。在你的情况下,你不能使用communicate
,但幸运的是你不需要多个管道。
请问你的代码需要在Windows(或其他非POSIXy平台)工作的?如果不是,则有一个更简单的答案。 – abarnert
它不需要! –
相关:[Python子进程获取儿童输出到文件和终端?](http://stackoverflow.com/q/4984428/4279) – jfs