Um die früheren Antworten hier etwas zu erweitern, gibt es eine Reihe von Details, die häufig übersehen werden.
- Lieber
subprocess.run()
über subprocess.check_call()
und Freunde subprocess.call()
über subprocess.Popen()
über os.system()
überos.popen()
- Verstehe und benutze es wahrscheinlich
text=True
auch universal_newlines=True
.
- Verstehe die Bedeutung von
shell=True
odershell=False
und wie sich das Angebot ändert, und die Verfügbarkeit von Shell-Annehmlichkeiten.
- Unterschiede zwischen verstehen
sh
und Bash
- Verstehen Sie, wie ein Unterprozess von seinem übergeordneten Prozess getrennt ist und den übergeordneten Prozess im Allgemeinen nicht ändern kann.
- Vermeiden Sie es, den Python-Interpreter als Unterprozess von Python auszuführen.
Diese Themen werden im Folgenden ausführlicher behandelt.
Bevorzugen subprocess.run()
odersubprocess.check_call()
Das subprocess.Popen()
Funktion ist ein Arbeitspferd auf niedriger Ebene, aber es ist schwierig, sie richtig zu verwenden, und Sie kopieren / fügen am Ende mehrere Codezeilen ein ... die bequemerweise bereits in der Standardbibliothek als Satz von Wrapper-Funktionen auf höherer Ebene für verschiedene Zwecke vorhanden sind. die im Folgenden näher erläutert werden.
Hier ist ein Absatz aus der Dokumentation :
Der empfohlene Ansatz zum Aufrufen von Unterprozessen besteht darin, die run()
Funktion für alle Anwendungsfälle zu verwenden, die sie verarbeiten kann. Für fortgeschrittenere Anwendungsfälle kann die zugrunde liegende Popen
Schnittstelle direkt verwendet werden.
Leider unterscheidet sich die Verfügbarkeit dieser Wrapper-Funktionen zwischen den Python-Versionen.
subprocess.run()
wurde offiziell in Python 3.5 eingeführt. Es soll alle folgenden ersetzen.
subprocess.check_output()
wurde in Python 2.7 / 3.1 eingeführt. Es ist im Grunde gleichbedeutend mitsubprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
subprocess.check_call()
wurde in Python 2.5 eingeführt. Es ist im Grunde gleichbedeutend mitsubprocess.run(..., check=True)
subprocess.call()
wurde in Python 2.4 im Originalmodul subprocess
( PEP-324 ) eingeführt. Es ist im Grunde gleichbedeutend mitsubprocess.run(...).returncode
High-Level-API vs. subprocess.Popen()
Das überarbeitete und erweiterte System subprocess.run()
ist logischer und vielseitiger als die älteren Legacy-Funktionen, die es ersetzt. Es gibt ein CompletedProcess
Objekt mit verschiedenen Methoden zurück, mit denen Sie den Exit-Status, die Standardausgabe und einige andere Ergebnisse und Statusindikatoren aus dem fertigen Unterprozess abrufen können.
subprocess.run()
Dies ist der richtige Weg, wenn Sie lediglich ein Programm benötigen, um die Steuerung auszuführen und an Python zurückzugeben. Für komplexere Szenarien (Hintergrundprozesse, möglicherweise mit interaktiver E / A mit dem übergeordneten Python-Programm) müssen Sie weiterhin subprocess.Popen()
alle Installationen selbst verwenden und pflegen. Dies erfordert ein ziemlich kompliziertes Verständnis aller beweglichen Teile und sollte nicht leichtfertig durchgeführt werden. Das einfachere Popen
Objekt stellt den (möglicherweise noch laufenden) Prozess dar, der für den Rest der Lebensdauer des Unterprozesses aus Ihrem Code verwaltet werden muss.
Es sollte vielleicht betont werden, dass nur subprocess.Popen()
ein Prozess entsteht. Wenn Sie es dabei belassen, wird neben Python gleichzeitig ein Unterprozess ausgeführt, also ein "Hintergrund" -Prozess. Wenn es keine Eingabe oder Ausgabe oder anderweitige Koordinierung mit Ihnen vornehmen muss, kann es nützliche Arbeit parallel zu Ihrem Python-Programm leisten.
Vermeiden Sie os.system()
undos.popen()
Seit ewiger Zeit (na ja, da Python 2.5) die os
Moduldokumentation hat die Empfehlung enthalten ist, bevorzugen subprocess
über os.system()
:
Das subprocess
Modul bietet leistungsfähigere Funktionen zum Laichen neuer Prozesse und zum Abrufen ihrer Ergebnisse. Die Verwendung dieses Moduls ist der Verwendung dieser Funktion vorzuziehen.
Die Probleme dabei system()
sind, dass es offensichtlich systemabhängig ist und keine Möglichkeiten zur Interaktion mit dem Unterprozess bietet. Es wird einfach ausgeführt, wobei die Standardausgabe und der 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 Nicht-Null-Werten auch etwas systemabhängig ist).
PEP-324 (das bereits oben erwähnt wurde) enthält eine detailliertere Begründung dafür, warum dies os.system
problematisch ist und wie subprocess
versucht wird, diese Probleme zu lösen.
os.popen()
war früher noch stärker entmutigt :
Veraltet seit Version 2.6: Diese Funktion ist veraltet. Verwenden Sie das subprocess
Modul.
Seit einiger Zeit in Python 3 wurde es jedoch neu implementiert, um es einfach zu verwenden subprocess
, und leitet zur subprocess.Popen()
Dokumentation weiter, um weitere Informationen zu erhalten.
Verstehe und benutze es normalerweise check=True
Sie werden auch feststellen, dass subprocess.call()
es viele der gleichen Einschränkungen gibt wie os.system()
. Bei regelmäßiger Verwendung sollten Sie im Allgemeinen prüfen, ob der Prozess erfolgreich abgeschlossen wurde, was subprocess.check_call()
und subprocess.check_output()
was (wobei letzterer auch die Standardausgabe des fertigen Unterprozesses zurückgibt). In ähnlicher Weise sollten Sie normalerweise check=True
mit verwenden, es subprocess.run()
sei denn, Sie müssen dem Unterprozess ausdrücklich erlauben, einen Fehlerstatus zurückzugeben.
In der Praxis löst Python mit check=True
oder subprocess.check_*
eine CalledProcessError
Ausnahme aus, wenn der Unterprozess einen Exit-Status ungleich Null zurückgibt.
Ein häufiger Fehler subprocess.run()
besteht darin, wegzulassen check=True
und überrascht zu sein, wenn Downstream-Code fehlschlägt, wenn der Unterprozess fehlschlägt.
Andererseits bestand ein häufiges Problem mit check_call()
und darin, check_output()
dass Benutzer, die diese Funktionen blind verwendeten, überrascht waren, als die Ausnahme ausgelöst wurde, z. B. wenn grep
keine Übereinstimmung gefunden wurde. (Sie sollten wahrscheinlich grep
trotzdem durch nativen Python-Code ersetzen , 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 damit umgegangen werden soll.
Verstehe und benutze wahrscheinlich text=True
akauniversal_newlines=True
Seit Python 3 sind in Python interne Zeichenfolgen Unicode-Zeichenfolgen. Es gibt jedoch keine Garantie dafür, dass ein Unterprozess eine Unicode-Ausgabe oder überhaupt Zeichenfolgen generiert.
(Wenn die Unterschiede nicht sofort offensichtlich sind, wird das Lesen von Pragmatic Unicode von Ned Batchelder empfohlen, wenn nicht sogar obligatorisch. Wenn Sie es vorziehen, befindet sich hinter dem Link eine 36-minütige Videopräsentation, obwohl das Lesen der Seite selbst wahrscheinlich erheblich weniger Zeit in Anspruch nimmt. )
Tief im Inneren muss Python einen bytes
Puffer holen und ihn irgendwie interpretieren. Wenn es einen Blob von Binärdaten enthält, sollte es nicht in eine Unicode-Zeichenfolge dekodiert werden, da dies ein fehleranfälliges und fehlerauslösendes Verhalten ist - genau die Art von lästigem Verhalten, das viele Python 2-Skripte durcheinander brachte, bevor es einen Weg dazu gab richtig zwischen codiertem Text und Binärdaten unterscheiden.
Mit text=True
teilen Sie Python mit, dass Sie tatsächlich Textdaten in der Standardcodierung des Systems zurückerwarten und dass diese nach besten Python-Fähigkeiten in eine Python-Zeichenfolge (Unicode) dekodiert werden sollten (normalerweise UTF-8 bei mäßig bis zu Datumssystem, außer vielleicht Windows?)
Wenn Sie dies nicht zurückfordern, gibt Python Ihnen nur bytes
Zeichenfolgen in den Zeichenfolgen stdout
und stderr
. An einigen Vielleicht zeigen Sie später noch wissen , dass sie Text - Strings , nachdem alle waren, und Sie wissen , ihre Codierung. 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'))
Python 3.7 führte den kürzeren, aussagekräftigeren und verständlicheren Alias text
für das Keyword-Argument ein, das zuvor etwas irreführend genannt wurde universal_newlines
.
Verstehe shell=True
vs.shell=False
Übergeben shell=True
Sie eine einzelne Zeichenfolge an Ihre Shell, und die Shell übernimmt sie von dort.
Wenn shell=False
Sie eine Liste von Argumenten an das Betriebssystem übergeben, umgehen Sie die Shell.
Wenn Sie keine Shell haben, speichern Sie einen Prozess und beseitigen eine ziemlich große Menge an versteckter Komplexität, die Fehler oder sogar Sicherheitsprobleme enthalten kann oder nicht.
Wenn Sie jedoch keine Shell haben, verfügen Sie nicht über Umleitung, Platzhaltererweiterung, Jobsteuerung und eine große Anzahl anderer Shell-Funktionen.
Ein häufiger Fehler besteht darin shell=True
, Python eine Liste von Token zu verwenden und diese dann weiterzuleiten oder umgekehrt. Dies funktioniert in einigen Fällen, ist jedoch sehr schlecht definiert und kann auf interessante Weise brechen.
# 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 es funktioniert für mich" ist keine nützliche Gegenargumentation, es sei denn, Sie verstehen genau, unter welchen Umständen sie nicht mehr funktionieren könnte.
Refactoring-Beispiel
Sehr oft können die Funktionen der Shell durch nativen Python-Code ersetzt werden. Simple Awk oder sed
Skripte sollten wahrscheinlich stattdessen einfach in Python übersetzt werden.
Um dies teilweise zu veranschaulichen, ist hier ein typisches, aber etwas albernes Beispiel, das viele Shell-Merkmale beinhaltet.
cmd = '''while read -r x;
do ping -c 3 "$x" | grep 'round-trip 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 'round-trip min/avg/max' in line:
print('{}: {}'.format(host, line))
Einige Dinge, die hier zu beachten sind:
- Mit
shell=False
brauchen Sie nicht das Zitat, das die Shell um Strings benötigt. Das Setzen von Anführungszeichen ist wahrscheinlich ein Fehler.
- Oft ist es sinnvoll, in einem Unterprozess so wenig Code wie möglich auszuführen. Dies gibt Ihnen mehr Kontrolle über die Ausführung innerhalb Ihres Python-Codes.
- Allerdings sind komplexe Shell-Pipelines langwierig und manchmal schwierig in Python neu zu implementieren.
Der überarbeitete Code zeigt auch, wie viel die Shell mit einer sehr knappen Syntax wirklich für Sie tut - zum Guten oder zum Schlechten. Python sagt, explizit ist besser als implizit, aber der Python-Code ist ziemlich ausführlich und sieht wohl komplexer aus, als dies wirklich ist. Auf der anderen Seite bietet es eine Reihe von Punkten, an denen Sie mitten in etwas anderem die Kontrolle übernehmen können. Dies wird trivial durch die Verbesserung veranschaulicht, dass wir den Hostnamen einfach zusammen mit der Ausgabe des Shell-Befehls einfügen können. (Dies ist auch in der Shell keineswegs schwierig, sondern auf Kosten einer weiteren Umleitung und möglicherweise eines weiteren Prozesses.)
Gemeinsame Shell-Konstrukte
Der Vollständigkeit halber finden Sie hier kurze Erläuterungen zu einigen dieser Shell-Funktionen sowie einige Hinweise, wie sie möglicherweise durch native Python-Funktionen ersetzt werden können.
- Globbing aka Wildcard-Erweiterung kann durch
glob.glob()
oder sehr oft durch einfache Python-String-Vergleiche wie ersetzt werden for file in os.listdir('.'): if not file.endswith('.png'): continue
. Bash verfügt über verschiedene andere Erweiterungsfunktionen wie die .{png,jpg}
Erweiterung von Klammern und die Erweiterung {1..100}
von Tilde (wird ~
in Ihr Home-Verzeichnis und allgemein ~account
in das Home-Verzeichnis eines anderen Benutzers erweitert).
- Shell-Variablen wie
$SHELL
oder $my_exported_var
können manchmal einfach durch Python-Variablen ersetzt werden. Exportierte Shell-Variablen sind verfügbar als z. B. os.environ['SHELL']
(die Bedeutung von export
besteht darin, die Variable für Unterprozesse verfügbar zu machen - eine Variable, die für Unterprozesse nicht verfügbar ist, steht Python offensichtlich nicht zur Verfügung, die als Unterprozess der Shell ausgeführt wird, oder umgekehrt. Das env=
Schlüsselwort Mit dem Argument für subprocess
Methoden können Sie die Umgebung des Unterprozesses als Wörterbuch definieren. Auf diese Weise können Sie eine Python-Variable für einen Unterprozess sichtbar machen. Mit müssen shell=False
Sie verstehen, wie Sie Anführungszeichen entfernen; ist zum Beispiel cd "$HOME"
gleichbedeutend mitos.chdir(os.environ['HOME'])
ohne Anführungszeichen um die Verzeichnisnamen. (Sehr oftcd
ist sowieso nicht nützlich oder notwendig, und viele Anfänger lassen die doppelten Anführungszeichen um die Variable weg und kommen damit bis eines Tages davon ... )
- Mit der Umleitung können Sie als Standardeingabe aus einer Datei lesen und Ihre Standardeingabe in eine Datei schreiben.
grep 'foo' <inputfile >outputfile
öffnet sich outputfile
zum Schreiben und inputfile
Lesen und übergibt seinen Inhalt als Standardeingabe an grep
, dessen Standardausgabe dann landet outputfile
. Dies ist im Allgemeinen nicht schwer durch nativen Python-Code zu ersetzen.
- Pipelines sind eine Form der Umleitung.
echo foo | nl
führt zwei Unterprozesse aus, wobei die Standardausgabe von echo
die Standardeingabe von ist nl
(auf Betriebssystemebene ist dies in Unix-ähnlichen Systemen ein einzelnes Dateihandle). Wenn Sie ein oder beide Enden der Pipeline nicht durch nativen Python-Code ersetzen können, sollten Sie vielleicht doch eine Shell verwenden, insbesondere wenn die Pipeline mehr als zwei oder drei Prozesse enthält (sehen Sie sich jedoch das pipes
Modul in der Python-Standardbibliothek oder eine Nummer an von moderneren und vielseitigeren Wettbewerbern Dritter).
- Mit der Jobsteuerung können Sie Jobs unterbrechen, im Hintergrund ausführen, in den Vordergrund zurücksetzen usw. Die grundlegenden Unix-Signale zum Stoppen und Fortsetzen eines Prozesses sind natürlich auch in Python verfügbar. Jobs sind jedoch eine übergeordnete Abstraktion in der Shell, die Prozessgruppen usw. umfasst, die Sie verstehen müssen, wenn Sie so etwas in Python ausführen möchten.
- Das Zitieren in der Shell ist möglicherweise verwirrend, bis Sie verstehen, dass alles im Grunde eine Zeichenfolge ist. Ist
ls -l /
also gleichbedeutend mit, 'ls' '-l' '/'
aber das Zitieren um Literale ist völlig optional. Nicht zitierte Zeichenfolgen, die Shell-Metazeichen enthalten, werden einer Parametererweiterung, einer Leerzeichen-Tokenisierung und einer Platzhaltererweiterung unterzogen. Doppelte Anführungszeichen verhindern die Leerzeichen-Tokenisierung und die Platzhaltererweiterung, ermöglichen jedoch Parametererweiterungen (Variablensubstitution, Befehlssubstitution und Backslash-Verarbeitung). Dies ist theoretisch einfach, kann jedoch verwirrend werden, insbesondere wenn mehrere Interpretationsebenen vorhanden sind (z. B. ein Remote-Shell-Befehl).
Verstehe die Unterschiede zwischensh
und Bash
subprocess
führt Ihre Shell-Befehle mit aus, /bin/sh
sofern Sie nicht ausdrücklich etwas anderes anfordern (außer natürlich unter Windows, wo der Wert der COMSPEC
Variablen verwendet wird). Dies bedeutet, dass verschiedene Nur-Bash-Funktionen wie Arrays [[
usw. nicht verfügbar sind.
Wenn Sie die Nur-Bash-Syntax verwenden müssen, können Sie den Pfad an die Shell als übergeben. executable='/bin/bash'
Wenn Ihr Bash an einer anderen Stelle installiert ist, müssen Sie den Pfad 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 übergeordneten Element getrennt und kann es nicht ändern
Ein etwas häufiger Fehler ist, so etwas zu tun
subprocess.run('foo=bar', shell=True)
subprocess.run('echo "$foo"', shell=True) # Doesn't work
was neben dem Mangel an Eleganz auch einen grundlegenden Mangel an Verständnis für den "Sub" -Teil des Namens "Subprozess" verrät.
Ein untergeordneter Prozess wird vollständig getrennt von Python ausgeführt, und wenn er abgeschlossen ist, hat Python keine Ahnung, was er getan hat (abgesehen von den vagen Indikatoren, die er aus dem Beendigungsstatus ableiten und aus dem untergeordneten Prozess ausgeben kann). Ein Kind kann die Umgebung des Elternteils im Allgemeinen nicht ändern. Es kann keine Variable festlegen, das Arbeitsverzeichnis nicht ändern oder in so vielen Worten mit dem übergeordneten Element kommunizieren, ohne dass das übergeordnete Element mitarbeitet.
Die sofortige Lösung in diesem speziellen Fall besteht darin, beide Befehle in einem einzigen Unterprozess auszuführen.
subprocess.run('foo=bar; echo "$foo"', shell=True)
obwohl für diesen speziellen Anwendungsfall die Shell offensichtlich überhaupt nicht erforderlich ist. Denken Sie daran, dass Sie die Umgebung des aktuellen Prozesses (und damit auch seiner untergeordneten Elemente) über manipulieren können
os.environ['foo'] = 'bar'
oder übergeben Sie eine Umgebungseinstellung an einen untergeordneten Prozess mit
subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})
(ganz zu schweigen von der offensichtlichen Umgestaltung subprocess.run(['echo', 'bar'])
; aber es echo
ist natürlich ein schlechtes Beispiel für etwas, das in einem Unterprozess ausgeführt werden kann).
Führen Sie Python nicht über Python aus
Dies ist ein etwas zweifelhafter Rat; Es gibt sicherlich Situationen, in denen es sinnvoll oder sogar unbedingt erforderlich ist, den Python-Interpreter als Unterprozess aus einem Python-Skript auszuführen. Sehr häufig ist es jedoch richtig, einfach import
das andere Python-Modul in Ihr aufrufendes Skript zu integrieren und dessen Funktionen direkt aufzurufen.
Wenn das andere Python-Skript unter Ihrer Kontrolle steht und kein Modul ist, sollten Sie es in eines umwandeln . (Diese Antwort ist bereits zu lang, daher werde ich hier nicht auf Details eingehen.)
Wenn Sie Parallelität benötigen, können Sie Python-Funktionen in Unterprozessen mit dem multiprocessing
Modul ausführen . Es gibt auch Funktionen, threading
die mehrere Aufgaben in einem einzigen Prozess ausführen (was leichter ist und Ihnen mehr Kontrolle bietet, aber auch dadurch eingeschränkt ist, dass Threads innerhalb eines Prozesses eng miteinander verbunden und an eine einzelne GIL gebunden sind ).
cwm
. Vielleicht haben Sie eine Konfiguration in Ihrer.bashrc
, die die Umgebung für die interaktive Bash-Verwendung einrichtet?