python - Python:subprocess.call,stdout到文件,stderr到文件,在屏幕上实时显示stderr

标签 python subprocess stderr

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

该工具通常是这样使用的:

 $ path_to_tool -option1 -option2 > file_out

用户将输出写入file_out,并且还可以在工具运行时查看其各种状态消息。

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

我所拥有的是:
from subprocess import call
call(['path_to_tool','-option1','option2'], stdout = file_out, stderr = log_file)

除未将stderr写入屏幕外,此方法都可以正常工作。
我当然可以添加代码以将log_file的内容打印到屏幕上,但是然后用户会在完成所有操作后而不是在执行操作时看到它。

概括地说,所需的行为是:
  • 使用call()或subprocess()
  • 将stdout定向到文件
  • 将stderr定向到文件,同时还将stderr实时写入屏幕,就像
    该工具已直接从命令行调用。

  • 我觉得我要么错过了一些非常简单的事情,要么比我想象的要复杂得多……感谢您的帮助!

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

    最佳答案

    您可以使用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:会出现一些问题-基本上,如果您正在阅读的内容由于某种原因而没有被行缓冲,即使实际上有一半的数据需要处理,您也可以坐在那里等待换行您可以使用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)
    

    但是我不想调试 shell 管道。让我们在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上的子进程和/或shell周围有十几个或更高级的包装器-shshellshell_commandshelloutiterpipessargecmd_utilscommandwrapper等。搜索“shell”,“subprocess”, “进程”,“命令行”等,然后找到您喜欢的解决方案,从而使问题变得微不足道。

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

    正如Sven Marnach在评论中建议的那样,简单的方法是将一个重定向到另一个。只需像这样更改Popen参数:
    tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    

    然后在所有使用tool.stderr的地方,改用tool.stdout,例如,对于最后一个示例:
    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 react 堆,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平台上,您可以改用selectpoll react 器,但是要使该跨平台变得异常困难。)模块的The source,尤其是communicate及其助手,显示了如何执行此操作。 (我链接到3.3,因为在较早的版本中,communicate本身会犯一些重要的错误……)因此,如果需要多个管道,则尽可能使用communicate。就您而言,您不能使用communicate,但是幸运的是,您不需要多个管道。

    关于python - Python:subprocess.call,stdout到文件,stderr到文件,在屏幕上实时显示stderr,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18344932/

    相关文章:

    bash - stdout 到 file1,stderr 到 file2,都正确交错到 stdout 和文件

    powershell - 如何在不污染 PowerShell 错误输出的情况下重定向 stdout 和 stderr

    python - 使用python执行shell邮件命令

    python - 使用 subprocess.Popen 顺序执行 python 脚本

    python - 如何在 pytest hooks 中捕获打印语句

    python - 使用 selenium 执行事件和抓取

    python - subprocess.popen pid 更改

    perl - 为什么本地在 STDERR 和 STDOUT 上不起作用?

    python - Pygame 支持哪些格式来播放声音?

    python - Spark 中多列的窗口聚合