How to catch exception output from Python subproce

2019-01-17 06:03发布

问题:

I'm trying to do a Bitcoin payment from within Python. In bash I would normally do this:

bitcoin sendtoaddress <bitcoin address> <amount>

so for example:

bitcoin sendtoaddress 1HoCUcbK9RbVnuaGQwiyaJGGAG6xrTPC9y 1.4214

if it is successfull I get a transaction id as output but if I try to transfer an amount larger than my bitcoin balance, I get the following output:

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

In my Python program I now try to do the payment as follows:

import subprocess

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

If there's enough balance it works fine, but if there's not enough balance sys.exc_info() prints out this:

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

It doesn't include the error which I get on the command line though. So my question is; how can I get the outputted error ({"code":-4,"message":"Insufficient funds"}) from within Python?

All tips are welcome!

回答1:

According to the subprocess.check_output() docs, the exception raised on error has an output attribute that you can use to access the error details:

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

You should then be able to analyse this string and parse the error details with the json module:

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


回答2:

I don't think the accepted solution handles the case where the error text is reported on stderr. From my testing the exception's output attribute did not contain the results from stderr and the docs warn against using stderr=PIPE in check_output(). Instead, I would suggest one small improvement to J.F Sebastian's solution by adding stderr support. We are, after all, trying to handle errors and stderr is where they are often reported.

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))


回答3:

Trying to "transfer an amount larger than my bitcoin balance" is not an unexpected error. You could use Popen.communicate() directly instead of check_output() to avoid raising an exception unnecessarily:

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))


回答4:

There are good answers here, but in these answers, there has not been an answer that comes up with the text from the stack-trace output, which is the default behavior of an exception.

If you wish to use that formatted traceback information, you might wish to:

import traceback

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

As you might be able to tell, the above is useful in case you have a password in the check_call( args ) that you wish to prevent from displaying.