Tatsächliche Bedeutung von 'shell = True' im Unterprozess

260

Ich rufe mit dem subprocessModul verschiedene Prozesse auf . Ich habe jedoch eine Frage.

In den folgenden Codes:

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

und

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

Beide arbeiten. Nachdem ich die Dokumente gelesen hatte, wurde mir klar, dass dies shell=Truebedeutet, den Code über die Shell auszuführen. Das heißt, in Abwesenheit wird der Prozess direkt gestartet.

Was sollte ich für meinen Fall bevorzugen? Ich muss einen Prozess ausführen und seine Ausgabe erhalten. Welchen Vorteil habe ich, wenn ich es innerhalb oder außerhalb der Shell aufrufe.

user225312
quelle
21
Der erste Befehl ist falsch: Wird-l an /bin/sh(die Shell) anstelle des lsProgramms unter Unix übergeben, wennshell=True . shell=TrueIn den meisten Fällen sollte ein String-Argument anstelle einer Liste verwendet werden.
JFS
1
Zu "der Prozess wird direkt gestartet": Wut?
Allyourcode
9
Die Aussage "Beide funktionieren." über diese 2 Anrufe ist falsch und irreführend. Die Anrufe funktionieren anders. Nur das Umschalten von shell=Truenach Falseund umgekehrt ist ein Fehler. Aus den Dokumenten : "Unter POSIX mit shell = True (...) Wenn args eine Sequenz ist, gibt das erste Element die Befehlszeichenfolge an, und alle zusätzlichen Elemente werden als zusätzliche Argumente für die Shell selbst behandelt." Unter Windows gibt es eine automatische Konvertierung , die möglicherweise unerwünscht ist.
mbdevpl

Antworten:

183

Der Vorteil, nicht über die Shell aufzurufen, besteht darin, dass Sie kein "Mystery-Programm" aufrufen. Unter POSIX SHELLsteuert die Umgebungsvariable, welche Binärdatei als "Shell" aufgerufen wird. Unter Windows gibt es keine Nachkommen der Bourne-Shell, sondern nur cmd.exe.

Das Aufrufen der Shell ruft also ein Programm nach Wahl des Benutzers auf und ist plattformabhängig. Vermeiden Sie generell Aufrufe über die Shell.

Durch Aufrufen über die Shell können Sie Umgebungsvariablen und Dateiglob nach dem üblichen Mechanismus der Shell erweitern. Auf POSIX-Systemen erweitert die Shell Dateiglob zu einer Liste von Dateien. Unter Windows wird ein Dateiglob (z. B. "*. *") Von der Shell ohnehin nicht erweitert (Umgebungsvariablen in einer Befehlszeile werden jedoch um cmd.exe erweitert).

Wenn Sie der Meinung sind, dass Sie Erweiterungen von Umgebungsvariablen und Dateiglob möchten, untersuchen Sie die ILSAngriffe von 1992 auf Netzwerkdienste, die Unterprogrammaufrufe über die Shell ausgeführt haben. Beispiele sind die verschiedenen sendmailHintertüren ILS.

Zusammenfassend verwenden shell=False.

Heath Hunnicutt
quelle
2
Danke für die Antwort. Ich bin zwar noch nicht in der Phase, in der ich mir Gedanken über Exploits machen sollte, aber ich verstehe, worauf Sie hinaus wollen.
user225312
55
Wenn Sie am Anfang nachlässig sind, hilft Ihnen keine Sorge, später aufzuholen. ;)
Heath Hunnicutt
Was ist, wenn Sie den maximalen Speicher des Unterprozesses begrenzen möchten? stackoverflow.com/questions/3172470/…
Pramod
8
Die Aussage über $SHELList nicht korrekt. Um subprocess.html zu zitieren: "Unter Unix mit shell=Trueist die Shell standardmäßig /bin/sh." (nicht $SHELL)
Marcin
1
@ user2428107: Ja, wenn Sie den Backtick-Aufruf für Perl verwenden, verwenden Sie den Shell-Aufruf und eröffnen dieselben Probleme. Verwenden Sie 3+ arg, openwenn Sie sichere Möglichkeiten zum Aufrufen eines Programms und zum Erfassen der Ausgabe wünschen.
ShadowRanger
137
>>> 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

