2009-08-10 15 views
19

Quando si utilizza subprocess.Popen(args, shell=True) di eseguire "gcc --version" (tanto per fare un esempio), su Windows otteniamo questo:Perché subprocess.Popen() con shell = True funziona in modo diverso su Linux vs Windows?

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

Quindi è bene stampare la versione come mi aspetto. Ma su Linux otteniamo questo:

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

Perché gcc non ha ricevuto l'opzione --version.

I documenti non specificano esattamente cosa dovrebbe accadere agli argomenti in Windows, ma su Unix, "Se args è una sequenza, il primo elemento specifica la stringa di comando e gli eventuali elementi aggiuntivi saranno trattati come argomenti della shell aggiuntivi. " IMHO il modo in cui Windows è migliore, perché consente di trattare le chiamate Popen(arglist) allo stesso numero di Popen(arglist, shell=True).

Perché la differenza tra Windows e Linux qui?

+0

Di solito è una buona idea includere la versione di Python che stai usando o almeno se è la riga 2 o 3. – mloskot

risposta

14

In realtà su Windows, si fa uso cmd.exe quando shell=True - si antepone cmd.exe /c (in realtà sembra la variabile COMSPEC ambiente, ma il default è cmd.exe se non presente) agli argomenti della shell. (Su Windows 95/98 utilizza il programma intermedio w9xpopen per avviare effettivamente il comando).

Così la strana applicazione è in realtà la UNIX uno, che fa il seguente (in cui ogni spazio separa un argomento diverso):

/bin/sh -c gcc --version 

Sembra che la corretta applicazione (almeno su Linux) sarebbe:

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

Poiché ciò imposterà la stringa di comando dai parametri quotati e passerà correttamente gli altri parametri.

Dalla sezione pagina sh man per -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.

Questa patch sembra di fare abbastanza semplicemente il trucco:

--- 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] 
+0

È grandioso, grazie David. Sono d'accordo sulla corretta implementazione e la tua patch sembra buona. Ti trovi in ​​una (migliore) posizione rispetto a quella di presentare una segnalazione bug di Python - in altre parole, l'hai già fatto prima o devo esaminarla? –

+1

Aggiunto http://bugs.python.org/issue6689 - sarebbe bello se si potesse seguire, commentare lì ecc. –

+0

Grazie! Mi sono aggiunto alla lista ficcanaso. –

5

Dalla fonte subprocess.py:

In UNIX, con shell = True: Se args è una stringa, specifica la stringa comando da eseguire attraverso il guscio. Se args è una sequenza, , il primo elemento specifica la stringa di comando e tutti gli elementi aggiuntivi verranno trattati come argomenti della shell aggiuntivi.

Su Windows: la classe Popen utilizza CreateProcess() per eseguire il programma figlio , che opera su stringhe. Se args è una sequenza, sarà convertito in in una stringa utilizzando il metodo list2cmdline. Si noti che non tutte le applicazioni MS Windows interpretano la riga di comando nello stesso modo : list2cmdline è progettato per applicazioni che utilizzano le stesse regole come runtime MS C.

Questo non risponde perché, solo chiarisce che stai vedendo il comportamento previsto.

Il "perché" è probabilmente quello su sistemi UNIX-like, gli argomenti dei comandi vengono effettivamente passati alle applicazioni (usando la famiglia di chiamate exec*) come una matrice di stringhe. In altre parole, il processo di chiamata decide cosa deve essere inserito in ogni argomento della riga di comando. Mentre quando si dice di usare una shell, il processo di chiamata ottiene solo la possibilità di passare un singolo argomento da riga di comando alla shell da eseguire: l'intera riga di comando che si desidera eseguire, nome eseguibile e argomenti, come una singola stringa.

Ma su Windows, l'intera riga di comando (in base alla documentazione precedente) viene passata come una singola stringa al processo figlio. Se si osserva la documentazione dell'API CreateProcess, si noterà che si aspetta che tutti gli argomenti della riga di comando siano concatenati insieme in una stringa grande (da qui la chiamata a list2cmdline).

Inoltre v'è il fatto che su sistemi UNIX-like là in realtà è un guscio che può fare cose utili, così ho il sospetto che l'altro motivo per la differenza è che su Windows, shell=True non fa nulla, ed è per questo sta funzionando nel modo in cui stai vedendo.L'unico modo per far sì che i due sistemi agiscano in modo identico sarebbe che abbandonasse semplicemente tutti gli argomenti della riga di comando quando shell=True su Windows.

+1

C'è una shell anche su Windows (normalmente 'cmd.exe'), ma il commento che hai citato sopra indica che Python in realtà non lo usa quando shell = True (invece, usa 'CreateProcess()' direttamente). –

+1

Grazie - come ha detto Greg, c'è sicuramente una shell su Windows (cmd.exe o quella in COMSPEC). Ed è usato da Popen (anche se tramite CreateProcess) - vedi l'origine subprocess.py. Quindi è ancora sembra decisamente a me che sottoprocesso dovrebbe farli funzionare allo stesso modo, per evitare le insidie ​​di portabilità ... –

+0

nota: [la documentazione è stata aggiornata dal 2010] (https://docs.python.org/3/library /subprocess.html#popen-constructor) – jfs

0

La ragione per il comportamento UNIX shell=True ha a che fare con la citazione. Quando scriviamo un comando di shell, sarà diviso in spazi, quindi dobbiamo citare alcuni argomenti:

cp "My File" "New Location" 

Questo porta a problemi quando i nostri argomenti contengono citazioni, che richiede in fuga:

grep -r "\"hello\"" . 

A volte possiamo ottenere awful situations in cui deve essere sfuggito troppo \!

Naturalmente, il vero problema è che stiamo cercando di utilizzare uno stringa per specificare multipla stringhe. Quando si chiama comandi di sistema, la maggior parte dei linguaggi di programmazione evitare questo da noi permette di inviare più stringhe, in primo luogo, da qui:

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

A volte può essere piacevole per eseguire comandi di shell "grezzi"; per esempio, se stiamo copiando qualcosa da uno script di shell o da un sito Web, e non vogliamo convertire manualmente tutte le fughe orribili. È per questo che esiste la possibilità shell=True:

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

io non sono a conoscenza di Windows in modo da non so come o perché si comporta in modo diverso.

Problemi correlati