2009-08-10 11 views
19

Al utilizar subprocess.Popen(args, shell=True) para funcionar "gcc --version" (solo como ejemplo), en Windows obtenemos esto:¿Por qué subprocess.Popen() with shell = True funciona de manera diferente en Linux vs Windows?

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

Así que está muy bien imprimiendo la versión como espero. Pero en Linux obtenemos esto:

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

Debido gcc no ha recibido la opción --version.

Los documentos no especifican exactamente lo que debería sucederle a los argumentos bajo Windows, pero sí dice, en Unix, "Si args es una secuencia, el primer elemento especifica la cadena de comandos, y cualquier elemento adicional será tratado como argumentos de shell adicionales ". En mi humilde opinión, la forma de Windows es mejor, ya que le permite tratar las llamadas Popen(arglist) de la misma manera que Popen(arglist, shell=True).

¿Por qué la diferencia entre Windows y Linux aquí?

+0

Por lo general, es una buena idea incluir la versión de Python que está utilizando o al menos si es la línea 2 o 3. – mloskot

Respuesta

14

realidad en Windows, que hace uso de cmd.exe cuando shell=True - se antepone cmd.exe /c (que en realidad busca la variable de entorno COMSPEC pero pasará al cmd.exe si no está presente) a los argumentos de la cáscara. (En Windows 95/98 usa el programa intermedio w9xpopen para ejecutar realmente el comando).

Así que la extraña aplicación es en realidad el UNIX uno, lo que hace lo siguiente (donde cada espacio que separa a un argumento diferente):

/bin/sh -c gcc --version 

Parece que la aplicación correcta (al menos en Linux) sería:

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

Dado que esto establecería la cadena de comando a partir de los parámetros entrecomillados, y pasaría los demás parámetros con éxito.

Desde la sección de la página sh hombre por -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.

Este parche parece bastante simple hacer el truco:

--- 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

Eso es genial, gracias David. Estoy de acuerdo con la implementación correcta y tu parche se ve bien. ¿Estás en una (mejor) posición que yo para enviar un informe de errores de Python, en otras palabras, has hecho eso antes, o debo analizarlo? –

+1

Agregado http://bugs.python.org/issue6689 - sería bueno si pudieras seguirlo, comentar allí, etc. –

+0

¡Gracias! Me he agregado a la lista entrometida. –

5

Desde la fuente subprocess.py:

En UNIX, con la cáscara = True: Si args es una cadena, se especifica la cadena comando a ejecutar a través de la cáscara. Si args es una secuencia, el primer elemento especifica la cadena de comandos, y los elementos adicionales se tratarán como argumentos de shell adicionales.

En Windows: la clase Popen utiliza CreateProcess() para ejecutar el programa secundario, que opera en cadenas. Si args es una secuencia, será convertida en una cadena utilizando el método list2cmdline. Tenga en cuenta que no todas las aplicaciones de MS Windows interpretan la línea de comandos de la misma manera : La lista2cmdline está diseñada para aplicaciones que usan las mismas reglas que el tiempo de ejecución MS C.

Eso no responde por qué, solo aclara que está viendo el comportamiento esperado.

El "por qué" es probablemente que en los sistemas tipo UNIX, los argumentos del comando se pasan a las aplicaciones (usando la familia de llamadas exec*) como una matriz de cadenas. En otras palabras, el proceso de llamada decide qué entra en CADA argumento de línea de comando. Mientras que cuando le dices que use un intérprete de órdenes, el proceso de llamada solo tiene la posibilidad de pasar un solo argumento de línea de comando al intérprete de comandos para ejecutar: Toda la línea de comando que quieras ejecutar, nombre ejecutable y argumentos, como una cadena única.

Pero en Windows, toda la línea de comandos (según la documentación anterior) se pasa como una cadena al proceso secundario. Si nos fijamos en la documentación de la API CreateProcess, observará que espera que todos los argumentos de la línea de comandos se concatenen juntos en una cadena grande (de ahí la llamada al list2cmdline).

Además está el hecho de que en los sistemas tipo UNIX allí en realidad es una concha que puede hacer cosas útiles, por lo que sospecho que la otra razón de la diferencia es que en Windows, shell=True no hace nada, y por eso está funcionando de la manera que estás viendo.La única forma de hacer que los dos sistemas actúen de forma idéntica sería que simplemente descarte todos los argumentos de la línea de comando cuando shell=True en Windows.

+1

También hay un shell en Windows (normalmente 'cmd.exe'), pero el comentario que citó más arriba indica que Python en realidad no lo usa cuando shell = True (en cambio, usa 'CreateProcess()' directamente). –

+1

Gracias - como Greg mencionó, definitivamente hay un shell en Windows (cmd.exe o el de COMSPEC). Y es usado por Popen (aunque a través de CreateProcess) - vea la fuente subprocess.py. Así que definitivamente me parece que el subproceso debería hacer que funcionen de la misma manera, para evitar riesgos de portabilidad ... –

+0

nota: [los documentos se han actualizado desde 2010] (https://docs.python.org/3/library /subprocess.html#popen-constructor) – jfs

0

La razón de la conducta UNIX shell=True tiene que ver con las citas. Cuando escribimos un comando shell, se divide en espacios, así que tenemos que citar algunos argumentos:

cp "My File" "New Location" 

Esto lleva a problemas cuando nuestros argumentos contienen citas, lo que requiere que escapan:

grep -r "\"hello\"" . 

A veces podemos obtener awful situations donde también se debe escapar \!

Por supuesto, el problema real es que estamos tratando de utilizar una cadena para especificar múltiples cadenas. Cuando se llama a los comandos del sistema, la mayoría de los lenguajes de programación evitar esto lo que nos permite enviar múltiples cadenas, en primer lugar, por lo tanto:

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

A veces puede ser agradable para ejecutar comandos shell "en bruto"; por ejemplo, si estamos copiando y pegando algo de un script de shell o un sitio web, y no queremos convertir todo el horrible escape de forma manual. Es por eso que existe la opción shell=True:

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

No estoy familiarizado con Windows, así que no sé cómo o por qué se comporta de manera diferente.

Cuestiones relacionadas