【问题标题】:subprocess.Popen handling stdout and stderr as they comesubprocess.Popen 处理标准输出和标准错误
【发布时间】:2024-01-22 21:02:02
【问题描述】:

我正在尝试处理来自subprocess.Popen 调用的stdoutstderr,该调用通过subprocess.PIPE 捕获两者,但想处理输出(例如在终端上打印它们)。

我见过的所有当前解决方案都将等待Popen 调用完成,以确保捕获所有stdoutstderr,以便对其进行处理。

这是一个带有混合输出的示例 Python 脚本,我在实时(或尽可能实时地)处理它时似乎无法复制订单:

$ cat mix_out.py

import sys

sys.stdout.write('this is an stdout line\n')
sys.stdout.write('this is an stdout line\n')
sys.stderr.write('this is an stderr line\n')
sys.stderr.write('this is an stderr line\n')
sys.stderr.write('this is an stderr line\n')
sys.stdout.write('this is an stdout line\n')
sys.stderr.write('this is an stderr line\n')
sys.stdout.write('this is an stdout line\n')

似乎可行的一种方法是使用线程,因为这样读取将是异步的,并且可以在 subprocess 产生输出时进行处理。

当前的实现只是首先处理stdout,最后处理stderr,如果输出最初在两者之间交替,则可能具有欺骗性:

cmd = ['python', 'mix_out.py']

process = subprocess.Popen(
    cmd,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    close_fds=True,
    **kw
)

if process.stdout:
    while True:
        out = process.stdout.readline()
        if out == '' and process.poll() is not None:
            break
        if out != '':
            print 'stdout: %s' % out
            sys.stdout.flush()

if process.stderr:
    while True:
        err = process.stderr.readline()
        if err == '' and process.poll() is not None:
            break
        if err != '':
            print 'stderr: %s' % err
            sys.stderr.flush()

如果我运行上述(另存为out.py)来处理上面的mix_out.py 示例脚本,则流(如预期)按顺序处理:

$ python out.py
stdout: this is an stdout line
stdout: this is an stdout line
stdout: this is an stdout line
stdout: this is an stdout line
stderr: this is an stderr line
stderr: this is an stderr line
stderr: this is an stderr line
stderr: this is an stderr line

我知道某些系统调用可能会缓冲,我对此表示同意,我希望解决的一件事是尊重流发生时的顺序。

有没有一种方法可以同时处理来自subprocessstdoutstderr 而无需 使用线程? (代码在无法使用线程的受限远程系统中执行)。

区分 stdout 和 stderr 是必须的(如示例输出所示)

理想情况下,最好不要使用额外的库(例如,我知道 pexpect 解决了这个问题)

很多例子都提到了select 的使用,但我没有想出一些可以保持输出顺序的东西。

【问题讨论】:

  • 我真的想不出任何不使用线程的解决方案。您有什么特别的原因要避免使用它们吗?
  • 如果您只想打印到终端,如果您取出PIPEs,这是默认设置。它也不能保证完全正确的顺序,但你能把stdoutstderr 读入同一个while True 循环吗?
  • 订单需要保留。
  • @dano 我不能使用线程,因为这段代码会在无法生成线程的远程(受限)系统中执行

标签: python subprocess


【解决方案1】:

对不起,如果我误解了这个问题...但是如果您正在寻找一种 subprocess.Popen 实时输出到 stdout/stderr 的方式,您应该能够通过以下方式实现:

import sys, subprocess
p = subprocess.Popen(cmdline,
                     stdout=sys.stdout,
                     stderr=sys.stderr)

stderr=subprocess.STDOUT 可能会简化您的过滤?

如果这不是您想要/想要的,对不起。但希望它能满足其他人的需求。

【讨论】:

    【解决方案2】:

    我在这里找到了example 工作(参见capture_together.py 的列表)。编译的 C++ 代码混合了 cerrcout 在 Windows 和 UNIX 操作系统上作为子进程执行。结果相同

    【讨论】:

      【解决方案3】:

      我可以通过使用select.select() 解决这个问题

      process = subprocess.Popen(
          cmd,
          stdout=subprocess.PIPE,
          stderr=subprocess.PIPE,
          close_fds=True,
          **kw
      )
      
      while True:
          reads, _, _ = select(
              [process.stdout.fileno(), process.stderr.fileno()],
              [], []
          )
      
          for descriptor in reads:
              if descriptor == process.stdout.fileno():
                  read = process.stdout.readline()
                  if read:
                      print 'stdout: %s' % read
      
              if descriptor == process.stderr.fileno():
                  read = process.stderr.readline()
                  if read:
                      print 'stderr: %s' % read
              sys.stdout.flush()
      
          if process.poll() is not None:
              break
      

      通过在reads 参数(select() 的第一个参数)上将文件描述符传递给select() 并循环它们(只要process.poll() 表明该进程仍然存在)。

      不需要线程。代码改编自* answer

      【讨论】:

      • 是的,我已经测试过了:它不会保留订单。要禁用缓冲,请使用-u 标志(如果孩子是python)或use stdbuf utility (or its analogs) or pseudo-tty (pty, pexpect modules)。一些程序提供了一个特殊的标志,例如,grep's --line-buffered
      • 您无法控制缓冲的其他工具/外壳的缓冲。此答案中的代码确实有效并保留了顺序。
      • 你可以控制缓冲——我之前的评论提到了几种方法。使用您的问题中的mix_out.py 尝试您的代码。您的代码不会保留顺序,除非您将-u 标志传递给python 可执行文件使用stdbuf 运行它或使用伪tty 运行它。