423 Stimmen

Tatsächliche Bedeutung von 'shell=True' in subprocess

Ich rufe verschiedene Prozesse mit dem subprocess Modul auf. Allerdings habe ich eine Frage.

In dem folgenden Code:

callProcess = subprocess.Popen(['ls', '-l'], shell=True)

und

callProcess = subprocess.Popen(['ls', '-l']) # ohne shell

Beide funktionieren. Nachdem ich die Dokumentation gelesen habe, habe ich herausgefunden, dass shell=True bedeutet, den Code über die Shell auszuführen. Das heißt, in Abwesenheit wird der Prozess direkt gestartet.

Was sollte ich also für meinen Fall bevorzugen - ich muss einen Prozess ausführen und seine Ausgabe erhalten. Welchen Vorteil habe ich davon, ihn innerhalb oder außerhalb der Shell aufzurufen?

47 Stimmen

Der erste Befehl ist falsch: -l wird an /bin/sh (die Shell) übergeben statt an das ls Programm auf Unix wenn shell=True. Ein String-Argument sollte in den meisten Fällen mit shell=True verwendet werden anstelle einer Liste.

3 Stimmen

Bitte übersetzen: "der Prozess wird direkt gestartet": Was?

21 Stimmen

Die Aussage "Beide funktionieren." über diese 2 Aufrufe ist falsch und irreführend. Die Aufrufe funktionieren unterschiedlich. Das Umschalten von shell=True auf False und umgekehrt ist ein Fehler. Aus Dokumenten: "Auf POSIX mit shell=True, (...) Wenn args eine Sequenz ist, gibt das erste Element den Befehlsstring an, und alle zusätzlichen Elemente werden als zusätzliche Argumente für die Shell selbst behandelt.". Auf Windows gibt es eine automatische Umwandlung, die unerwünscht sein könnte.

301voto

Heath Hunnicutt Punkte 17871

Der Vorteil, nicht über die Shell anzurufen, besteht darin, dass kein "Mystery-Programm" aufgerufen wird. Auf POSIX-Systemen steuert die Umgebungsvariable SHELL, welches Binärprogramm als "Shell" aufgerufen wird. Auf Windows gibt es keinen Bourne-Shell-Nachkommen, nur cmd.exe.

Daher erfolgt der Aufruf der Shell durch den Benutzer ausgewählten Programm und ist plattformabhängig. Grundsätzlich sollten Aufrufe über die Shell vermieden werden.

Das Aufrufen über die Shell ermöglicht es Ihnen, Umgebungsvariablen und Dateimuster gemäß dem üblichen Mechanismus der Shell zu erweitern. Auf POSIX-Systemen erweitert die Shell Dateimuster zu einer Liste von Dateien. Auf Windows wird ein Dateimuster (z.B. "*.*") sowieso nicht von der Shell erweitert (aber Umgebungsvariablen auf der Befehlszeile werden von cmd.exe erweitert).

Wenn Sie Umgebungsvariablenerweiterungen und Dateimuster wünschen, sollten Sie die Angriffe von 1992 auf Netzwerkdienste untersuchen, die Subprogrammaufrufe über die Shell durchgeführt haben (ILS-Angriffe). Beispiele hierfür sind verschiedene Hintertüren von sendmail, die ILS involvieren.

Zusammenfassend verwenden Sie shell=False.

235voto

Mina Gabriel Punkte 19473
>>> import subprocess
>>> subprocess.call('echo $HOME')
Traceback (most recent call last):
...
OSError: [Errno 2] No such file or directory
>>>
>>> subprocess.call('echo $HOME', shell=True)
/user/khong
0

Das Setzen des shell-Arguments auf einen Wahrwert bewirkt, dass subprocess einen Zwischenschalenprozess erstellt und ihm mitteilt, den Befehl auszuführen. Mit anderen Worten bedeutet die Verwendung einer Zwischenschale, dass Variablen, globale Muster und andere spezielle Shell-Funktionen im Befehlszeichen vor der Ausführung des Befehls verarbeitet werden. Hier wurde im Beispiel $HOME vor dem echo-Befehl verarbeitet. Tatsächlich handelt es sich hier um den Fall eines Befehls mit Shell-Erweiterung, während der Befehl ls -l als einfacher Befehl betrachtet wird.

Quelle: Subprozess-Modul

68voto

Richeek Punkte 1958

Ein Beispiel, wo mit Shell=True etwas schiefgehen könnte, wird hier gezeigt

>>> from subprocess import call
>>> filename = input("Welche Datei möchten Sie anzeigen?\n")
Welche Datei möchten Sie anzeigen?
non_existent; rm -rf / # DAS WIRD ALLES IM ROOT-PARTITION LÖSCHEN!!!
>>> call("cat " + filename, shell=True) # Uh-oh. Das wird schlecht enden...

Überprüfen Sie das Dokument hier: subprocess.call()

45voto

lunaryorn Punkte 32342

