首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >subprocess.call : stdout to file,stderr to file,实时显示stderr。

subprocess.call : stdout to file,stderr to file,实时显示stderr。
EN

Stack Overflow用户
提问于 2013-08-20 21:03:22
回答 3查看 62.7K关注 0票数 32

我有一个命令行工具(实际上是几个),我正在用Python编写一个包装器。

该工具通常如下所用:

代码语言:javascript
复制
 $ path_to_tool -option1 -option2 > file_out

用户将获得写入file_out的输出,并且能够在工具运行时看到该工具的各种状态消息。

我希望复制此行为,同时将stderr (状态消息)记录到文件中。

我所拥有的是:

代码语言:javascript
复制
from subprocess import call
call(['path_to_tool','-option1','option2'], stdout = file_out, stderr = log_file)

这很好,除非stderr没有写到屏幕上。当然,我可以添加代码将log_file的内容打印到屏幕上,但是用户会在完成所有操作之后而不是在发生时看到它。

概括地说,想要的行为是:

  1. 使用call()或subprocess()
  2. 将标准输出直接指向文件
  3. 将stderr直接写入文件,同时将stderr实时写入屏幕,就像直接从命令行调用工具一样。

我有一种感觉,我要么错过了一些非常简单的东西,要么这比我thought...thanks的任何帮助都要复杂得多!

编辑:这只需要在Linux上工作。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2013-08-20 21:16:22

您可以使用subprocess完成这一任务,但这并不简单。如果您查看文档中的常用参数,您将看到可以将PIPE作为stderr参数传递,该参数创建一个新管道,将管道的一侧传递给子进程,并使另一端可用作stderr属性。

因此,您需要为该管道提供服务,将其写入屏幕和文件。一般来说,正确处理细节是非常棘手的。**在您的例子中,只有一个管道,并且您计划同步维护它,所以没有那么糟糕。

代码语言:javascript
复制
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来通过它。就像这样:

代码语言:javascript
复制
subprocess.call('path_to_tool -option1 option2 2|tee log_file 1>2', shell=True,
                stdout=file_out)

但是我不想调试shell管道,让我们用Python进行调试,如在医生里所示

代码语言:javascript
复制
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-shshellshell_commandshelloutiterpipessargecmd_utilscommandwrapper等子进程和/或shell上有十几个或更高级别的包装器。搜索"shell“、”子进程“、”进程“、”命令行“等,并找到一个您喜欢的包装器,使问题变得简单。

如果你需要同时收集stderr和stdout怎么办?

这样做很容易,就像Sven在一条评论中建议的那样,将其中一个重定向到另一个。只需像这样更改Popen参数:

代码语言:javascript
复制
tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
                        stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

然后,无论您在哪里使用tool.stderr,都可以使用tool.stdout --例如,对于最后一个示例:

代码语言:javascript
复制
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得到一些信息。

如果使用管道贯通tee解决方案,则可以避免初始问题…。但只有通过创建一个同样糟糕的新项目。您有两个tee实例,当您在其中一个上调用communicate时,另一个正在等待很久。

因此,无论哪种方式,您都需要某种异步机制。您可以使用线程、select反应器、类似于gevent之类的方法来完成这一任务。

下面是一个快速而肮脏的例子:

代码语言:javascript
复制
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到达的顺序。我不认为这会影响到我们,因为我们没有发送任何输入…但在这一点上,不要不经过思考和/或测试就相信我。)来自subprocess.communicate的3.3+函数获得了所有微妙的细节。但是,您可能会发现使用PyPI和ActiveState上的异步子流程包装器实现之一要简单得多,甚至可以使用像Twisted这样成熟的异步框架中的子流程实现。

*文档并没有真正解释管道是什么,就好像他们期望您是一个旧的Unix手工…一样但是其中的一些例子,特别是在模块部分,展示了它们是如何使用的,而且非常简单。

**最困难的部分是正确地排列两个或两个以上的管道。如果您在一个管道上等待,另一个管道可能会溢出并阻塞,从而阻止您对另一个管道的等待完成。解决这个问题的唯一简单方法是创建一个线程来服务每个管道。(在大多数*nix平台上,您可以使用selectpoll反应堆,但让跨平台非常困难。)模块的来源,特别是communicate及其助手,演示了如何做到这一点。(我链接到了3.3,因为在早期版本中,communicate本身犯了一些重要的错误,…)这就是为什么,只要有可能,如果需要多个管道,就需要使用communicate。在您的例子中,您不能使用communicate,但幸运的是,您不需要多个管道。

票数 67
EN

Stack Overflow用户

发布于 2016-09-11 12:20:34

我想你要找的是:

代码语言:javascript
复制
import sys, subprocess
p = subprocess.Popen(cmdline,
                     stdout=sys.stdout,
                     stderr=sys.stderr)

要将输出/日志写入文件,我将修改我的cmdline以包括通常的重定向,就像在普通linux /shell上所做的那样。例如,我会将tee附加到命令行:cmdline += ' | tee -a logfile.txt'

希望这能有所帮助。

票数 1
EN

Stack Overflow用户

发布于 2017-11-16 12:13:38

我不得不对@abarnert对Python 3的回答做一些修改。

代码语言:javascript
复制
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
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/18344932

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档