25 Stimmen

Warum funktioniert subprocess.Popen() mit shell=True unter Linux anders als unter Windows?

Bei der Verwendung von subprocess.Popen(args, shell=True) zu laufen " gcc --version " (nur als Beispiel), unter Windows erhalten wir dies:

>>> from subprocess import Popen
>>> Popen(['gcc', '--version'], shell=True)
gcc (GCC) 3.4.5 (mingw-vista special r3) ...

Es druckt also die Version so aus, wie ich es erwarte. Aber unter Linux erhalten wir dies:

>>> from subprocess import Popen
>>> Popen(['gcc', '--version'], shell=True)
gcc: no input files

Da der gcc nicht die --version Option.

Die Dokumentation gibt nicht genau an, was unter Windows mit den Args passieren soll, aber unter Unix steht es, "Wenn args eine Sequenz ist, gibt das erste Element die Befehlszeichenfolge an, und alle weiteren Elemente werden als zusätzliche Shell-Argumente behandelt." IMHO ist die Windows-Methode besser, denn sie ermöglicht die Behandlung von Popen(arglist) ruft das Gleiche auf wie Popen(arglist, shell=True) ein.

Warum wird hier ein Unterschied zwischen Windows und Linux gemacht?

16voto

David Fraser Punkte 4550

Unter Windows verwendet es tatsächlich cmd.exe wenn shell=True - es setzt vor cmd.exe /c (es sucht tatsächlich die COMSPEC Umgebungsvariable, sondern standardmäßig auf cmd.exe falls nicht vorhanden) zu den Shell-Argumenten. (Unter Windows 95/98 verwendet es das Zwischenprodukt w9xpopen Programm, um den Befehl tatsächlich zu starten).

Die seltsame Implementierung ist also eigentlich die UNIX eine, die Folgendes tut (wobei jedes Leerzeichen ein anderes Argument trennt):

/bin/sh -c gcc --version

Es sieht so aus, als ob die korrekte Implementierung (zumindest unter Linux) wäre:

/bin/sh -c "gcc --version" gcc --version

Da dies die Befehlszeichenfolge aus den in Anführungszeichen gesetzten Parametern setzen und die anderen Parameter erfolgreich übergeben würde.

Von der sh Abschnitt der Manpage für -c :

Read commands from the command_string operand instead of from the standard input. Special parameter 0 will be set from the command_name operand and the positional parameters ($1, $2, etc.) set from the remaining argument operands.

Dieser Patch scheint das Problem ganz einfach zu lösen:

--- subprocess.py.orig  2009-04-19 04:43:42.000000000 +0200
+++ subprocess.py       2009-08-10 13:08:48.000000000 +0200
@@ -990,7 +990,7 @@
                 args = list(args)

             if shell:
-                args = ["/bin/sh", "-c"] + args
+                args = ["/bin/sh", "-c"] + [" ".join(args)] + args

             if executable is None:
                 executable = args[0]

5voto

Adam Batkin Punkte 49295

Aus dem Quelltext von subprocess.py:

Unter UNIX, mit shell=True: Wenn args eine Zeichenkette ist, gibt sie die Befehlszeichenfolge an, die über die Shell ausgeführt werden soll. Wenn args eine Sequenz ist, gibt das erste Element die Befehlszeichenfolge an, und alle weiteren Elemente werden als zusätzliche Shell-Argumente behandelt.

Unter Windows: Die Popen-Klasse verwendet CreateProcess(), um das Kindprogramm auszuführen Programm auszuführen, das mit Zeichenketten arbeitet. Wenn args eine Sequenz ist, wird sie mit der Methode list2cmdline in eine Zeichenkette umgewandelt. Bitte beachten Sie, dass nicht alle MS-Windows-Anwendungen die Befehlszeile gleich interpretieren Weise interpretieren: Die list2cmdline-Methode ist für Anwendungen gedacht, die die gleichen Regeln wie die MS C-Laufzeit verwenden.

Das beantwortet nicht die Frage nach dem Grund, sondern verdeutlicht nur, dass Sie das erwartete Verhalten sehen.

Das "Warum" liegt wahrscheinlich daran, dass auf UNIX-ähnlichen Systemen Befehlsargumente tatsächlich an Anwendungen weitergegeben werden (unter Verwendung der exec* Familie von Aufrufen) als ein Array von Zeichenketten. Mit anderen Worten: Der aufrufende Prozess entscheidet, was in JEDES Befehlszeilenargument eingeht. Wenn Sie ihm hingegen sagen, dass er eine Shell verwenden soll, hat der aufrufende Prozess nur die Möglichkeit, ein einziges Befehlszeilenargument zur Ausführung an die Shell zu übergeben: Die gesamte Befehlszeile, die ausgeführt werden soll, mit dem Namen der ausführbaren Datei und den Argumenten, als eine einzige Zeichenkette.

Unter Windows wird jedoch die gesamte Befehlszeile (gemäß der obigen Dokumentation) als eine einzige Zeichenkette an den Kindprozess übergeben. Wenn Sie sich die CreateProcess API-Dokumentation werden Sie feststellen, dass sie erwartet, dass alle Befehlszeilenargumente zu einer großen Zeichenkette zusammengefügt werden (daher der Aufruf von list2cmdline ).

Hinzu kommt die Tatsache, dass es auf UNIX-ähnlichen Systemen tatsächlich es eine Shell, die nützliche Dinge tun kann, so dass ich vermute, dass der andere Grund für den Unterschied ist, dass auf Windows, shell=True macht nichts, deshalb funktioniert es so, wie Sie es sehen. Die einzige Möglichkeit, dass sich die beiden Systeme identisch verhalten, wäre, dass es einfach alle Befehlszeilenargumente fallen lässt, wenn shell=True unter Windows.

-1voto

Warbo Punkte 2482

Der Grund für das UNIX-Verhalten von shell=True hat mit Zitaten zu tun. Wenn wir einen Shell-Befehl schreiben, wird er an Leerzeichen geteilt, also müssen wir einige Argumente in Anführungszeichen setzen:

cp "My File" "New Location"

Dies führt zu Problemen, wenn unsere Argumente enthalten Anführungszeichen, was ein Escaping erfordert:

grep -r "\"hello\"" .

Manchmal können wir schlimme Situationen donde \ muss auch entkommen werden!

Das eigentliche Problem ist natürlich, dass wir versuchen, die Zeichenfolge zur Angabe mehrere Zeichenketten. Beim Aufruf von Systembefehlen vermeiden die meisten Programmiersprachen dies, indem sie uns erlauben, von vornherein mehrere Zeichenketten zu senden:

Popen(['cp', 'My File', 'New Location'])
Popen(['grep', '-r', '"hello"'])

Manchmal kann es sinnvoll sein, "rohe" Shell-Befehle auszuführen, z. B. wenn wir etwas aus einem Shell-Skript oder von einer Website kopieren und nicht all die schrecklichen Escapings manuell umwandeln wollen. Aus diesem Grund ist das shell=True Option existiert:

Popen(['cp "My File" "New Location"'], shell=True)
Popen(['grep -r "\"hello\"" .'], shell=True)

Ich bin mit Windows nicht vertraut und weiß daher nicht, wie oder warum es sich anders verhält.

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