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.

Curso online 👨‍💻

¡Ya lanzamos el curso oficial de Recursos Python en Udemy! Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter en 2024.

Consultoría 💡

Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.

7 comentarios.

  1. Gracias por el aporte!!
    Verás estoy intentando automatizar con subprocess la encriptación de carpetas con el programa ecryptfs de Linux y me encuentro con el siguiente problema;

    pasSudo=’passSudo’
    pasEcry=»pasPhrase»

    p1=sub.run(args,
    input=pasSudo, # Entrada de datos para el proceso
    stdout=PIPE, # Si se desea capturar y combinar los dos flujos stderr y stdout, se ha de usar:
    stderr=STDOUT, # stdout=PIPE y stderr=STDOUT en lugar de capture_output.
    text=True, # Obtener la salida como un texto
    cwd=’/home/taicho/’,# Ruta directorio de trabajo actual
    shell=False) # Sin shell interactiva

    print(‘Salida de p1: {} >>> Código de retorno {}’.format(p1.stdout, p1.returncode)) # Captura de errores y salidas conbinados

    if p1.returncode==0:

    p2=sub.run((‘cat’),
    input=pasEcry,
    stdout=PIPE,
    stderr=STDOUT,
    text=True)
    print(‘Salida de p2: {} >>> Código de retorno {}’.format(p2.stdout, p2.returncode))

    Qué argumento le paso a p2 para introducir el pass pasEcry,

  2. Buenos días,

    Estoy intentando hacer una aplicacion pero me encuentro bloqueado tengo por un lado un interface
    app=wx.App()
    fr=test_frame(None)
    fr.Show()
    app.MainLoop()

    y por otro tengo que lanzar un proceso que tambien tiene un loop

    updater.start_polling()
    updater.idle()

    no se como hacerlo para que se puedan pasar variables y que quede limpio. Si me puedes aconsejar.

    Gracias.

  3. El 17/07/2018 encontré este post y me fue de completa utilidad para utilizar las librerías y entender las diferencias entre métodos.

  4. Desde hace mucho tiempo no me encontraba con un buen post de Python en español que estuviera bien explicado pero que fuera suficientemente detallado para programadores de un nivel mayor a «Principiante».

Deja una respuesta