如何从Python子进程捕获异常输出。检查_output()?

如何从Python子进程捕获异常输出。检查_output()?,python,bash,subprocess,Python,Bash,Subprocess,我正在尝试从Python内部进行比特币支付。在bash中,我通常会这样做: bitcoin sendtoaddress <bitcoin address> <amount> 如果成功,我将获得一个交易id作为输出,但如果我尝试转移的金额大于我的比特币余额,我将获得以下输出: error: {"code":-4,"message":"Insufficient funds"} 在我的Python程序中,我现在尝试按如下方式进行付款: import subprocess

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

bitcoin sendtoaddress <bitcoin address> <amount>
如果成功,我将获得一个交易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”:“资金不足”}

欢迎所有提示

根据,在出错时引发的异常具有一个属性,您可以使用该属性访问错误详细信息:

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

我认为公认的解决方案无法处理在stderr上报告错误文本的情况。根据我的测试,异常的output属性不包含来自stderr的结果,文档警告不要在check_output()中使用stderr=PIPE。相反,我建议对J.F Sebastian的解决方案进行一个小小的改进,添加stderr支持。毕竟,我们正在尝试处理错误,而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))

这里有很好的答案,但在这些答案中,没有从堆栈跟踪输出的文本中找到答案,这是异常的默认行为

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

import traceback

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

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

根据@macetw的答案,我将异常直接打印到decorator中的stderr

Python 3

从functools导入包装
从sys导入stderr
从回溯导入格式\u exc
通过键入import Callable、Collection、Any、Mapping
def force_错误_输出(func:可调用):
@包装(func)
def强制_错误_输出(*args:Collection[Any],**kwargs:Mapping[str,Any]):
非局部函数
尝试:
func(*args,**kwargs)
例外情况除外:
stderr.write(格式为exc())
标准写入(“\n”)
stderr.flush()
引发异常
返回强制\u错误\u输出
Python 2

从functools导入包装
从sys导入stderr
从回溯导入格式\u exc
def强制错误输出(func):
@包装(func)
def强制_错误_输出(*args,**kwargs):
尝试:
func(*args,**kwargs)
例外情况除外:
stderr.write(格式为exc())
标准写入(“\n”)
stderr.flush()
引发异常
返回强制\u错误\u输出
然后在你的工人只需使用装饰

@force\u错误\u输出
def da_worker(arg1:int,arg2:str):
通过

正如@Sebastian所提到的,默认解决方案应旨在使用
run()

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

和示例用法(代码与此无关,但我认为它可以作为一个示例,说明这个简单实现的可读性和处理错误的容易程度):


这对我起了作用。它捕获子流程的所有标准输出(对于python 3.8):


Python鼓励EAFP编程风格(请求原谅比请求许可更容易),在这种情况下更喜欢异常处理而不是“如果”检查。@FerdinandBeyer:EAFP不适用于这种情况:您不会进行任何其他情况下不会进行的调用。代码没有LBYL结构:
if check():do()
,您可以将其替换为EAFP
try:do(),除了Error:handle\u Error()
。答案中的代码内联
check_output()
函数,避免在
if p.returncode
分支中引发异常,只在同一级别捕获它。避免使用cargo cult编程,thinkWe也可以这样做:
p=Popen(['bitcoin','sendtoaddress',…],stdout=PIPE,stderr=PIPE)
,并将错误消息捕获为:
output,error=p.communicate()
。对于使用PIPE的命令,我该怎么做呢@jfs@alper将命令作为字符串传递并添加
shell=True
参数:
p=Popen(“a | b”,shell=True,…)
我正在调用一个程序,该程序向stdout输出一些内容,然后返回1,但check\u输出未捕获it@JorgeeFG那我猜你的程序有问题。请注意,评论部分不是提出新问题的合适位置。如果您需要有关特定问题的帮助,请单击页面右上角的“提问”按钮。在Python 3.5+中,您可以使用
e.stderr
e.stdout
而不是
e.output
。我同意
stderr
输出在这里非常相关。另一种解决方案是使用
run()
函数(请参阅如何替换)。因为您可以在错误报告中从异常中使用
e.stderr
。这应该在顶部。为了保持输出的明显顺序,您可以使用
stderr=STDOUT
(合并两个流)。我想最好指出,如果您不调用
.communicate
,则
.returncode
输出将为空(
None
如果p.returncode!=0:import traceback

try:
    check_call( args )
except CalledProcessError:
    tb = traceback.format_exc()
    tb = tb.replace(passwd, "******")
    print(tb)
    exit(1)
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
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
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