Holen Sie sich den Exit-Code und stderr aus dem Unterprozessaufruf

71

Ich habe mich über die Funktionen des Unterprozesses - call, check_call, check_output - informiert und verstehe, wie die einzelnen Funktionen funktionieren und sich in ihrer Funktionalität voneinander unterscheiden. Ich verwende derzeit check_output, damit ich auf das stdout zugreifen kann, und habe "try block" verwendet, um die Ausnahme wie folgt abzufangen:

# "cmnd" is a string that contains the command along with it's arguments. 
try:
    cmnd_output = check_output(cmnd, stderr=STDOUT, shell=True, timeout=3, universal_newlines=True);                         
except CalledProcessError:                                                                                                   
    print("Status : FAIL")                                                                                                   
print("Output: \n{}\n".format(cmnd_output))                                                                                  

Das Problem, auf das ich stoße, ist, wenn eine Ausnahme ausgelöst wird, "cmnd_output" nicht initialisiert wird und keinen Zugriff auf stderr hat und die folgende Fehlermeldung angezeigt wird:

print("Output: \n{}\n".format(cmnd_output))
UnboundLocalError: local variable 'cmnd_output' referenced before assignment

Ich denke, das liegt daran, dass die Ausnahme dazu führt, dass der "check_output" sofort ohne weitere Verarbeitung, auch bekannt als Zuweisung zu "cmnd_output", im try-Block abgebrochen wird. Bitte korrigieren Sie mich, wenn ich falsch liege.

Gibt es eine Möglichkeit, auf stderr zuzugreifen (es ist in Ordnung, wenn es an stout gesendet wird) und Zugriff auf den Exit-Code zu haben? Ich kann anhand des Exit-Codes manuell prüfen, ob bestanden / nicht bestanden wurde, ohne dass eine Ausnahme vorliegt.

Danke, Ahmed.

Ahmed A.
quelle

Antworten:

105

Versuchen Sie diese Version:

import subprocess
try:
    output = subprocess.check_output(
        cmnd, stderr=subprocess.STDOUT, shell=True, timeout=3,
        universal_newlines=True)
except subprocess.CalledProcessError as exc:
    print("Status : FAIL", exc.returncode, exc.output)
else:
    print("Output: \n{}\n".format(output))

Auf diese Weise drucken Sie die Ausgabe nur, wenn der Anruf erfolgreich war. Im Falle eines CalledProcessErrordrucken Sie den Rückkehrcode und die Ausgabe.

warvariuc
quelle
2
Ich erhalte diesen Fehler:ret = subprocess.check_output(cmd , stderr=STDOUT,shell=True) NameError: global name 'STDOUT' is not defined
ARH
1
Perfekt! Vergessen Sie nicht stderr=STDOUT, wenn Sie diesen Ansatz versuchen
VicX
timeoutwird verwendet, weil der Code von OP es verwendet hat. Offensichtlich ist alles außer cmndoptional.
Warvariuc
Ich denke, dieser Ansatz wird die Ausgabe zu stdout und stderr mischen.
AuBee
65

Die akzeptierte Lösung deckt den Fall ab, in dem Sie in Ordnung mischen stdoutund stderrin Fällen, in denen der untergeordnete Prozess (aus welchem ​​Grund auch immer) beschließt, stderrIN ADDITION stdoutfür eine nicht fehlgeschlagene Ausgabe zu verwenden (dh eine unkritische Warnung auszugeben) Die gegebene Lösung ist möglicherweise nicht wünschenswert.

Wenn Sie beispielsweise eine zusätzliche Verarbeitung für die Ausgabe durchführen, z. B. eine Konvertierung in JSON, und die Daten einmischen stderr, schlägt der Gesamtprozess fehl, da die Ausgabe aufgrund der hinzugefügten stderrAusgabe kein reines JSON ist .

Ich habe festgestellt, dass in diesem Fall Folgendes funktioniert:

cmd_args = ... what you want to execute ...

