How to catch exception output from Python subprocess.check_output()?

后端 未结 7 1131
灰色年华
灰色年华 2020-11-29 00:35

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

bitcoin sendtoaddress  


        
相关标签:
7条回答
  • 2020-11-29 01:01

    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))
    
    0 讨论(0)
  • 2020-11-29 01:01

    As mentioned by @Sebastian the default solution should aim to use run(): https://docs.python.org/3/library/subprocess.html#subprocess.run

    Here a convenient implementation (feel free to change the log class with print statements or what ever other logging functionality you are using):

    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
    

    And sample usage (code is unrelated, but I think it serves as example of how readable and easy to work with errors it is with this simple implementation):

    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
    
    0 讨论(0)
  • 2020-11-29 01:02

    Based on the answer of @macetw I print the exception directly to stderr in a decorator.

    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
    

    Then in your worker just use the decorator

    @force_error_output
    def da_worker(arg1: int, arg2: str):
        pass
    
    0 讨论(0)
  • 2020-11-29 01:07

    This did the trick for me. It captures all the stdout output from the subprocess(For 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
    
    0 讨论(0)
  • 2020-11-29 01:13

    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))
    
    0 讨论(0)
  • 2020-11-29 01:14

    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.

    0 讨论(0)
提交回复
热议问题