如何捕获Python subprocess.check_output()的异常输出?

kra*_*r65 55 python bash subprocess

我正在尝试从Python中进行比特币支付.在bash中我通常会这样做:

bitcoin sendtoaddress <bitcoin address> <amount>
Run Code Online (Sandbox Code Playgroud)

例如:

bitcoin sendtoaddress 1HoCUcbK9RbVnuaGQwiyaJGGAG6xrTPC9y 1.4214
Run Code Online (Sandbox Code Playgroud)

如果它是成功的我得到一个事务ID作为输出,但如果我尝试转移大于我的比特币余额的数量,我得到以下输出:

error: {"code":-4,"message":"Insufficient funds"}
Run Code Online (Sandbox Code Playgroud)

在我的Python程序中,我现在尝试按以下方式进行付款:

import subprocess

try:
    output = subprocess.check_output(['bitcoin', 'sendtoaddress', address, str(amount)])
except:
    print "Unexpected error:", sys.exc_info()
Run Code Online (Sandbox Code Playgroud)

如果有足够的余额它可以正常工作,但如果没有足够的余额sys.exc_info()打印出来:

(<class 'subprocess.CalledProcessError'>, CalledProcessError(), <traceback object at 0x7f339599ac68>)
Run Code Online (Sandbox Code Playgroud)

它不包括我在命令行上得到的错误.所以我的问题是; 如何{"code":-4,"message":"Insufficient funds"}从Python中获取输出的错误()?

欢迎所有提示!

Fer*_*yer 84

根据subprocess.check_output()文档,错误引发的异常有一个output属性,您可以使用该属性来访问错误详细信息:

try:
    subprocess.check_output(...)
except subprocess.CalledProcessError as e:
    print e.output
Run Code Online (Sandbox Code Playgroud)

然后,您应该能够分析此字符串并使用json模块解析错误详细信息:

if e.output.startswith('error: {'):
    error = json.loads(e.output[7:]) # Skip "error: "
    print error['code']
    print error['message']
Run Code Online (Sandbox Code Playgroud)

  • 在 Python 3.5+ 中,您可以使用 `e.stderr` 和 `e.stdout` 而不是 `e.output`。 (7认同)

Phi*_*l R 27

我不认为接受的解决方案处理在stderr上报告错误文本的情况.从我的测试中,异常的输出属性不包含stderr的结果,并且docs警告不要在check_output()中使用stderr = PIPE.相反,我建议通过添加stderr支持对JF Sebastian的解决方案进行一些小的改进.毕竟,我们试图处理错误,stderr经常被报告.

from subprocess import Popen, PIPE

p = Popen(['bitcoin', 'sendtoaddress', ..], stdout=PIPE, stderr=PIPE)
output, error = p.communicate()
if p.returncode != 0: 
   print("bitcoin failed %d %s %s" % (p.returncode, output, error))
Run Code Online (Sandbox Code Playgroud)

  • 这应该在顶部。 (2认同)
  • 在 Python 3.5+ 中,您可以在接受的答案中使用“.stderr”和“.stdout”而不是“.output”。 (2认同)

Juu*_*nen 11

从Python 3.5开始,subprocess.run()支持check参数:

如果check为 true,并且进程以非零退出代码退出,则会引发 CalledProcessError 异常。该异常的属性包含参数、退出代码以及 stdout 和 stderr(如果捕获了它们)。

一个将引发并打印出的简单示例CalledProcessError:

import subprocess
try:
    subprocess.run("exit 1", shell=True, check=True, timeout=15, capture_output=True)
except subprocess.CalledProcessError as e:
    print(e)  # Output: Command 'exit 1' returned non-zero exit status 1.
Run Code Online (Sandbox Code Playgroud)


jfs*_*jfs 8

试图"转移大于比特币余额的金额"并不是意外错误.您可以Popen.communicate()直接使用而不是check_output()避免不必要地引发异常:

from subprocess import Popen, PIPE

p = Popen(['bitcoin', 'sendtoaddress', ..], stdout=PIPE)
output = p.communicate()[0]
if p.returncode != 0: 
   print("bitcoin failed %d %s" % (p.returncode, output))
Run Code Online (Sandbox Code Playgroud)

  • @FerdinandBeyer:在这种情况下,EAFP不适用:你没有拨打任何你不会做的电话.代码没有LBYL结构:`if check():do()`你可以用EAFP替换`try:do()除了错误:handle_error()`.答案中的代码内联`check_output()`函数,并避免在`if p.returncode`分支中引发异常,只是为了在同一级别捕获它.想想,避免使用货物编程 (7认同)
  • 我们也可以这样做:`p = Popen(['bitcoin', 'sendtoaddress', ..], stdout=PIPE, stderr=PIPE)` 并将错误消息捕获为:`output, error = p.communicate()` . (2认同)

Yi *_*ang 7

这对我来说很有效。它捕获子进程的 stdout 和 stderr 输出(对于 python 3.8):

from subprocess import check_output, STDOUT
cmd = "Your Command goes here"
try:
    cmd_stdout = check_output(cmd, stderr=STDOUT, shell=True).decode()
except Exception as e:
    print(e.output.decode()) # print out the stdout messages up to the exception
    print(e) # To print out the exception message
Run Code Online (Sandbox Code Playgroud)


Bla*_*sco 6

正如@Sebastian 所提到的,默认解决方案应该旨在使用run():https : //docs.python.org/3/library/subprocess.html#subprocess.run

这是一个方便的实现(随意使用打印语句或您正在使用的其他日志记录功能更改日志类):

import subprocess

def _run_command(command):
    log.debug("Command: {}".format(command))
    result = subprocess.run(command, shell=True, capture_output=True)
    if result.stderr:
        raise subprocess.CalledProcessError(
                returncode = result.returncode,
                cmd = result.args,
                stderr = result.stderr
                )
    if result.stdout:
        log.debug("Command Result: {}".format(result.stdout.decode('utf-8')))
    return result
Run Code Online (Sandbox Code Playgroud)

和示例用法(代码不相关,但我认为它可以作为示例,说明使用这个简单的实现是多么易读和易于处理错误):

try:
    # Unlock PIN Card
    _run_command(
        "sudo qmicli --device=/dev/cdc-wdm0 -p --uim-verify-pin=PIN1,{}"
        .format(pin)
    )

except subprocess.CalledProcessError as error:
    if "couldn't verify PIN" in error.stderr.decode("utf-8"):
        log.error(
                "SIM card could not be unlocked. "
                "Either the PIN is wrong or the card is not properly connected. "
                "Resetting module..."
                )
        _reset_4g_hat()
        return
Run Code Online (Sandbox Code Playgroud)


mac*_*etw 5

这里有很好的答案,但在这些答案中,没有一个答案与堆栈跟踪输出中的文本一起出现,这是异常的默认行为。

如果您希望使用格式化的回溯信息,您可能希望:

import traceback

try:
    check_call( args )
except CalledProcessError:
    tb = traceback.format_exc()
    tb = tb.replace(passwd, "******")
    print(tb)
    exit(1)
Run Code Online (Sandbox Code Playgroud)

正如您可能知道的那样,如果您希望阻止显示 check_call( args ) 中有密码,则上述内容很有用。