Python 子进程 check_call,失败时打印 STDERR 的原生方式?

Python subprocess check_call, native way to print STDERR if it fails?

我经常希望执行 subprocess.check_output 的操作,即实时打印 STDOUT,并在退出时检查 return 代码是否为 0。但是,如果出现错误,我希望打印 STDERR。查看日志文件时,只看到 return 代码通常不是很有用。

例如:

subprocess.check_call(['ping', '-c', '1', 'github.com2'])

会产生这个错误:

subprocess.CalledProcessError: Command '['ping', '-c', '1', 'github.com2']' returned non-zero exit status 2.

这里我们想知道为什么它失败了,在现实世界的例子中,能够看到实际错误是什么变得至关重要。所以我发现我总是必须将这样的调用包装成这样才能看到 STDERR:

import subprocess
cmds = ['ping', '-c', '1', 'github.com2']
result = subprocess.run(cmds, stderr=subprocess.PIPE)
if result.returncode != 0:
    msg = result.stderr.decode().strip()
    raise subprocess.CalledProcessError(f"CALLED SUBPROCESS ERROR: Command: {' '.join(cmds)}\nReturn code {result.returncode}\nSTDERR: {msg}\n")

通过以上可以看出STDERR是:

ping: github.com2: Temporary failure in name resolution

我觉得一定有一个使用子进程的内置方式,但我想不通。

我猜这是不可能的,因为 CalledSubprocessError 不使用 STDERR 是异常的字符串版本:

class CalledProcessError(SubprocessError):
    """Raised when run() is called with check=True and the process
    returns a non-zero exit status.

    Attributes:
      cmd, returncode, stdout, stderr, output
    """
    def __init__(self, returncode, cmd, output=None, stderr=None):
        self.returncode = returncode
        self.cmd = cmd
        self.output = output
        self.stderr = stderr

    def __str__(self):
        if self.returncode and self.returncode < 0:
            try:
                return "Command '%s' died with %r." % (
                        self.cmd, signal.Signals(-self.returncode))
            except ValueError:
                return "Command '%s' died with unknown signal %d." % (
                        self.cmd, -self.returncode)
        else:
            return "Command '%s' returned non-zero exit status %d." % (
                    self.cmd, self.returncode)

    @property
    def stdout(self):
        """Alias for output attribute, to match stderr"""
        return self.output

    @stdout.setter
    def stdout(self, value):
        # There's no obvious reason to set this, but allow it anyway so
        # .stdout is a transparent alias for .output
        self.output = value

调用 subprocess.run(..., check=True) 应该会提供所有需要的信息。来自 manual:“如果检查为真,并且进程以 non-zero 退出代码退出,将引发 CalledProcessError 异常。该异常的属性保持参数、退出代码以及标准输出和标准错误 如果它们被捕获。