Wenn Sie das Shell-Argument auf einen wahren Wert setzen, erzeugt der Unterprozess einen Zwischen-Shell-Prozess und weist ihn an, den Befehl auszuführen. Mit anderen Worten bedeutet die Verwendung einer Zwischen-Shell, dass Variablen, Glob-Muster und andere spezielle Shell-Funktionen in der Befehlszeichenfolge verarbeitet werden, bevor der Befehl ausgeführt wird. In diesem Beispiel wurde $ HOME vor dem Befehl echo verarbeitet. Tatsächlich ist dies bei Befehlen mit Shell-Erweiterung der Fall, während der Befehl ls -l als einfacher Befehl betrachtet wird.

Quelle: Unterprozessmodul

Mina Gabriel
quelle
16
Ich weiß nicht, warum dies nicht die ausgewählte Antwort ist. Bei weitem derjenige, der tatsächlich der Frage entspricht
Rodrigo Lopez Guerra
1
zustimmen. Dies ist ein gutes Beispiel für mich, um zu verstehen, was Shell = True bedeutet.
user389955
2
Das Setzen des Shell-Arguments auf einen wahren Wert bewirkt, dass der Unterprozess einen Zwischen-Shell-Prozess erzeugt und ihn anweist, den Befehl Oh Gott, das sagt alles. Warum wird diese Antwort nicht akzeptiert ??? Warum?
Pouya
Ich denke, das Problem ist das erste Argument, das aufgerufen wird, ist eine Liste, keine Zeichenfolge, aber das gibt den Fehler aus, wenn die Shell False ist. Wenn Sie den Befehl in eine Liste
Lincoln Randall McFarland,
Entschuldigung, mein vorheriger Kommentar ging, bevor ich fertig war. Um es klar auszudrücken: Ich sehe oft die Verwendung von Unterprozessen mit shell = True und der Befehl ist eine Zeichenfolge, z. B. 'ls -l' (ich erwarte, diesen Fehler zu vermeiden), aber der Unterprozess verwendet eine Liste (und eine Zeichenfolge als Liste mit einem Element). . Verwenden Sie eine Liste subprocess.call (['ls', '-l'])
Lincoln Randall McFarland,
42

Ein Beispiel, in dem mit Shell = True etwas schief gehen könnte, wird hier gezeigt

>>> from subprocess import call
>>> filename = input("What file would you like to display?\n")
What file would you like to display?
non_existent; rm -rf / # THIS WILL DELETE EVERYTHING IN ROOT PARTITION!!!
>>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...

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

Richeek
quelle
6
Der Link ist sehr nützlich. Wie im Link angegeben: Das Ausführen von Shell-Befehlen, die nicht bereinigte Eingaben von einer nicht vertrauenswürdigen Quelle enthalten, macht ein Programm anfällig für Shell-Injection, eine schwerwiegende Sicherheitslücke, die zu einer willkürlichen Befehlsausführung führen kann. Aus diesem Grund wird von der Verwendung von shell = True in Fällen, in denen die Befehlszeichenfolge aus externen Eingaben erstellt wird, dringend abgeraten.
Jtuki
39

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

shell=TrueManchmal ist es praktisch, bestimmte Shell-Funktionen wie Wortaufteilung oder Parametererweiterung zu verwenden. Wenn eine solche Funktion erforderlich ist, können Sie jedoch andere Module verwenden (z. B. os.path.expandvars()zur Parametererweiterung oder shlexzur Wortaufteilung). Dies bedeutet mehr Arbeit, vermeidet jedoch andere Probleme.

Kurzum: Auf shell=Truejeden Fall vermeiden .

Mondhorn
quelle
16