Das Ausführen von Programmen über die Shell bedeutet, dass alle Benutzereingaben, die an das Programm übergeben werden, gemäß den Syntax- und semantischen Regeln der aufgerufenen Shell interpretiert werden. Im besten Fall führt dies nur zu Unannehmlichkeiten für den Benutzer, da der Benutzer diese Regeln befolgen muss. Beispielsweise müssen Pfade, die besondere Shell-Zeichen wie Anführungszeichen oder Leerzeichen enthalten, escapen. Im schlimmsten Fall führt dies zu Sicherheitslücken, da der Benutzer beliebige Programme ausführen kann.

shell=True ist manchmal praktisch, um spezifische Shell-Funktionen wie Worttrennung oder Parametererweiterung zu nutzen. Wenn jedoch eine solche Funktion erforderlich ist, nutzen Sie andere Module, die Ihnen zur Verfügung stehen (z.B. os.path.expandvars() für die Parametererweiterung oder shlex für die Worttrennung). Das bedeutet mehr Arbeit, vermeidet aber andere Probleme.

Kurz gesagt: Vermeiden Sie shell=True um jeden Preis.

29voto

tripleee Punkte 155951

Die anderen Antworten hier erklären angemessen die Sicherheitsvorkehrungen, die auch in der subprocess-Dokumentation erwähnt sind. Aber zusätzlich dazu ist der Overhead beim Starten einer Shell, um das Programm zu starten, das Sie ausführen möchten, oft unnötig und definitiv albern für Situationen, in denen Sie tatsächlich keine der Shell-Funktionen verwenden. Darüber hinaus sollte die zusätzliche versteckte Komplexität Sie abschrecken, insbesondere, wenn Sie mit der Shell oder den von ihr bereitgestellten Diensten nicht sehr vertraut sind.

Wenn die Interaktionen mit der Shell nicht unerheblich sind, erfordern Sie jetzt, dass der Leser und der Wartende des Python-Skripts (der möglicherweise nicht Ihr zukünftiges Ich ist) sowohl Python als auch Shell-Skript verstehen. Denken Sie an das Python-Motto "explizit ist besser als implizit"; selbst wenn der Python-Code etwas komplexer ist als das äquivalente (und oft sehr knappe) Shell-Skript, könnte es besser sein, die Shell zu entfernen und die Funktionalität durch native Python-Konstrukte zu ersetzen. Die Minimierung der Arbeit in einem externen Prozess und die Aufrechterhaltung der Kontrolle innerhalb Ihres eigenen Codes soweit möglich ist oft eine gute Idee, einfach weil sie die Sichtbarkeit verbessert und die Risiken von -- erwünschten oder unerwünschten -- Nebenwirkungen reduziert.

Wildcards, Variableninterpolation und Umleitungen sind alle einfach durch native Python-Konstrukte zu ersetzen. Eine komplexe Shell-Pipeline, die teilweise oder ganz vernünftigerweise nicht in Python neu geschrieben werden kann, wäre die eine Situation, in der Sie vielleicht die Shell verwenden könnten. Sie sollten jedoch sicherstellen, dass Sie die Leistungs- und Sicherheitsauswirkungen verstehen.

In einfachen Fällen, um shell=True zu vermeiden, ersetzen Sie einfach

subprocess.Popen("Befehl -mit -Optionen 'so' und\\ ein\\ Argument", shell=True)

durch

subprocess.Popen(['Befehl', '-mit', '-Optionen', 'so', 'und ein Argument'])

Beachten Sie, wie das erste Argument eine Liste von Zeichenfolgen ist, die an execvp() übergeben werden sollen, und wie das Anführungszeichen um Zeichenfolgen und das Backslash-Escapen von Shell-Metazeichen im Allgemeinen nicht erforderlich (oder nützlich oder korrekt) ist. Vielleicht sehen Sie auch Wann man Anführungszeichen um eine Shell-Variable setzen sollte?

Wenn Sie dies nicht selbst herausfinden möchten, kann die Funktion shlex.split() dies für Sie tun. Es ist Teil der Python-Standardbibliothek, aber natürlich können Sie, wenn Ihre Shell-Befehlszeichenfolge statisch ist, sie einfach einmal während der Entwicklung ausführen und das Ergebnis in Ihr Skript einfügen.

Als Anmerkung, Sie möchten sehr oft Popen vermeiden, wenn eine der einfacheren Wrapper im subprocess-Paket das tut, was Sie möchten. Wenn Sie ein aktuelles Python haben, sollten Sie wahrscheinlich subprocess.run verwenden.

  • Mit check=True schlägt es fehl, wenn der von Ihnen ausgeführte Befehl fehlschlägt.
  • Mit stdout=subprocess.PIPE wird die Ausgabe des Befehls erfasst.
  • Mit text=True (oder etwas obskurer, mit dem Synonym universal_newlines=True) wird die Ausgabe in eine ordnungsgemäße Unicode-Zeichenfolge decodiert (ansonsten sind es nur bytes im Systemencoding, auf Python 3).

Wenn nicht, für viele Aufgaben möchten Sie check_output verwenden, um die Ausgabe eines Befehls zu erhalten, während Sie überprüfen, ob er erfolgreich war, oder check_call, wenn es keine Ausgabe zu erfassen gibt.

Ich schließe mit einem Zitat von David Korn: "Es ist einfacher, eine portable Shell zu schreiben als ein portables Shell-Skript." Selbst subprocess.run('echo "$HOME"', shell=True) ist nicht auf Windows portierbar.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X