Significado real de 'shell = True' en el subproceso

260

Estoy llamando a diferentes procesos con el subprocessmódulo. Sin embargo, tengo una pregunta.

En los siguientes códigos:

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

y

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

Ambos trabajan. Después de leer los documentos, supe que eso shell=Truesignifica ejecutar el código a través del shell. Eso significa que en ausencia, el proceso se inicia directamente.

Entonces, ¿qué debería preferir para mi caso? Necesito ejecutar un proceso y obtener su salida. ¿Qué beneficio tengo de llamarlo desde dentro del shell o fuera de él?

usuario225312
fuente
21
el primer comando es incorrecto: -lse pasa a /bin/sh(el shell) en lugar del lsprograma en Unix sishell=True . El argumento de cadena debe usarse shell=Trueen la mayoría de los casos en lugar de una lista.
jfs
1
re "el proceso se inicia directamente": ¿Qué?
allyourcode el
9
La afirmación "Ambos funcionan". acerca de esas 2 llamadas es incorrecta y engañosa. Las llamadas funcionan de manera diferente. Simplemente cambiando de shell=Truea Falsey viceversa es un error. Desde documentos : "En POSIX con shell = True, (...) Si args es una secuencia, el primer elemento especifica la cadena de comando, y cualquier elemento adicional se tratará como argumentos adicionales para el propio shell". En Windows hay una conversión automática , que puede no ser deseada.
mbdevpl
Ver también stackoverflow.com/q/59641747/874188
tripleee

Respuestas:

183

El beneficio de no llamar a través del shell es que no está invocando un 'programa misterioso'. En POSIX, la variable de entorno SHELLcontrola qué binario se invoca como el "shell". En Windows, no hay descendiente de shell bourne, solo cmd.exe.

Invocar el shell invoca un programa de elección del usuario y depende de la plataforma. En términos generales, evite las invocaciones a través del shell

Invocar a través del shell le permite expandir variables de entorno y archivos globales de acuerdo con el mecanismo habitual del shell. En los sistemas POSIX, el shell expande los archivos globales a una lista de archivos. En Windows, un pegote de archivos (por ejemplo, "*. *") No se expande por la cáscara, de todos modos (pero variables de entorno en una línea de comandos están expandido en cmd.exe).

Si cree que desea expansiones variables de entorno y globos de archivos, investigue los ILSataques de 1992-ish en los servicios de red que realizaron invocaciones de subprogramas a través del shell. Los ejemplos incluyen las diversas sendmailpuertas traseras involucradas ILS.

En resumen, use shell=False.

Heath Hunnicutt
fuente
2
Gracias por la respuesta. Aunque realmente no estoy en esa etapa en la que deba preocuparme por las hazañas, pero entiendo a lo que te estás refiriendo.
user225312
55
Si eres descuidado al principio, ninguna preocupación te ayudará a ponerte al día más tarde. ;)
Heath Hunnicutt
¿Qué sucede si desea limitar la memoria máxima del subproceso? stackoverflow.com/questions/3172470/…
Pramod
8
La afirmación sobre $SHELLno es correcta. Para citar subprocess.html: "En Unix con shell=True, el shell por defecto es /bin/sh". (no $SHELL)
marcin
1
@ user2428107: Sí, si utiliza la invocación de retroceso en Perl, está utilizando la invocación de shell y está abriendo los mismos problemas. Use 3+ arg opensi desea formas seguras de invocar un programa y capturar la salida.
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

Al establecer el argumento de shell en un valor verdadero, el subproceso genera un proceso de shell intermedio y le dice que ejecute el comando. En otras palabras, el uso de un shell intermedio significa que las variables, los patrones globales y otras características especiales del shell en la cadena de comandos se procesan antes de ejecutar el comando. Aquí, en el ejemplo, $ HOME se procesó antes del comando echo. En realidad, este es el caso del comando con expansión de shell mientras que el comando ls -l se considera un comando simple.

fuente: Módulo de subproceso

Mina Gabriel
fuente
16
No sé por qué esta no es la respuesta seleccionada. Con mucho, el que realmente coincide con la pregunta
Rodrigo López Guerra
1
de acuerdo. Este es un buen ejemplo para mí para entender lo que significa shell = True.
user389955
2
Establecer el argumento de shell en un valor verdadero hace que el subproceso genere un proceso de shell intermedio y le diga que ejecute el comando Oh dios, esto lo dice todo. ¿Por qué no se acepta esta respuesta? ¿por qué?
pouya
Creo que el problema es que el primer argumento para llamar es una lista, no una cadena, pero eso da el error si shell es False. Cambiar el comando a una lista hará que esto funcione
Lincoln Randall McFarland
Lo siento, mi comentario anterior fue antes de terminar. Para ser claros: a menudo veo el uso de subprocesos con shell = True y el comando es una cadena, por ejemplo, 'ls -l' (espero evitar este error) pero el subproceso toma una lista (y una cadena como una lista de un elemento) . Para ejecutar sin invocar un shell (y los problemas de seguridad con eso ) utilice una lista subprocess.call (['ls', '-l'])
Lincoln Randall McFarland
42

