paint-brush
Llamar a los comandos de Shell desde Python: OS.system vs Subprocesspor@marcosdelcueto
38,674 lecturas
38,674 lecturas

Llamar a los comandos de Shell desde Python: OS.system vs Subprocess

por Marcos del Cueto6m2020/11/17
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

Llamar a los comandos de Shell desde Python es útil para familiarizarse con cómo llamar a estos comandos de manera eficiente desde su código de Python. En este breve artículo, discuto cómo usar el comando os.system más antiguo (aunque todavía relativamente común) y el comando subprocess más nuevo. Mostraré algunos de sus riesgos potenciales, limitaciones y proporcionaré ejemplos completos de su uso. Por ejemplo, el siguiente código sería equivalente al código anterior a continuación. El artículo también proporciona ejemplos del uso de estos comandos.

People Mentioned

Mention Thumbnail

Company Mentioned

Mention Thumbnail
featured image - Llamar a los comandos de Shell desde Python: OS.system vs Subprocess
Marcos del Cueto HackerNoon profile picture

Si es un programador de Python, es muy probable que tenga experiencia en secuencias de comandos de shell. No es raro enfrentarse a una tarea que parece trivial de resolver con un comando de shell. Por lo tanto, es útil familiarizarse con cómo llamar a estos comandos de manera eficiente desde su código Python y conocer sus limitaciones.

En este breve artículo, discuto cómo usar el antiguo (aunque aún relativamente común)

 os.system
comando y el más nuevo
 subprocess
dominio. Mostraré algunos de sus riesgos potenciales, limitaciones y proporcionaré ejemplos completos de su uso.

Los comandos externos pueden ser una opción atractiva para tareas pequeñas si está familiarizado con ellos y la alternativa de Python implicaría más tiempo de aprendizaje/implementación. Sin embargo, las funciones de Python le ahorrarán tiempo y problemas a largo plazo.

Opción 1: OS.system (obsoleto)

Muestro a continuación un ejemplo sencillo de

 os.system
para imprimir la primera línea de un archivo proporcionado por el usuario, usando
 head -n 1
.

 #!/usr/bin/env python3 import os # Define command and options wanted command = "head -n 1 " # Ask user for file name(s) - SECURITY RISK: susceptible to shell injection filename = input( "Please introduce name of file of interest:\n" ) # Run os.system and save return_value return_value = os.system(command+filename) print( '###############' ) print( 'Return Value:' , return_value)

los

 os.system
La función es fácil de usar e interpretar: simplemente use como entrada de la función el mismo comando que usaría en un shell. Sin embargo, está en desuso y se recomienda su uso
 subprocess
ahora.

Tenga en cuenta que esta función simplemente ejecutará el comando de shell y el resultado se imprimirá en la salida estándar, pero la salida que devuelve la función es el valor de retorno (0 si se ejecutó correctamente y diferente de 0 en caso contrario).

1.1: Susceptibilidad a la inyección de caparazón

Entre otros inconvenientes,

 os.system
ejecuta directamente el comando en un shell, lo que significa que es susceptible a la inyección de shell (también conocida como inyección de comando). Puede leer más sobre esto en 10 problemas de seguridad comunes en Python y cómo evitarlos por Anthony Shaw).

La inyección de Shell es un problema cada vez que

 os.system
está recibiendo una entrada sin formato, como por ejemplo cuando un usuario puede introducir un nombre de archivo, como en el ejemplo anterior. Puede intentar ejecutar el script anterior y dar la siguiente entrada:

 dummy; touch harmful_file

Esto dará como resultado que el shell haga:

 head -n 1 dummy; touch harmful_file

Como resultado, el programa ejecutará primero

 head -n 1 dummy
, como se esperaba, pero luego ejecutará el comando
 touch harmful_file
para crear un archivo llamado 'archivo_dañino'.

De acuerdo, este archivo vacío no es una gran amenaza, pero puede imaginar a un usuario agregando comandos adicionales para crear un archivo con propósitos nefastos reales. Esta inyección de shell también se puede usar para simplemente transferir o eliminar información. Por ejemplo, uno podría usar

 ;rm -rf ~
,
 ;rm -rf /
o cualquier otro comando potencialmente peligroso (¡por favor , no los intente!).

Opción 2: subprocess.call (recomendado para Python<3.5)

2.1: forma no segura de ejecutar comandos de shell externos

Una alternativa preferible es

 subprocess.call
. Como os.sys, la función subprocess.call devuelve el valor devuelto como salida. Un primer enfoque ingenuo para el subproceso es usar el
 shell=True
opción. De esta forma, el comando deseado también se ejecutará en una subcapa. Tenga en cuenta que esto no se recomienda, ya que tiene los mismos riesgos de seguridad potenciales que
 os.system
