Ejecutando comandos de Shell con Python

    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?

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *