Um auf die früheren Antworten einzugehen, gibt es eine Reihe von Details, die häufig übersehen werden.
- Bevorzugt
subprocess.run()
über subprocess.check_call()
und Freunde über subprocess.call()
über subprocess.Popen()
über os.system()
über os.popen()
- Verstehen und wahrscheinlich verwenden
text=True
auch bekannt als universal_newlines=True
.
- Verstehen Sie die Bedeutung von
shell=True
o shell=False
und wie sie das Zitieren und die Verfügbarkeit von Shell-Annehmlichkeiten verändert.
- Verstehen Sie die Unterschiede zwischen
sh
und Bash
- Verstehen Sie, dass ein Unterprozess von seinem übergeordneten Prozess getrennt ist und den übergeordneten Prozess im Allgemeinen nicht ändern kann.
- Vermeiden Sie es, den Python-Interpreter als einen Unterprozess von Python auszuführen.
Diese Themen werden im Folgenden etwas ausführlicher behandelt.
Bevorzugt subprocess.run()
o subprocess.check_call()
En subprocess.Popen()
Funktion ist ein Arbeitstier auf niedriger Ebene, aber es ist schwierig, sie richtig zu benutzen, und man muss am Ende mehrere Codezeilen kopieren/einfügen ... die praktischerweise bereits in der Standardbibliothek als eine Reihe von Wrapper-Funktionen auf höherer Ebene für verschiedene Zwecke vorhanden sind, die im Folgenden genauer vorgestellt werden.
Hier ist ein Absatz aus der Dokumentation :
Der empfohlene Ansatz zum Aufrufen von Unterprozessen ist die Verwendung der run()
Funktion für alle Anwendungsfälle, die sie bearbeiten kann. Für fortgeschrittene Anwendungsfälle wird die zugrunde liegende Popen
Schnittstelle kann direkt verwendet werden.
Leider ist die Verfügbarkeit dieser Wrapper-Funktionen von Python-Version zu Python-Version unterschiedlich.
subprocess.run()
wurde offiziell in Python 3.5 eingeführt. Es soll alle der folgenden ersetzen.
subprocess.check_output()
wurde in Python 2.7 / 3.1 eingeführt. Es ist im Grunde äquivalent zu subprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
subprocess.check_call()
wurde in Python 2.5 eingeführt. Es ist im Grunde äquivalent zu subprocess.run(..., check=True)
subprocess.call()
wurde in Python 2.4 im Original eingeführt subprocess
Modul ( PEP-324 ). Dies entspricht im Wesentlichen subprocess.run(...).returncode
High-Level-API vs. subprocess.Popen()
Die umgestaltete und erweiterte subprocess.run()
ist logischer und vielseitiger als die älteren Legacy-Funktionen, die sie ersetzt. Sie gibt eine CompletedProcess
Objekt, das über verschiedene Methoden verfügt, mit denen Sie den Exit-Status, die Standardausgabe und einige andere Ergebnisse und Statusindikatoren des beendeten Unterprozesses abrufen können.
subprocess.run()
ist der richtige Weg, wenn Sie einfach ein Programm ausführen und die Kontrolle an Python zurückgeben wollen. Für komplexere Szenarien (Hintergrundprozesse, vielleicht mit interaktiver E/A mit dem übergeordneten Python-Programm) müssen Sie immer noch subprocess.Popen()
und kümmern Sie sich selbst um alle Klempnerarbeiten. Dies erfordert ein ziemlich kompliziertes Verständnis aller beweglichen Teile und sollte nicht auf die leichte Schulter genommen werden. Die einfachere Popen
Objekt steht für den (möglicherweise noch laufenden) Prozess, der für den Rest der Lebensdauer des Unterprozesses von Ihrem Code verwaltet werden muss.
Es sollte vielleicht hervorgehoben werden, dass nur subprocess.Popen()
schafft lediglich einen Prozess. Wenn Sie es dabei belassen, haben Sie einen Unterprozess, der gleichzeitig mit Python läuft, also einen "Hintergrundprozess". Wenn er keine Eingaben oder Ausgaben tätigen oder sich anderweitig mit Ihnen abstimmen muss, kann er parallel zu Ihrem Python-Programm nützliche Arbeit leisten.
Vermeiden Sie os.system()
y os.popen()
Seit ewigen Zeiten (naja, seit Python 2.5) ist die os
Modul-Dokumentation hat die Empfehlung enthalten, die subprocess
über os.system()
:
En subprocess
Modul bietet leistungsfähigere Möglichkeiten zum Starten neuer Prozesse und zum Abrufen ihrer Ergebnisse; die Verwendung dieses Moduls ist der Verwendung dieser Funktion vorzuziehen.
Die Probleme mit system()
sind, dass er offensichtlich systemabhängig ist und keine Möglichkeiten zur Interaktion mit dem Unterprozess bietet. Er wird einfach ausgeführt, wobei Standardausgabe und Standardfehler außerhalb der Reichweite von Python liegen. Die einzige Information, die Python zurückerhält, ist der Exit-Status des Befehls (Null bedeutet Erfolg, obwohl die Bedeutung von Werten ungleich Null auch etwas systemabhängig ist).
PEP-324 (der bereits oben erwähnt wurde) enthält eine ausführlichere Begründung, warum os.system
problematisch ist und wie subprocess
versucht, diese Probleme zu lösen.
os.popen()
war früher noch mehr dringend abgeraten :
Veraltet seit Version 2.6: Diese Funktion ist obsolet. Verwenden Sie die subprocess
Modul.
Irgendwann in Python 3 wurde sie jedoch neu implementiert und verwendet nun einfach subprocess
und leitet weiter zur subprocess.Popen()
Dokumentation für Details.
Verstehen und in der Regel verwenden check=True
Sie werden auch feststellen, dass subprocess.call()
hat viele der gleichen Einschränkungen wie os.system()
. Bei regelmäßiger Verwendung sollten Sie in der Regel prüfen, ob der Prozess erfolgreich abgeschlossen wurde, was subprocess.check_call()
y subprocess.check_output()
do (wobei letzterer auch die Standardausgabe des beendeten Unterprozesses zurückgibt). In ähnlicher Weise sollten Sie normalerweise check=True
avec subprocess.run()
es sei denn, Sie müssen ausdrücklich zulassen, dass der Unterprozess einen Fehlerstatus zurückgibt.
In der Praxis, mit check=True
o subprocess.check_*
wirft Python ein CalledProcessError
Ausnahme wenn der Unterprozess einen Exit-Status ungleich Null zurückgibt.
Ein häufiger Fehler bei subprocess.run()
ist zu unterlassen check=True
und sich wundern, wenn nachgelagerter Code fehlschlägt, wenn der Unterprozess fehlgeschlagen ist.
Andererseits ist ein häufiges Problem bei check_call()
y check_output()
war, dass Benutzer, die diese Funktionen blind verwendeten, überrascht waren, wenn die Ausnahme ausgelöst wurde, z. B. wenn grep
hat keine Übereinstimmung gefunden. (Sie sollten wahrscheinlich ersetzen grep
mit nativem Python-Code sowieso, wie unten beschrieben).
Alles in allem müssen Sie verstehen, wie Shell-Befehle einen Exit-Code zurückgeben und unter welchen Bedingungen sie einen Exit-Code ungleich Null (Fehler) zurückgeben, und eine bewusste Entscheidung treffen, wie genau dies gehandhabt werden soll.
Verstehen und wahrscheinlich benutzen text=True
alias universal_newlines=True
Seit Python 3 sind Python-interne Zeichenketten Unicode-Zeichenketten. Es gibt jedoch keine Garantie, dass ein Unterprozess Unicode-Ausgaben oder überhaupt Zeichenketten erzeugt.
(Falls die Unterschiede nicht sofort ersichtlich sind: Ned Batchelder's Pragmatischer Unicode ist eine empfohlene, wenn nicht gar obligatorische Lektüre. Es gibt eine 36-minütige Videopräsentation hinter dem Link, wenn Sie dies bevorzugen, obwohl das Lesen der Seite selbst wahrscheinlich deutlich weniger Zeit in Anspruch nimmt).
Tief im Inneren muss Python eine bytes
Puffer und interpretieren ihn irgendwie. Enthält er einen Blob mit Binärdaten, wird er sollte nicht in eine Unicode-Zeichenkette dekodiert werden, weil das ein fehleranfälliges und fehlerverursachendes Verhalten ist - genau die Art von lästigem Verhalten, das viele Python-2-Skripte befallen hat, bevor es eine Möglichkeit gab, zwischen kodiertem Text und binären Daten zu unterscheiden.
Mit text=True
teilen Sie Python mit, dass Sie tatsächlich Textdaten in der Standardkodierung des Systems zurückerwarten und dass diese nach bestem Wissen und Gewissen in eine Python-Zeichenkette (Unicode) dekodiert werden sollen (normalerweise UTF-8 auf jedem halbwegs aktuellen System, außer vielleicht Windows?)
Wenn das so ist no was Sie zurückfordern, gibt Python Ihnen nur bytes
Zeichenketten im stdout
y stderr
Strings. Vielleicht werden Sie zu einem späteren Zeitpunkt do Sie wissen, dass es sich um Textstrings handelt, und Sie kennen ihre Kodierung. Dann können Sie sie dekodieren.
normal = subprocess.run([external, arg],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True,
text=True)
print(normal.stdout)
convoluted = subprocess.run([external, arg],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True)
# You have to know (or guess) the encoding
print(convoluted.stdout.decode('utf-8'))
Mit Python 3.7 wurde der kürzere, anschaulichere und verständlichere Alias text
für das Schlüsselwortargument, das zuvor etwas irreführend als universal_newlines
.
Verstehen Sie shell=True
vs shell=False
Mit shell=True
übergeben Sie eine einzelne Zeichenkette an Ihre Shell, und die Shell übernimmt sie.
Mit shell=False
übergeben Sie eine Liste von Argumenten an das Betriebssystem, wobei Sie die Shell umgehen.
Wenn man keine Shell hat, speichert man einen Prozess und entledigt sich einer eine ziemlich große Menge an versteckter Komplexität, die Bugs oder sogar Sicherheitsprobleme enthalten kann oder auch nicht.
Auf der anderen Seite, wenn Sie keine Shell haben, haben Sie keine Umleitung, Wildcard-Expansion, Job-Kontrolle und eine große Anzahl von anderen Shell-Funktionen.
Ein häufiger Fehler ist die Verwendung von shell=True
und dann noch Python eine Liste von Token übergeben, oder andersherum. Das funktioniert zwar in einigen Fällen, ist aber sehr undefiniert und könnte auf interessante Weise scheitern.
# XXX AVOID THIS BUG
buggy = subprocess.run('dig +short stackoverflow.com')
# XXX AVOID THIS BUG TOO
broken = subprocess.run(['dig', '+short', 'stackoverflow.com'],
shell=True)
# XXX DEFINITELY AVOID THIS
pathological = subprocess.run(['dig +short stackoverflow.com'],
shell=True)
correct = subprocess.run(['dig', '+short', 'stackoverflow.com'],
# Probably don't forget these, too
check=True, text=True)
# XXX Probably better avoid shell=True
# but this is nominally correct
fixed_but_fugly = subprocess.run('dig +short stackoverflow.com',
shell=True,
# Probably don't forget these, too
check=True, text=True)
Die übliche Erwiderung "aber bei mir funktioniert es" ist kein nützliches Gegenargument, wenn man nicht genau weiß, unter welchen Umständen es nicht mehr funktionieren könnte.
Um es kurz zusammenzufassen, die korrekte Verwendung sieht wie folgt aus
subprocess.run("string for 'the shell' to parse", shell=True)
# or
subprocess.run(["list", "of", "tokenized strings"]) # shell=False
Wenn Sie die Shell vermeiden wollen, aber zu faul sind oder nicht wissen, wie Sie eine Zeichenkette in eine Liste von Token zerlegen können, beachten Sie, dass shlex.split()
kann dies für Sie tun.
subprocess.run(shlex.split("no string for 'the shell' to parse")) # shell=False
# equivalent to
# subprocess.run(["no", "string", "for", "the shell", "to", "parse"])
Die reguläre split()
funktioniert hier nicht, da es die Anführungszeichen nicht beibehält. Beachten Sie im obigen Beispiel, wie "the shell"
ist eine einzelne Zeichenkette.
Refactoring-Beispiel
Sehr oft können die Funktionen der Shell durch nativen Python-Code ersetzt werden. Einfaches Awk oder sed
Skripte sollten stattdessen wahrscheinlich einfach in Python übersetzt werden.
Um dies teilweise zu veranschaulichen, hier ein typisches, aber etwas albernes Beispiel, das viele Shell-Funktionen beinhaltet.
cmd = '''while read -r x;
do ping -c 3 "$x" | grep 'min/avg/max'
done <hosts.txt'''
# Trivial but horrible
results = subprocess.run(
cmd, shell=True, universal_newlines=True, check=True)
print(results.stdout)
# Reimplement with shell=False
with open('hosts.txt') as hosts:
for host in hosts:
host = host.rstrip('\n') # drop newline
ping = subprocess.run(
['ping', '-c', '3', host],
text=True,
stdout=subprocess.PIPE,
check=True)
for line in ping.stdout.split('\n'):
if 'min/avg/max' in line:
print('{}: {}'.format(host, line))
Hier sind einige Dinge zu beachten:
- Mit
shell=False
Sie brauchen die Anführungszeichen nicht, die die Shell für Zeichenketten verlangt. Wenn Sie trotzdem Anführungszeichen setzen, ist das wahrscheinlich ein Fehler.
- Oft ist es sinnvoll, so wenig Code wie möglich in einem Unterprozess auszuführen. So haben Sie mehr Kontrolle über die Ausführung Ihres Python-Codes.
- Allerdings sind komplexe Shell-Pipelines mühsam und manchmal schwierig in Python zu reimplementieren.
Der überarbeitete Code veranschaulicht auch, wie viel die Shell mit einer sehr knappen Syntax wirklich für Sie tut - im Guten wie im Schlechten. Python sagt explizit ist besser als implizit aber der Python-Code es ziemlich langatmig und sieht wohl komplexer aus, als es tatsächlich ist. Andererseits bietet es eine Reihe von Punkten, an denen man mitten in etwas anderem die Kontrolle übernehmen kann, wie zum Beispiel die Verbesserung, dass wir den Hostnamen einfach zusammen mit der Ausgabe des Shell-Befehls einfügen können. (Das ist auch in der Shell keineswegs schwierig, aber auf Kosten einer weiteren Ablenkung und vielleicht eines weiteren Prozesses.)
Gemeinsame Shell-Konstrukte
Der Vollständigkeit halber finden Sie hier kurze Erklärungen zu einigen dieser Shell-Funktionen und einige Hinweise darauf, wie sie möglicherweise durch native Python-Funktionen ersetzt werden können.
- Globbing, auch bekannt als Wildcard-Expansion, kann ersetzt werden durch
glob.glob()
oder sehr oft mit einfachen Python-String-Vergleichen wie for file in os.listdir('.'): if not file.endswith('.png'): continue
. Bash hat verschiedene andere Erweiterungsmöglichkeiten wie .{png,jpg}
Klammererweiterung und {1..100}
sowie die Tilde-Erweiterung ( ~
auf Ihr Home-Verzeichnis erweitert, und allgemeiner ~account
in das Heimatverzeichnis eines anderen Benutzers)
- Shell-Variablen wie
$SHELL
o $my_exported_var
können manchmal einfach durch Python-Variablen ersetzt werden. Exportierte Shell-Variablen sind z.B. verfügbar als os.environ['SHELL']
(die Bedeutung von export
ist es, die Variable für Unterprozesse verfügbar zu machen - eine Variable, die für Unterprozesse nicht verfügbar ist, ist natürlich auch nicht für Python verfügbar, das als Unterprozess der Shell läuft, oder umgekehrt. Die env=
Schlüsselwortargument für subprocess
Methoden können Sie die Umgebung des Unterprozesses als Wörterbuch definieren. Das ist also eine Möglichkeit, eine Python-Variable für einen Unterprozess sichtbar zu machen). Mit shell=False
müssen Sie wissen, wie Sie Anführungszeichen entfernen können; zum Beispiel, cd "$HOME"
ist gleichbedeutend mit os.chdir(os.environ['HOME'])
ohne Anführungszeichen um den Verzeichnisnamen. (Sehr oft cd
ist ohnehin nicht sinnvoll oder notwendig, und viele Anfänger lassen die doppelten Anführungszeichen um die Variable weg und kommen damit durch bis eines Tages ... )
- Die Umleitung ermöglicht es Ihnen, von einer Datei als Standardeingabe zu lesen und Ihre Standardausgabe in eine Datei zu schreiben.
grep 'foo' <inputfile >outputfile
öffnet outputfile
zum Schreiben und inputfile
zum Lesen und übergibt seinen Inhalt als Standardeingabe an grep
, dessen Standardausgabe dann in outputfile
. Dies ist im Allgemeinen nicht schwer durch nativen Python-Code zu ersetzen.
- Pipelines sind eine Form der Umlenkung.
echo foo | nl
führt zwei Unterprozesse aus, wobei die Standardausgabe von echo
ist die Standardeingabe von nl
(auf Betriebssystemebene, in Unix-ähnlichen Systemen, ist dies ein einzelnes Dateihandle). Wenn Sie ein oder beide Enden der Pipeline nicht durch nativen Python-Code ersetzen können, sollten Sie vielleicht doch über die Verwendung einer Shell nachdenken, vor allem, wenn die Pipeline mehr als zwei oder drei Prozesse hat (sehen Sie sich jedoch die pipes
Modul in der Python-Standardbibliothek oder eine Reihe von moderneren und vielseitigeren Wettbewerbern).
- Mit der Auftragssteuerung können Sie Aufträge unterbrechen, im Hintergrund laufen lassen, wieder in den Vordergrund bringen usw. Die grundlegenden Unix-Signale zum Anhalten und Fortsetzen eines Prozesses sind natürlich auch in Python verfügbar. Aber Jobs sind eine übergeordnete Abstraktion in der Shell, die Prozessgruppen usw. beinhaltet, die man verstehen muss, wenn man so etwas mit Python machen will.
- Das Zitieren in der Shell ist potenziell verwirrend, bis Sie verstehen, dass alles ist im Grunde eine Zeichenkette. Also
ls -l /
ist gleichbedeutend mit 'ls' '-l' '/'
aber die Anführungszeichen um Literale herum sind völlig optional. Nicht in Anführungszeichen gesetzte Zeichenketten, die Shell-Metazeichen enthalten, werden einer Parametererweiterung, einer Leerraum-Tokenisierung und einer Platzhaltererweiterung unterzogen; doppelte Anführungszeichen verhindern die Leerraum-Tokenisierung und die Platzhaltererweiterung, erlauben aber Parametererweiterungen (Variablensubstitution, Befehlssubstitution und Backslash-Verarbeitung). Dies ist in der Theorie einfach, kann aber verwirrend sein, vor allem wenn es mehrere Interpretationsschichten gibt (z. B. bei einem entfernten Shell-Befehl).
Verstehen Sie die Unterschiede zwischen sh
und Bash
subprocess
führt Ihre Shell-Befehle mit /bin/sh
wenn Sie nicht ausdrücklich etwas anderes verlangen (außer natürlich unter Windows, wo der Wert des Parameters COMSPEC
variabel). Dies bedeutet, dass [verschiedene reine Bash-Funktionen wie Arrays, [[
usw.](https://stackoverflow.com/a/42666651/874188) sind nicht verfügbar.
Wenn Sie eine reine Bash-Syntax verwenden müssen, können Sie den Pfad zur Shell als executable='/bin/bash'
(wenn Ihre Bash woanders installiert ist, müssen Sie den Pfad natürlich anpassen).
subprocess.run('''
# This for loop syntax is Bash only
for((i=1;i<=$#;i++)); do
# Arrays are Bash-only
array[i]+=123
done''',
shell=True, check=True,
executable='/bin/bash')
A subprocess
ist von seinem Elternteil getrennt und kann ihn nicht ändern
Ein weit verbreiteter Fehler ist es, etwas zu tun wie
subprocess.run('cd /tmp', shell=True)
subprocess.run('pwd', shell=True) # Oops, doesn't print /tmp
Dasselbe passiert, wenn der erste Unterprozess versucht, eine Umgebungsvariable zu setzen, die natürlich verschwunden ist, wenn Sie einen anderen Unterprozess starten, usw.
Ein Kindprozess läuft völlig getrennt von Python, und wenn er beendet wird, hat Python keine Ahnung, was er getan hat (abgesehen von den vagen Indikatoren, die es aus dem Exit-Status und der Ausgabe des Kindprozesses ableiten kann). Ein Kindprozess kann im Allgemeinen die Umgebung des Elternprozesses nicht verändern; er kann keine Variablen setzen, das Arbeitsverzeichnis wechseln oder, mit anderen Worten, mit seinem Elternprozess kommunizieren, ohne dass dieser mit ihm zusammenarbeitet.
Die unmittelbare Lösung in diesem speziellen Fall besteht darin, beide Befehle in einem einzigen Unterprozess auszuführen;
subprocess.run('cd /tmp; pwd', shell=True)
obwohl dieser spezielle Anwendungsfall offensichtlich nicht sehr nützlich ist; verwenden Sie stattdessen die cwd
Schlüsselwortargument, oder einfach os.chdir()
bevor Sie den Unterprozess starten. In ähnlicher Weise können Sie beim Setzen einer Variablen die Umgebung des aktuellen Prozesses (und damit auch seiner Kinder) über
os.environ['foo'] = 'bar'
oder übergeben Sie eine Umgebungseinstellung an einen Kindprozess mit
subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})
(ganz zu schweigen von der offensichtlichen Umstrukturierung subprocess.run(['echo', 'bar'])
aber echo
ist natürlich ein schlechtes Beispiel für etwas, das in einem Unterprozess laufen sollte).
Python nicht von Python aus ausführen
Dies ist ein etwas zweifelhafter Ratschlag; es gibt sicherlich Situationen, in denen es sinnvoll oder sogar zwingend erforderlich ist, den Python-Interpreter als Unterprozess eines Python-Skripts auszuführen. Aber sehr häufig ist der richtige Ansatz einfach der, dass import
das andere Python-Modul in Ihr aufrufendes Skript ein und rufen dessen Funktionen direkt auf.
Wenn das andere Python-Skript unter Ihrer Kontrolle ist und es sich nicht um ein Modul handelt, sollten Sie es zu einem machen . (Diese Antwort ist bereits zu lang, so dass ich hier nicht ins Detail gehen werde).
Wenn Sie Parallelität benötigen, können Sie Python-Funktionen in Unterprozessen mit der multiprocessing
Modul. Außerdem gibt es threading
der mehrere Aufgaben in einem einzigen Prozess ausführt (was leichter ist und Ihnen mehr Kontrolle gibt, aber auch mehr Einschränkungen mit sich bringt, da die Threads innerhalb eines Prozesses eng gekoppelt und an einen einzigen GIL .)