Die anderen Antworten hier erläutern angemessen die Sicherheitsvorbehalte, die auch in der subprocessDokumentation erwähnt werden. Darüber hinaus ist der Aufwand für das Starten einer Shell zum Starten des Programms, das Sie ausführen möchten, häufig unnötig und in Situationen, in denen Sie keine der Funktionen der Shell verwenden, definitiv albern. Darüber hinaus sollte Sie die zusätzliche versteckte Komplexität erschrecken, insbesondere wenn Sie mit der Shell oder den von ihr bereitgestellten Diensten nicht sehr vertraut sind.

Wenn die Interaktionen mit der Shell nicht trivial sind, benötigen Sie jetzt den Leser und Betreuer des Python-Skripts (das möglicherweise Ihr zukünftiges Selbst ist oder nicht), um sowohl Python- als auch Shell-Skript zu verstehen. Denken Sie daran, dass das Python-Motto "explizit ist besser als implizit"; Selbst wenn der Python-Code etwas komplexer sein wird als das entsprechende (und oft sehr knappe) Shell-Skript, ist es möglicherweise besser, die Shell zu entfernen und die Funktionalität durch native Python-Konstrukte zu ersetzen. Es ist oft eine gute Idee, die in einem externen Prozess geleistete Arbeit zu minimieren und die Kontrolle innerhalb Ihres eigenen Codes so weit wie möglich zu behalten, nur weil dies die Sichtbarkeit verbessert und das Risiko von gewünschten oder unerwünschten Nebenwirkungen verringert.

Platzhaltererweiterung, variable Interpolation und Umleitung lassen sich einfach durch native Python-Konstrukte ersetzen. Eine komplexe Shell-Pipeline, in der Teile oder alle in Python nicht angemessen umgeschrieben werden können, ist die einzige Situation, in der Sie möglicherweise die Verwendung der Shell in Betracht ziehen könnten. Sie sollten dennoch sicherstellen, dass Sie die Auswirkungen auf Leistung und Sicherheit verstehen.

Um dies zu vermeiden shell=True, ersetzen Sie es einfach

subprocess.Popen("command -with -options 'like this' and\\ an\\ argument", shell=True)

mit

subprocess.Popen(['command', '-with','-options', 'like this', 'and an argument'])

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

Abgesehen davon möchten Sie sehr oft vermeiden, Popendass einer der einfacheren Wrapper im subprocessPaket das tut, was Sie wollen. Wenn Sie über genügend Python verfügen, sollten Sie es wahrscheinlich verwenden subprocess.run.

  • Damit check=Trueschlägt fehl, wenn der von Ihnen ausgeführte Befehl fehlgeschlagen ist.
  • Damit stdout=subprocess.PIPEwird die Ausgabe des Befehls erfasst.
  • Etwas dunkel, universal_newlines=Truedamit wird die Ausgabe in eine richtige Unicode-Zeichenfolge dekodiert (es ist nur bytesin der Systemcodierung anders, unter Python 3).

Wenn nicht, möchten Sie für viele Aufgaben check_outputdie Ausgabe von einem Befehl abrufen, während Sie überprüfen, ob er erfolgreich war, oder check_callwenn keine Ausgabe zum Sammeln vorhanden ist.

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

Tripleee
quelle
Ich dachte, das Zitat stamme von Larry Wall, aber Google sagt mir etwas anderes.
Tripleee
Das ist viel geredet - aber kein technischer Vorschlag zum Ersetzen: Hier versuche ich unter OS-X, die PID einer Mac-App zu erhalten, die ich über 'open' gestartet habe: process = subprocess.Popen ('/ usr / bin / pgrep - n '+ app_name, shell = False, stdout = subprocess.PIPE, stderr = subprocess.PIPE) app_pid, err = process.communicate () --- aber es funktioniert nur, wenn ich shell = True verwende. Was jetzt?
Motti Shneor
Es gibt eine Menge Fragen, wie man es vermeidet shell=True, viele mit ausgezeichneten Antworten. Sie haben zufällig die ausgewählt, bei der es darum geht, warum .
Tripleee
@MottiShneor Danke für das Feedback; einfaches Beispiel hinzugefügt
Tripleee
Vielleicht sehen Sie auch meine Antwort auf eine allgemeine Frage übersubprocess
Tripleee