Python: subprocess.call, stdout to file, stderr to file, display stderr on screen in real time
我有一个命令行工具(实际上是几个),我正在用Python编写包装器。
该工具通常是这样使用的:
1 | $ path_to_tool -option1 -option2 > file_out |
用户将输出写入file_out,并且还可以在工具运行时查看其各种状态消息。
我想复制此行为,同时还将stderr(状态消息)记录到文件中。
我所拥有的是:
1 2 | from subprocess import call call(['path_to_tool','-option1','option2'], stdout = file_out, stderr = log_file) |
除未将stderr写入屏幕外,此方法都可以正常工作。
我当然可以添加代码以将log_file的内容打印到屏幕上,但是随后用户将在完成所有操作后而不是在执行操作时看到它。
概括地说,所需的行为是:
该工具已直接从命令行调用。
我觉得我要么错过了一些非常简单的事情,要么比我想的要复杂得多……感谢您的帮助!
编辑:这只需要在Linux上工作。
您可以使用
好。
因此,您将需要维修该管道,并写入屏幕和文件。通常,正确地获取详细信息非常棘手。**在您的情况下,只有一个管道,并且您打算同步维护它,所以还不错。
好。
1 2 3 4 5 6 7 | 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() |
(请注意,使用
好。
但是,如果您使用的是Unix,则使用
好。
对于快速,肮脏的解决方案,您可以使用外壳通过管道进行管理。像这样:
好。
1 2 | subprocess.call('path_to_tool -option1 option2 2|tee log_file 1>2', shell=True, stdout=file_out) |
但是我不想调试外壳管道。让我们在python中进行操作,如文档所示:
好。
1 2 3 4 5 | 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上的子进程和/或shell周围有十几个或更多个更高级别的包装器-
好。
如果您需要同时收集stderr和stdout怎么办?
好。
正如Sven Marnach在评论中建议的那样,简单的方法是将一个重定向到另一个。只需像这样更改
好。
1 2 | tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
然后在所有使用
好。
1 2 3 | 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被行缓冲(除非子进程显式更改了它)这一事实,可能不再成立。
好。
如果您需要分别处理这两个过程,则将变得更加困难。之前,我说过,只要您只有一个管道并且可以同步维护管道,就可以轻松地对管道进行维修。如果您有两个管道,那么显然不再适用。想象您正在等待
好。
如果使用pipe-through-
好。
因此,无论哪种方式,您都需要某种异步机制。您可以使用线程,
好。
这是一个快速而肮脏的例子:
好。
1 2 3 4 5 6 7 8 9 10 11 | 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和更高版本的
好。
*文档并没有真正解释什么是管道,几乎就像他们希望您是Unix C的老手一样……但是,其中的一些示例,特别是在用
好。
**困难的部分是正确地对两个或多个管道进行排序。如果您在一根管道上等待,另一根管道可能会溢出并阻塞,从而导致您无法继续等待另一根管道。解决此问题的唯一简单方法是创建一个线程来服务每个管道。 (在大多数* nix平台上,可以改为使用
好。
好。
我必须对@abarnert对于Python 3的答案进行一些更改。这似乎可行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | def tee_pipe(pipe, f1, f2): for line in pipe: f1.write(line) f2.write(line) proc = subprocess.Popen(["/bin/echo","hello"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # Open the output files for stdout/err in unbuffered mode. out_file = open("stderr.log","wb", 0) err_file = open("stdout.log","wb", 0) stdout = sys.stdout stderr = sys.stderr # On Python3 these are wrapped with BufferedTextIO objects that we don't # want. if sys.version_info[0] >= 3: stdout = stdout.buffer stderr = stderr.buffer # Start threads to duplicate the pipes. out_thread = threading.Thread(target=tee_pipe, args=(proc.stdout, out_file, stdout)) err_thread = threading.Thread(target=tee_pipe, args=(proc.stderr, err_file, stderr)) out_thread.start() err_thread.start() # Wait for the command to finish. proc.wait() # Join the pipe threads. out_thread.join() err_thread.join() |
我认为您正在寻找的是这样的:
1 2 3 4 | import sys, subprocess p = subprocess.Popen(cmdline, stdout=sys.stdout, stderr=sys.stderr) |
要将输出/日志写入文件,我将修改
希望能有所帮助。