Aquí se muestra un ejemplo en el que las cosas podrían salir mal con Shell = True

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

Consulta el documento aquí: subprocess.call ()

Richeek
fuente
66
El enlace es muy útil. Como decía el enlace: la ejecución de comandos de shell que incorporan entradas no desinfectadas de una fuente no confiable hace que un programa sea vulnerable a la inyección de shell, un grave defecto de seguridad que puede resultar en la ejecución arbitraria de comandos. Por esta razón, se desaconseja el uso de shell = True en los casos en que la cadena de comandos se construye a partir de una entrada externa.
jtuki
39

La ejecución de programas a través del shell significa que todas las entradas del usuario pasadas al programa se interpretan de acuerdo con la sintaxis y las reglas semánticas del shell invocado. En el mejor de los casos, esto solo causa inconvenientes al usuario, porque el usuario tiene que obedecer estas reglas. Por ejemplo, las rutas que contienen caracteres especiales de shell como comillas o espacios en blanco deben escaparse. En el peor de los casos, causa fugas de seguridad, porque el usuario puede ejecutar programas arbitrarios.

shell=TrueA veces es conveniente hacer uso de funciones específicas de shell como división de palabras o expansión de parámetros. Sin embargo, si se requiere dicha función, utilice otros módulos que se le proporcionan (por ejemplo, os.path.expandvars()para la expansión de parámetros o shlexpara la división de palabras). Esto significa más trabajo, pero evita otros problemas.

En resumen: evitar shell=Truepor todos los medios.

Lunaryorn
fuente
16

Las otras respuestas aquí explican adecuadamente las advertencias de seguridad que también se mencionan en la subprocessdocumentación. Pero además de eso, la sobrecarga de iniciar un shell para iniciar el programa que desea ejecutar a menudo es innecesaria y definitivamente tonta para situaciones en las que realmente no utiliza ninguna de las funciones del shell. Además, la complejidad oculta adicional debería asustarlo, especialmente si no está muy familiarizado con el shell o los servicios que proporciona.

Cuando las interacciones con el shell no son triviales, ahora necesita que el lector y el responsable del script Python (que puede ser o no su futuro yo) comprenda tanto el script Python como el shell. Recuerde el lema de Python "explícito es mejor que implícito"; incluso cuando el código de Python va a ser algo más complejo que el script de shell equivalente (y a menudo muy breve), es mejor que elimine el shell y reemplace la funcionalidad con construcciones de Python nativas. Minimizar el trabajo realizado en un proceso externo y mantener el control dentro de su propio código en la medida de lo posible suele ser una buena idea simplemente porque mejora la visibilidad y reduce los riesgos de efectos secundarios deseados o no deseados.

La expansión de comodines, la interpolación variable y la redirección son fáciles de reemplazar con construcciones nativas de Python. Una tubería compleja de shell en la que partes o todas las partes no pueden reescribirse razonablemente en Python sería la única situación en la que quizás podría considerar usar el shell. Todavía debe asegurarse de comprender las implicaciones de rendimiento y seguridad.

En el caso trivial, para evitar shell=True, simplemente reemplace

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

con

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

Observe cómo el primer argumento es una lista de cadenas para pasar execvp(), y cómo citar cadenas y metacaracteres de shell con barra invertida generalmente no es necesario (o útil o correcto). Quizás vea también ¿ Cuándo ajustar las comillas alrededor de una variable de shell?

Como comentario aparte, a menudo desea evitar Popensi uno de los envoltorios más simples del subprocesspaquete hace lo que desea. Si tiene un Python lo suficientemente reciente, probablemente debería usarlo subprocess.run.

  • Con check=Trueesto fallará si el comando que ejecutó falló.
  • Con stdout=subprocess.PIPEél capturará la salida del comando.
  • De manera algo oscura, con universal_newlines=Trueél decodificará la salida en una cadena Unicode adecuada (de lo bytescontrario, solo está en la codificación del sistema, en Python 3).

Si no, para muchas tareas, desea check_outputobtener la salida de un comando, mientras verifica que tuvo éxito, o check_callsi no hay salida para recopilar.

Terminaré con una cita de David Korn: "Es más fácil escribir un shell portátil que un script de shell portátil". Even subprocess.run('echo "$HOME"', shell=True)no es portátil para Windows.

tripleee
fuente
Pensé que la cita era de Larry Wall, pero Google me dice lo contrario.
tripleee
Eso es muy popular, pero no hay sugerencias técnicas para el reemplazo: aquí estoy, en OS-X, tratando de adquirir el pid de una aplicación de Mac que lancé a través de 'open': process = subprocess.Popen ('/ usr / bin / pgrep - n '+ nombre_aplicación, shell = False, stdout = subprocess.PIPE, stderr = subprocess.PIPE) app_pid, err = process.communicate () --- pero no funciona a menos que use shell = True. ¿Ahora que?
Motti Shneor
Hay un montón de preguntas sobre cómo evitar shell=True, muchas con excelentes respuestas. Por casualidad elegiste el que trata sobre por qué .
tripleee
@ MottiShneor Gracias por los comentarios; ejemplo simple agregado
tripleee