. Por ejemplo, el siguiente código sería equivalente al anterior
 os.system
ejemplo.

 #!/usr/bin/env python3 import subprocess # Define command and options wanted command = "head -n 1 " # Ask user for file name(s) - SECURITY RISK: susceptible to shell injection filename = input( "Please introduce name of file of interest:\n" ) # Run subprocess.call and save return_value return_value = subprocess.call(command+filename, shell= True ) print( '###############' ) print( 'Return value:' , return_value)

2.2: Forma preferida de ejecutar comandos externos

Una forma preferible de correr

 subprocess.call
es usando
 shell=False
(es la opción por defecto, por lo que no es necesario especificarla). Entonces, simplemente podemos llamar
 subprocess.call(args)
, dónde
 agrs[0]
contiene el comando, y
 args[1:]
contiene todas las opciones adicionales al comando.

 #!/usr/bin/env python3 import subprocess # Define command and options wanted command = "head" options = "-n 1" # Ask user for file name(s) - now it's safe from shell injection filename = input( "Please introduce name(s) of file(s) of interest:\n" ) # Create list with arguments for subprocess.call args=[] args.append(command) args.append(options) for i in filename.split(): args.append(i) # Run subprocess.call and save return_value return_value = subprocess.call(args) print( '###############' ) print( 'Return value:' , return_value)

2.3: Obtener salida estándar:
 subprocess.check_output

Como se mencionó antes,

 subprocess.call
devuelve el valor devuelto . Sin embargo, a veces nos puede interesar la salida estándar devuelta por el comando de shell. En este caso, podemos hacer uso de
 subprocess.check_output
.

Para hacer que nuestro script sea más sólido, podemos agregar una declaración de prueba/exclusión para tratar situaciones en las que

 check_output
genera un error (en este caso, por ejemplo, cuando no se encuentra ningún archivo).

Como se muestra a continuación, podemos usar

 subprocess.CalledProcessError
en la cláusula de excepción para tratar el error de forma controlada y obtener información al respecto.

 #!/usr/bin/env python3 import subprocess # Define command and options wanted command = "head" options = "-n 1" # Ask user for file name(s) - now it's safe from shell injection filename = input( "Please introduce name(s) of file(s) of interest:\n" ) # Create list with arguments for subprocess.check_output args=[] args.append(command) args.append(options) for i in filename.split(): args.append(i) # Run subprocess.check_output and save command output try : output = subprocess.check_output(args) # use decode function to convert to string print( '###############' ) print( 'Output:' , output.decode( "utf-8" )) # If check_output returns an error: except subprocess.CalledProcessError as error: print( 'Error code:' , error.returncode, '. Output:' , error.output.decode( "utf-8" ))

Opción 3: subprocess.run (recomendado desde Python 3.5)

La forma recomendada de ejecutar comandos de shell externos , desde Python 3.5, es con el

 subprocess.run
función. Es más seguro y fácil de usar que las opciones anteriores discutidas.

De forma predeterminada, esta función devuelve un objeto con el comando de entrada y el código de retorno. También se puede obtener fácilmente la salida estándar usando la opción

 capture_output=True
y finalmente recuperar el código de retorno y la salida del comando usando:
 output.returncode
y
 output.stdout
, respectivamente.

 #!/usr/bin/env python3 import subprocess # Define command and options wanted command = "head" options = "-n 1" # Ask user for file name(s) - now it's safe from shell injection filename = input( "Please introduce name(s) of file(s) of interest:\n" ) # Create list with arguments for subprocess.run args=[] args.append(command) args.append(options) for i in filename.split(): args.append(i) # Run subprocess.run and save output object output = subprocess.run(args,capture_output= True ) print( '###############' ) print( 'Return code:' , output.returncode) # use decode function to convert to string print( 'Output:' ,output.stdout.decode( "utf-8" ))

Nota final sobre los comandos de Shell en Python

Si está pensando en usar cualquiera de los métodos discutidos aquí para llamar a un comando externo, podría valer la pena considerar si existe una función estándar de Python que le permita realizar la misma tarea y evitará la creación de un nuevo proceso. Tenga en cuenta que el uso de comandos externos también hace que su programa sea menos compatible con varias plataformas.

Los comandos externos pueden ser una opción atractiva para tareas pequeñas si está familiarizado con ellos y la alternativa de Python implicaría más tiempo de aprendizaje/implementación.

Sin embargo, apegarse a las funciones de Python le ahorrará tiempo de cálculo y problemas a largo plazo, por lo que los comandos externos deben guardarse para tareas que no se pueden lograr con las bibliotecas estándar de Python.

¡Espero que este artículo te haya ayudado a llamar comandos externos desde tu código python!