subprocess – Creación y comunicación con procesos



El módulo estándar subprocess permite invocar procesos desde Python y comunicarse con ellos: enviar datos a la entrada (stdin) y recibir la información de salida (stdout). Además, esperar a que el proceso finalice o bien terminarlo prematuramente, y obtener el valor de retorno. Resulta ideal y es el método recomendado para ejecutar comandos del sistema operativo o lanzar programas (en lugar de la tradicional os.system()) y opcionalmente interactuar con ellos.

La forma más sencilla de ejecutar un comando o invocar un proceso es vía la función call() (desde Python 2.4 hasta 3.4) o run() (3.5+). Por ejemplo, el siguiente código ejecuta un comando que reinicia el sistema.

>>> import subprocess
>>> subprocess.run(["shutdown", "-r"])

>>> import subprocess
>>> subprocess.call(["shutdown", "-r"])

Cuando invocamos un comando que contiene espacios (especialmente en distribuciones de Linux) es recomendable pasar una lista en lugar de una cadena para evitar que sea interpretado erróneamente.

>>> subprocess.run("shutdown -r")
Traceback (most recent call last):
  ...
OSError: [Errno 2] No such file or directory

>>> subprocess.call("shutdown -r")
Traceback (most recent call last):
  ...
OSError: [Errno 2] No such file or directory

Ambas funciones no retornan hasta tanto el programa invocado no haya finalizado. call() devuelve el valor de retorno del comando o programa ejecutado como un entero; run() devuelve una instancia de CompletedProcess, que contiene el atributo returncode.

>>> p = subprocess.run(["python", "--version"])
Python 3.5.1
>>> p.returncode
0

>>> ret = subprocess.call(["python", "--version"])
Python 2.7.13
>>> ret
0

(Por lo general 0 indica que el programa se ha ejecutado correctamente).

Para poder hacer uso de los comandos de terminal (tales como clear o cls para limpiar la consola, cd para movernos en el árbol de directorios, etc.) es necesario indicar el parámetro shell=True.

>>> subprocess.run("cls", shell=True)

>>> subprocess.call("cls", shell=True)

Es importante tener en cuenta que la utilización de shell=True es una potencial vulnerabilidad si el comando es provisto por el usuario (similar al método de inyección de código SQL en aplicaciones que se comuniquen con bases de datos).

Ahora bien, para almacenar la salida de un comando como una cadena de Python debemos especificar los parámetros stdout y stderr. Por ejemplo:

>>> p = subprocess.run("ver", stdout=subprocess.PIPE, shell=True)
>>> p.stdout
b'\r\nMicrosoft Windows [Versi\xa2n 6.2.9200]\r\n'

>>> p = subprocess.Popen("ver", stdout=subprocess.PIPE, shell=True)
>>> stdout = p.communicate()[0]
>>> stdout
'\r\nMicrosoft Windows [Versi\xa2n 6.2.9200]\r\n'

El comando ver de Windows envía la versión del sistema a la salida (stdout). Podemos acceder a él vía el atributo stdout (3.5+) o el método communicate() (2.4-3.4) Nótese que en Python 3 el resultado es una instancia del tipo bytes; en Python 2, del tipo str.

Para acceder a los potenciales errores enviados a stderr el proceso es similar:

>>> p = subprocess.run(["python", "-m",  "inexistente"], stderr=subprocess.PIPE)
>>> p.stderr
b'C:\\python35\\python.exe: No module named inexistente\r\n'

>>> p = subprocess.Popen(["python", "-m",  "inexistente"],
                         stderr=subprocess.PIPE)
>>> stderr = p.communicate()[1]
>>> stderr
'C:\\Python27\\python.exe: No module named inexistente\r\n'

Tanto stdout como stderr aceptan opcionalmente ficheros, para almacenar los datos en disco:

with open("errores.log", "w") as f:
    p = subprocess.run(["python", "-m", "inexistente"], stderr=f)

with open("errores.log", "w") as f:
    p = subprocess.Popen(["python", "-m", "inexistente"], stderr=f)

También es posible redireccionar todo lo enviado a stderr a stdout:

>>> p = subprocess.run(["python", "-m",  "inexistente"], stdout=subprocess.PIPE,
                       stderr=subprocess.STDOUT)
>>> p.stdout
b'C:\\python35\\python.exe: No module named inexistente\r\n'

>>> p = subprocess.Popen(["python", "-m",  "inexistente"],
                         stdout=subprocess.PIPE,
                         stderr=subprocess.STDOUT)
>>> stdout = p.communicate()[0]
>>> stdout
'C:\\Python27\\python.exe: No module named inexistente\r\n'

Para enviar datos a la entrada (stdin), consideremos en primer lugar el siguiente programa:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

try:
    # Python 2.
    name = raw_input("Nombre: ")
except NameError:
    # Python 3.
    name = input("Nombre: ")
print("Hola " + name)

Este pequeño código, llamémosle programa.py, solicita al usuario que escriba su nombre y luego imprime un saludo en pantalla. Vía subprocess podemos invocarlo, ingresar un nombre programáticamente y obtener el saludo como una cadena de Python.

p = subprocess.run(["python", "programa.py"], input=b"Pablo",
                   stdout=subprocess.PIPE)
print(p.stdout)

p = subprocess.Popen(["python", "programa.py"], stdin=subprocess.PIPE,
                     stdout=subprocess.PIPE)
stdout = p.communicate(input="Pablo")[0]
print stdout

El nombre se envía vía el parámetro input (de las funciones run() y communicate() según la versión) a la entrada (stdin), y el saludo se obtiene accediendo al contenido de la salida (stdout).

Otros argumentos válidos para Popen(), call() y run() incluyen cwd (current working directory), que indica la ubicación en la que será ejecutado el comando o programa. Esto resulta generalmente útil cuando el proceso accede a archivos de forma relativa.

>>> subprocess.run("ls", cwd="Desktop")

>>> subprocess.call("ls", cwd="Desktop")

Las instancias de Popen() incoroporan los métodos terminate() y kill() para terminar o matar un proceso, respectivamente. Distribuciones de Linux distinguen entre señales SIGTERM y SIGKILL. En Windows no hay diferencia entre ambas funciones.

# Termina el proceso prematuramente.
p = subprocess.Popen(["python", "--version"])
p.terminate()

Otros métodos incluyen p.wait() para bloquear la ejecución hasta que el programa haya finalizado (esto ocurre por defecto al llamar a call() o run()) y el atributo p.pid que almacena el ID del proceso creado.



Deja un comentario