Ben*_* S. 27 python subprocess stderr
我有一个命令行工具(实际上是几个),我正在用Python编写包装器.
该工具通常使用如下:
$ path_to_tool -option1 -option2 > file_out
Run Code Online (Sandbox Code Playgroud)
用户将输出写入file_out,并且还能够在工具运行时查看该工具的各种状态消息.
我想复制此行为,同时还将stderr(状态消息)记录到文件中.
我有的是这个:
from subprocess import call
call(['path_to_tool','-option1','option2'], stdout = file_out, stderr = log_file)
Run Code Online (Sandbox Code Playgroud)
除了stderr没有写入屏幕之外,这工作正常.我当然可以添加代码来将log_file的内容打印到屏幕上,但是用户将在完成所有操作后看到它,而不是在它发生时.
总结一下,期望的行为是:
我有一种感觉,我要么错过了一些非常简单的东西,要么这比我想象的要复杂得多......感谢您的帮助!
编辑:这只需要在Linux上工作.
aba*_*ert 59
你可以这样做subprocess,但这不是微不足道的.如果查看文档中的常用参数,您将看到可以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()
Run Code Online (Sandbox Code Playgroud)
(注意,使用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)
Run Code Online (Sandbox Code Playgroud)
但我不想调试外壳管道; 让我们在Python中做,如文档中所示:
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()
Run Code Online (Sandbox Code Playgroud)
最后,有一打或周围的子过程和/或在PyPI-壳更多更高级别的包装sh,shell,shell_command,shellout,iterpipes,sarge,cmd_utils,commandwrapper,等等搜索"壳","子","过程","命令行"等等,找到你喜欢的那个让问题变得微不足道.
如果你需要收集stderr和stdout怎么办?
正如Sven Marnach在评论中所建议的那样,简单的方法就是将其中一个重定向到另一个.只需更改这样的Popen参数:
tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Run Code Online (Sandbox Code Playgroud)
然后在你使用的任何地方tool.stderr,使用tool.stdout替代 - 例如,为最后一个例子:
tee = subprocess.Popen(['tee', 'log_file'], stdin=tool.stdout)
tool.stdout.close()
tee.communicate()
Run Code Online (Sandbox Code Playgroud)
但这有一些权衡.最明显的是,将两个流混合在一起意味着您无法将stdout记录到file_out和stderr到log_file,或者将stdout复制到stdout和stderr到stderr.但它也意味着排序可能是非确定性的 - 如果子进程总是在向stdout写入任何内容之前将两行写入stderr,那么一旦混合流,最终可能会在这两行之间得到一堆stdout.这意味着他们必须共享stdout的缓冲模式,所以如果你依赖于linux/glibc保证stderr是行缓冲的事实(除非子进程明确地改变它),那可能不再是真的.
如果您需要单独处理这两个进程,则会变得更加困难.早些时候,我说只要你只有一个管道并且可以同步维修,就可以轻松维修管道.如果你有两个管道,那显然不再是真的.想象一下,你正在等待tool.stdout.read(),新的数据来自tool.stderr.如果数据太多,则可能导致管道溢出并阻止子进程.但即使没有发生这种情况,在stdout出现问题之前,你显然无法读取和记录stderr数据.
如果您使用直通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()
Run Code Online (Sandbox Code Playgroud)
但是,有些边缘情况不起作用.(问题是SIGCHLD和SIGPIPE/EPIPE/EOF到达的顺序.我不认为这会影响我们,因为我们没有发送任何输入......但是如果不考虑它就不要相信我通过和/或测试.)subprocess.communicate3.3+ 的功能可以正确地获取所有细节.但是你可能会发现使用PyPI和ActiveState上的async-subprocess包装器实现之一,甚至是像Twisted这样的完整异步框架的子进程内容更简单.
*文档并没有真正解释管道是什么,几乎就像他们希望你是一个旧的Unix C手...但是一些例子,特别是在用subprocess模块部分替换旧函数时,展示了它们是如何被使用的,这很简单.
**困难的部分是正确排序两个或多个管道.如果你在一个管道上等待,另一个可能会溢出并阻塞,阻止你在另一个管道上等待.解决这个问题的唯一简单方法是创建一个服务每个管道的线程.(在大多数*nix平台上,您可以使用select或poll反应堆,但是制作跨平台非常困难.)模块的来源,特别是communicate它的助手,显示了如何做到这一点.(我链接到3.3,因为在早期版本中,communicate本身会出现一些重要的错误...)这就是为什么,只要有可能,你想要使用,communicate如果你需要多个管道.在你的情况下,你不能使用communicate,但幸运的是你不需要多个管道.