pipes = subprocess.Popen(cmd_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
#If you are using python 2.x, you need to include shell=True in the above line
std_out, std_err = pipes.communicate()

if pipes.returncode != 0:
    # an error happened!
    err_msg = "%s. Code: %s" % (std_err.strip(), pipes.returncode)
    raise Exception(err_msg)

elif len(std_err):
    # return code is 0 (no error), but we may want to
    # do something with the info on std_err
    # i.e. logger.warning(std_err)

# do whatever you want with std_out
# i.e. json.loads(std_out)
oarevalo
quelle
Sehr hilfreich. Dies ist auch mein Anwendungsfall. stdout wird von meinem Ziel eher als Protokollausgabe als als Fehlerausgabe verwendet. Können Sie übrigens die Notwendigkeit der Bereitstellung kommentieren stderr=subprocess.PIPE? Ich habe andere Posts gesehen, die es ohne die zusätzliche Pipe gemacht haben, und ich bin mir nicht sicher, warum.
KobeJohn
1
Zumindest für Python 2.7 stürzt dies ab, es sei denn, Sie fügen dem Popen shell = True hinzu
a_river_in_canada
1
Danke, das ist sehr nützlich. Ich brauchte auch ein .decode("utf-8")After the Err's, da sie aus irgendeinem Grund in Bytes zurückgegeben werden.
Kardamom
Dies sollte als akzeptierte richtige Antwort gewesen sein, danke
grepit
10

Beide vorgeschlagenen Lösungen mischen entweder stdout / stderr oder verwenden Popeneine nicht ganz so einfache Verwendung wie check_output. Sie können jedoch dasselbe erreichen und stdout / stderr getrennt halten, während Sie verwenden, check_outputwenn Sie stderr einfach mit einer Pipe erfassen :

import sys
import subprocess

try:
    subprocess.check_output(cmnd, stderr=subprocess.PIPE)
except subprocess.CalledProcessError as e:
    print('exit code: {}'.format(e.returncode))
    print('stdout: {}'.format(e.output.decode(sys.getfilesystemencoding())))
    print('stderr: {}'.format(e.stderr.decode(sys.getfilesystemencoding())))

In diesem Beispiel ist es, da wir stderr erfasst haben, im stderrAttribut der Ausnahme verfügbar (ohne Erfassung mit der Pipe wäre dies nur der Fall None).

Kyle
quelle
5
Diese Lösung erfordert Python> = 3.5
Uranix
4
Ich bin kein Python-Entwickler, also verzeihen Sie die mögliche Unwissenheit, aber die Python-Dokumente klingen so, als würden sie vorschlagen , dies nicht zu tun :Note: Do not use stderr=PIPE with this function as that can deadlock based on the child process error volume. Use Popen with the communicate() method when you need a stderr pipe.
Mike
1
@Mike Popen mit communic () explodiert auch, wenn Sie zu viel Ausgabe haben, da beide Streams nicht iterativ verarbeitet werden können. Wenn Sie jemals etwas Ähnliches wie den teeBefehl tun wollten, ist dies in Python im Großen und Ganzen unmöglich, da Sie nicht viel mit Dateideskriptoren tanzen und die gesamte Arbeit über Systemaufrufe erledigen müssen. Die subprocessAPI ist vom Design her fehlerhaft.
wvxvw
Insbesondere fehlt in früheren Versionen Folgendes CalledProcessError.stderr. Frühere Versionen nur haben returncode, cmdund output.
Michael Dorst
0

Ich hatte eine ähnliche Anforderung und Folgendes funktionierte für mich:

    try:
        with open ("vtcstderr.out", "w") as file:
            rawOutput = subprocess.check_output(
                command,
                stderr=file,
                shell=True
            )
    except subprocess.CalledProcessError as error:
        # this is the stdout
        rawOutput = error.output

    with open ("vtcstderr.out", "r") as file:
        # this is the stderr
        errorLines = file.readlines()

John Paliotta
quelle
2
Während dieser Code die Frage lösen kann, einschließlich einer Erklärung, wie und warum dies das Problem löst, würde dies wirklich dazu beitragen, die Qualität Ihres Beitrags zu verbessern, und wahrscheinlich zu mehr Up-Votes führen. Denken Sie daran, dass Sie in Zukunft die Frage für die Leser beantworten, nicht nur für die Person, die jetzt fragt. Bitte bearbeiten Sie Ihre Antwort, um Erklärungen hinzuzufügen und anzugeben, welche Einschränkungen und Annahmen gelten.
19огдан Опир
-2

Warum nicht die Variable cmnd_output vor der try-Anweisung initialisieren? Auf diese Weise funktioniert es so, wie Sie es erwarten. Die folgende Zeile würde funktionieren, fügen Sie sie einfach über der try-Anweisung hinzu:

cmnd_output = ''
n3rV3
quelle
Ich brauche cmnd_output, um durch den Inhalt von stdout (und auch stderr) initialisiert zu werden. Dies würde in den meisten Fällen passieren, außer wenn der Exit-Code des Programms ungleich Null ist.
Ahmed A
Genau, falls es eine Ausnahme gibt, würde Ihr Code die Ausnahme drucken und Sie werden nichts in der Variablen haben. Wenn es jedoch normal ausgeführt wird (keine Ausnahmen), erhalten Sie die Werte von cmnd_output.
n3rV3