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

时间:2014-07-20 11:24:17

标签: python bash subprocess

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

bitcoin sendtoaddress <bitcoin address> <amount>

所以例如:

bitcoin sendtoaddress 1HoCUcbK9RbVnuaGQwiyaJGGAG6xrTPC9y 1.4214

如果成功,我会得到一个交易ID作为输出,但如果我尝试转移大于比特币余额的金额,我会得到以下输出:

error: {"code":-4,"message":"Insufficient funds"}

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

import subprocess

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

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

(<class 'subprocess.CalledProcessError'>, CalledProcessError(), <traceback object at 0x7f339599ac68>)

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

欢迎所有提示!

6 个答案:

答案 0 :(得分:71)

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

try:
    subprocess.check_output(...)
except subprocess.CalledProcessError as e:
    print e.output

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

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

答案 1 :(得分:22)

我认为接受的解决方案不会处理在stderr上报告错误文本的情况。从我的测试中,异常的输出属性不包含stderr的结果,并且docs警告不要在check_output()中使用stderr = PIPE。相反,我建议通过添加stderr支持对J.F 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))

答案 2 :(得分:7)

尝试“转移大于比特币余额的金额”并非意外错误。您可以直接使用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))

答案 3 :(得分:3)

这帮了我大忙。它捕获子进程的所有stdout输出(对于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

答案 4 :(得分:2)

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

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

import traceback

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

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

答案 5 :(得分:0)

基于@macetw的答案,我将异常直接打印到装饰器中的stderr。

Python 3

from functools import wraps
from sys import stderr
from traceback import format_exc
from typing import Callable, Collection, Any, Mapping


def force_error_output(func: Callable):
    @wraps(func)
    def forced_error_output(*args: Collection[Any], **kwargs: Mapping[str, Any]):
        nonlocal func

        try:
            func(*args, **kwargs)
        except Exception as exception:
            stderr.write(format_exc())
            stderr.write("\n")
            stderr.flush()

            raise exception

    return forced_error_output

Python 2

from functools import wraps
from sys import stderr
from traceback import format_exc


def force_error_output(func):
    @wraps(func)
    def forced_error_output(*args, **kwargs):
        try:
            func(*args, **kwargs)
        except Exception as exception:
            stderr.write(format_exc())
            stderr.write("\n")
            stderr.flush()

            raise exception

    return forced_error_output

然后在您的工作人员中使用装饰器

@force_error_output
def da_worker(arg1: int, arg2: str):
    pass