Wie fange ich die Ausnahmeausgabe von Python subprocess.check_output () ab?

72

Ich versuche, eine Bitcoin-Zahlung innerhalb von Python durchzuführen. In Bash würde ich normalerweise folgendes tun:

bitcoin sendtoaddress <bitcoin address> <amount>

so zum Beispiel:

bitcoin sendtoaddress 1HoCUcbK9RbVnuaGQwiyaJGGAG6xrTPC9y 1.4214

Wenn dies erfolgreich ist, erhalte ich eine Transaktions-ID als Ausgabe. Wenn ich jedoch versuche, einen Betrag zu überweisen, der größer als mein Bitcoin-Guthaben ist, erhalte ich die folgende Ausgabe:

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

In meinem Python-Programm versuche ich nun, die Zahlung wie folgt durchzuführen:

import subprocess

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

Wenn genügend Gleichgewicht vorhanden ist, funktioniert es einwandfrei, aber wenn nicht genügend Gleichgewicht vorhanden ist, wird sys.exc_info()Folgendes ausgedruckt:

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

Der Fehler, den ich in der Befehlszeile erhalte, ist jedoch nicht enthalten. Meine Frage ist also: Wie kann ich den ausgegebenen Fehler ( {"code":-4,"message":"Insufficient funds"}) aus Python heraus abrufen?

Alle Tipps sind willkommen!

kramer65
quelle

Antworten:

105

Gemäß den subprocess.check_output()Dokumenten verfügt die bei einem Fehler ausgelöste Ausnahme über ein outputAttribut, mit dem Sie auf die Fehlerdetails zugreifen können:

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

Sie sollten dann in der Lage sein, diese Zeichenfolge zu analysieren und die Fehlerdetails mit dem jsonModul zu analysieren :

if e.output.startswith('error: {'):
    error = json.loads(e.output[7:]) # Skip "error: "
    print(error['code'])
    print(error['message'])
Ferdinand Beyer
quelle
Ich rufe ein Programm auf, das etwas an stdout ausgibt und dann 1 zurückgibt, aber check_output erfasst es nicht
JorgeeFG
@JorgeeFG Dann denke ich, dass etwas mit Ihrem Programm nicht stimmt. Bitte beachten Sie, dass der Kommentarbereich nicht der richtige Ort ist, um neue Fragen zu stellen. Wenn Sie Hilfe bei Ihrem speziellen Problem benötigen, klicken Sie oben rechts auf der Seite auf die große Schaltfläche "Frage stellen".
Ferdinand Beyer
36

Ich glaube nicht, dass die akzeptierte Lösung den Fall behandelt, in dem der Fehlertext auf stderr gemeldet wird. Bei meinen Tests enthielt das Ausgabeattribut der Ausnahme nicht die Ergebnisse von stderr, und die Dokumente warnen vor der Verwendung von stderr = PIPE in check_output (). Stattdessen würde ich eine kleine Verbesserung der Lösung von JF Sebastian vorschlagen, indem ich stderr-Unterstützung hinzufüge. Wir versuchen schließlich, mit Fehlern umzugehen, und bei stderr werden sie häufig gemeldet.

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))
Phil R.
quelle
Ich stimme zu, dass die stderrAusgabe hier sehr relevant ist. Eine alternative Lösung wäre, run()stattdessen die Funktion zu verwenden (siehe Ersetzen in den Dokumenten check_output ). Denn dann können Sie e.stderraus der Ausnahme in Ihrer Fehlerberichterstattung verwenden.
Sebastian
Dies sollte oben sein.
Oleg
Um die scheinbare Reihenfolge der Ausgabe beizubehalten, können Sie stderr=STDOUTbeide Streams zusammenführen.
JFS
Ich denke, es ist gut darauf hinzuweisen, dass, wenn Sie nicht anrufen, .communicatedie .returncodeAusgabe leer sein wird ( None)
lupodellasleppa
if p.returncode != 0:hat hier nicht funktioniert, weil ich NoneErfolg habe. Musste verwendenif p.returncode:
Giulio
10

Der Versuch, "einen Betrag zu überweisen, der größer als mein Bitcoin-Guthaben ist", ist kein unerwarteter Fehler. Sie können Popen.communicate()direkt verwenden, anstatt check_output()zu vermeiden, dass eine Ausnahme unnötig ausgelöst wird:

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))
jfs
quelle
Python empfiehlt einen EAFP-Programmierstil (einfacher um Vergebung als um Erlaubnis zu bitten) und bevorzugt die Ausnahmebehandlung gegenüber "Wenn" -Prüfungen in solchen Situationen.
Ferdinand Beyer
8
@FerdinandBeyer: EAFP gilt in diesem Fall nicht: Sie tätigen keine Anrufe, die Sie sonst nicht tätigen würden. Der Code hat nicht die LBYL-Struktur if check(): do(), die Sie durch EAFP hätten ersetzen können try: do() except Error: handle_error(). Der Code in der Antwort inlines check_output()funktioniert und vermeidet das Auslösen einer Ausnahme in der if p.returncodeVerzweigung, nur um sie auf derselben Ebene abzufangen. Vermeiden Sie Frachtkult-Programmierung, denken Sie
jfs
2
Wir können auch: p = Popen(['bitcoin', 'sendtoaddress', ..], stdout=PIPE, stderr=PIPE)und die Fehlermeldung wie folgt erfassen : output, error = p.communicate().
Alper
Wie kann ich das auch für die Befehle tun, die Pipe verwenden? @jfs
alper
@alper übergeben Sie den Befehl als Zeichenfolge und fügen Sie ein shell=TrueArgument hinzu:p = Popen("a | b", shell=True, ..)
jfs
3

Hier gibt es gute Antworten, aber in diesen Antworten gibt es keine Antwort, die den Text aus der Stack-Trace-Ausgabe enthält. Dies ist das Standardverhalten einer Ausnahme.

Wenn Sie diese formatierten Traceback-Informationen verwenden möchten, möchten Sie möglicherweise:

import traceback

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

Wie Sie möglicherweise feststellen können, ist das oben Genannte hilfreich, wenn Sie in check_call (args) ein Kennwort haben, dessen Anzeige Sie verhindern möchten.

macetw
quelle
3

Das hat den Trick für mich getan. Es erfasst alle stdout-Ausgaben des Unterprozesses (für 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
Yi Zong Kuang
quelle
2

Wie von @Sebastian erwähnt, sollte die Standardlösung folgende Ziele verfolgen run(): https://docs.python.org/3/library/subprocess.html#subprocess.run

Hier eine praktische Implementierung (Sie können die Protokollklasse jederzeit mit print-Anweisungen oder anderen von Ihnen verwendeten Protokollierungsfunktionen ändern):

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

Und Beispielverwendung (Code hat nichts damit zu tun, aber ich denke, er dient als Beispiel dafür, wie lesbar und einfach es ist, mit Fehlern bei dieser einfachen Implementierung zu arbeiten):

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
Blasco
quelle
0

Basierend auf der Antwort von @macetw drucke ich die Ausnahme direkt in einem Dekorateur an 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

Dann benutzen Sie in Ihrem Arbeiter einfach den Dekorateur

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