如何使用 python subprocess.call,将标准输出的副本发送到日志文件,同时检测第一个命令的结果

How use python subprocess.call, sending copy of stdout to logfile, while detecting result of first command

我的 python 脚本需要调用一个程序,检测它是否失败(例如,result != 0)并将程序的输出像正常情况一样发送到标准输出和日志文件。

我的默认 shell 是 bash。我正在使用 Python 2.7.9

要将输出发送到标准输出和我通常使用的文件 tee:

result = subprocess.call('some_program --an-option  | tee -a ' + logfile , shell=True)

但是,即使第一个命令失败,bash 中的管道也会 return 为真,因此此方法无法检测命令是否失败。

如果我尝试在命令中使用 set -o pipefail(这样结果将指示第一个命令是否失败),如下所示:

result = subprocess.call('set -o pipefail && some_program --an_option  | tee -a ' + logfile , shell=True)

我收到错误 /bin/sh: 1: set: Illegal option -o pipefail

在 python 中有没有一种方法可以调用命令,将输出发送到普通的标准输出控制台和日志文件,并且仍然检测命令是否失败?

注意:我们必须继续将 some_program 的输出发送到 stdout,因为 stdout 正在发送到 websocket。

我的偏好是将标准输出发送到管道,然后在 Python 代码中读取管道。 Python 代码可以根据需要写入标准输出、文件等。它还可以让您设置 shell=False,因为将其设置为 True 是一个潜在的安全问题,如文档中所述。

However, the pipe in bash will return true even if the first command fails, so this approach fails to detect if the command fails.

事实并非如此。
但我认为你的意思是:即使在任何命令部分失败,'some_program --an-option | tee -a ' + logfile 退出状态代码始终为 0。

嗯,使用多个命令(当使用 &&|| 时)或通过管道将多个命令连接在一起会导致返回时不可靠的退出状态代码。

无论如何,在命令中:如果 some_program 失败,则不会写入 some_program --an-option | tee -a ' + logfile 日志文件。因此您无需担心退出代码。

无论如何,与子进程一起使用管道的最佳方法是创建 Popen 对象并处理 stdoutstdin:

将子进程导入为 sp

STATUS_OK = 0
logfile = '/tmp/test.log'
commands = {
    'main' :   'ls /home',
    'pipe_to': 'tee -a ' + logfile
}

process = sp.Popen(commands['main'], shell=True, stdout=sp.PIPE)
# explicitly force waits till command terminate, set and return exit status code
process.wait()

if process.returncode == STATUS_OK:
    stdoutdata = process.communicate()[0]
    # pipe last command output to "tee" command
    sp.Popen(commands['pipe_to'], stdin=sp.PIPE, shell=1).communicate(stdoutdata)
else:
    # do something when command fails 'ls /hom' (in this case) fails
    pass

就是这样!
在最后一个 Popen 中,我们调用 Popen.communicate()ls 命令的最后输出发送到 tee 命令 STDIN。

在 Python 文档中有一个名为 Replacing shell pipeline 的小教程,也许你想看一看。

I get the error /bin/sh: 1: set: Illegal option -o pipefail

通过 executable='/bin/bash' 否则使用 /bin/sh

您可以在纯 Python:

中实现 tee
#!/usr/bin/env python2
import sys
from subprocess import Popen, PIPE

chunk_size = 1 << 13    
p = Popen(["some_program", "--an-option"], stdout=PIPE, bufsize=1)
with p.stdout, open('logfile', 'ab') as logfile:
    for chunk in iter(lambda: p.stdout.read(chunk_size), b''):
        sys.stdout.write(chunk)
        logfile.write(chunk)
if p.wait() != 0:
    raise Error