Ejecutando comandos de Shell con Python

E

Introducción

Las tareas repetitivas están listas para la automatización. Es común que los desarrolladores y administradores de sistemas automaticen tareas rutinarias como verificaciones de estado y copias de seguridad de archivos con scripts de shell. Sin embargo, a medida que esas tareas se vuelven más complejas, los scripts de shell pueden volverse más difíciles de mantener.

Afortunadamente, podemos usar Python en lugar de scripts de shell para la automatización. Python proporciona métodos para ejecutar comandos de shell, dándonos la misma funcionalidad que esos scripts de shell. Aprender a ejecutar comandos de shell en Python nos abre la puerta para automatizar las tareas informáticas de una manera estructurada y escalable.

En este artículo, veremos las diversas formas de ejecutar comandos de shell en Python y la situación ideal para usar cada método.

Uso de os.system para ejecutar un comando

Python nos permite ejecutar inmediatamente un comando de shell que se almacena en una cadena usando el os.system() función.

Comencemos por crear un nuevo archivo de Python llamado echo_adelle.py e ingrese lo siguiente:

import os

os.system("echo Hello from the other side!")

Lo primero que hacemos en nuestro archivo Python es importar el os módulo, que contiene el system función que puede ejecutar comandos de shell. La siguiente línea hace exactamente eso, ejecuta el echo comando en nuestro shell a través de Python.

En su Terminal, ejecute este archivo usando el siguiente comando, y debería ver el resultado correspondiente:

$ python3 echo_adelle.py
Hello from the other side!

Como el echo comandos imprime a nuestro stdout, os.system() también muestra la salida en nuestro stdout corriente. Si bien no es visible en la consola, el os.system() comando devuelve el código de salida del comando de shell. Un código de salida de 0 significa que se ejecutó sin problemas y cualquier otro número significa un error.

Creemos un nuevo archivo llamado cd_return_codes.py y escriba lo siguiente:

import os

home_dir = os.system("cd ~")
print("`cd ~` ran with exit code %d" % home_dir)
unknown_dir = os.system("cd doesnotexist")
print("`cd doesnotexis` ran with exit code %d" % unknown_dir)

En este script, creamos dos variables que almacenan el resultado de ejecutar comandos que cambian el directorio a la carpeta de inicio y a una carpeta que no existe. Ejecutando este archivo, veremos:

$ python3 cd_return_codes.py
`cd ~` ran with exit code 0
sh: line 0: cd: doesnotexist: No such file or directory
`cd doesnotexist` ran with exit code 256

El primer comando, que cambia el directorio al directorio de inicio, se ejecuta correctamente. Por lo tanto, os.system() devuelve su código de salida, cero, que se almacena en home_dir. Por otra parte, unknown_dir almacena el código de salida del comando bash fallido para cambiar el directorio a una carpeta que no existe.

los os.system() La función ejecuta un comando, imprime cualquier salida del comando en la consola y devuelve el código de salida del comando. Si quisiéramos un control más detallado de la entrada y salida de un comando de shell en Python, deberíamos usar el subprocess módulo.

Ejecutando un comando con subproceso

los módulo de subproceso es la forma recomendada de Python para ejecutar comandos de shell. Nos da la flexibilidad de suprimir la salida de los comandos de shell o encadenar las entradas y salidas de varios comandos juntos, sin dejar de proporcionar una experiencia similar a os.system() para casos de uso básicos.

En un nuevo campo llamado list_subprocess.py, escribe el siguiente código:

import subprocess

list_files = subprocess.run(["ls", "-l"])
print("The exit code was: %d" % list_files.returncode)

En la primera línea, importamos el subprocess módulo, que forma parte de la biblioteca estándar de Python. Luego usamos el subprocess.run() función para ejecutar el comando. Me gusta os.system(), la subprocess.run() comando devuelve el código de salida de lo que se ejecutó.

diferente a os.system(), nota como subprocess.run() requiere una lista de cadenas como entrada en lugar de una sola cadena. El primer elemento de la lista es el nombre del comando. Los elementos restantes de la lista son las banderas y los argumentos del comando.

Nota: Como regla general, debe separar los argumentos según el espacio, por ejemplo ls -alh sería ["ls", "-alh"], mientras ls -a -l -h, sería ["ls", "-a", -"l", "-h"]. Como otro ejemplo, echo hello world sería ["echo", "hello", "world"], mientras que echo "hello world" o echo hello world sería ["echo", "hello world"].

Ejecute este archivo y la salida de su consola sería similar a:

$ python3 list_subprocess.py
total 80
[email protected] 1 Pharos.sh  staff    216 Dec  6 10:29 cd_return_codes.py
[email protected] 1 Pharos.sh  staff     56 Dec  6 10:11 echo_adelle.py
[email protected] 1 Pharos.sh  staff    116 Dec  6 11:20 list_subprocess.py
The exit code was: 0

Ahora intentemos utilizar una de las funciones más avanzadas de subprocess.run(), es decir, ignorar la salida a stdout. En el mismo list_subprocess.py archivo, cambio:

list_files = subprocess.run(["ls", "-l"])

A esto:

list_files = subprocess.run(["ls", "-l"], stdout=subprocess.DEVNULL)

La salida estándar del comando ahora canaliza al especial /dev/null dispositivo, lo que significa que la salida no aparecería en nuestras consolas. Ejecute el archivo en su shell para ver el siguiente resultado:

$ python3 list_subprocess.py
The exit code was: 0

¿Qué pasa si quisiéramos proporcionar información a un comando? los subprocess.run() facilita esto por su input argumento. Crea un nuevo archivo llamado cat_subprocess.py, escribiendo lo siguiente:

import subprocess

useless_cat_call = subprocess.run(["cat"], stdout=subprocess.PIPE, text=True, input="Hello from the other side")
print(useless_cat_call.stdout)  # Hello from the other side

Usamos subprocess.run() con bastantes comandos, repasemos ellos:

  • stdout=subprocess.PIPE le dice a Python que redirija la salida del comando a un objeto para que pueda leerse manualmente más tarde
  • text=True devoluciones stdout y stderr como cadenas. El tipo de retorno predeterminado es bytes.
  • input="Hello from the other side" le dice a Python que agregue la cadena como entrada al cat mando.

La ejecución de este archivo produce el siguiente resultado:

Hello from the other side

También podemos plantear un Exception sin comprobar manualmente el valor de retorno. En un nuevo archivo, false_subprocess.py, agregue el código a continuación:

import subprocess

failed_command = subprocess.run(["false"], check=True)
print("The exit code was: %d" % failed_command.returncode)

En su Terminal, ejecute este archivo. Verá el siguiente error:

$ python3 false_subprocess.py
Traceback (most recent call last):
  File "false_subprocess.py", line 4, in <module>
    failed_command = subprocess.run(["false"], check=True)
  File "/usr/local/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/subprocess.py", line 512, in run
    output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['false']' returned non-zero exit status 1.

Mediante el uso check=True, le decimos a Python que genere cualquier excepción si se encuentra un error. Dado que encontramos un error, el print declaración en la línea final no se ejecutó.

los subprocess.run() función nos da una inmensa flexibilidad que os.system() no lo hace al ejecutar comandos de shell. Esta función es una abstracción simplificada de la subprocess.Popen class, que proporciona una funcionalidad adicional que podemos explorar.

Ejecutando un comando con Popen

los subprocess.Popen La clase expone más opciones al desarrollador al interactuar con el shell. Sin embargo, debemos ser más explícitos sobre la recuperación de resultados y errores.

Por defecto, subprocess.Popen no detiene el procesamiento de un programa Python si su comando no ha terminado de ejecutarse. En un nuevo archivo llamado list_popen.py, escriba lo siguiente:

import subprocess

list_dir = subprocess.Popen(["ls", "-l"])
list_dir.wait()

Este código es equivalente al de list_subprocess.py. Ejecuta un comando usando subprocess.Popeny espera a que se complete antes de ejecutar el resto del script de Python.

Digamos que no queremos esperar a que nuestro comando de shell complete la ejecución para que el programa pueda trabajar en otras cosas. ¿Cómo sabría cuando el comando de shell ha terminado de ejecutarse?

los poll() El método devuelve el código de salida si un comando ha terminado de ejecutarse, o None si todavía se está ejecutando. Por ejemplo, si quisiéramos comprobar si list_dir estaba completo en lugar de esperarlo, tendríamos la siguiente línea de código:

list_dir.poll()

Para gestionar la entrada y la salida con subprocess.Popen, necesitamos usar el communicate() método.

En un nuevo archivo llamado cat_popen.py, agregue el siguiente fragmento de código:

import subprocess

useless_cat_call = subprocess.Popen(["cat"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output, errors = useless_cat_call.communicate(input="Hello from the other side!")
useless_cat_call.wait()
print(output)
print(errors)

los communicate() el método toma un input argumento que se usa para pasar la entrada al comando de shell. los communicate El método también devuelve tanto el stdout y stderr cuando se establecen.

Habiendo visto las ideas centrales detrás subprocess.Popen, ahora hemos cubierto tres formas de ejecutar comandos de shell en Python. Reexaminemos sus características para saber qué método se adapta mejor a los requisitos de un proyecto.

¿Cuál debo usar?

Si necesita ejecutar uno o algunos comandos simples y no le importa si su salida va a la consola, puede usar el os.system() mando. Si desea administrar la entrada y salida de un comando de shell, use subprocess.run(). Si desea ejecutar un comando y continuar haciendo otro trabajo mientras se ejecuta, use subprocess.Popen.

Aquí hay una tabla con algunas diferencias de usabilidad que también puede usar para informar su decisión:

os.system subprocess.run subprocess.Popen
Requiere argumentos analizados no sí sí
Espera la orden si si no
Se comunica con stdin y stdout no si si
Devuelve el objeto de valor devuelto

Conclusión

Python le permite ejecutar comandos de shell, que puede usar para iniciar otros programas o administrar mejor los scripts de shell que usa para la automatización. Dependiendo de nuestro caso de uso, podemos usar os.system(), subprocess.run() o subprocess.Popen para ejecutar comandos bash.

Usando estas técnicas, ¿qué tarea externa ejecutarías a través de Python?

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con tus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad