subprocess inicia programas externos y conecta entrada, salida y código de retorno. Al cruzar la frontera con el sistema operativo debes controlar argumentos, tiempo y entorno.

import subprocess

resultado = subprocess.run(
    ["git", "rev-parse", "--short", "HEAD"],
    check=True,
    capture_output=True,
    text=True,
    timeout=10,
)

commit = resultado.stdout.strip()

La lista evita interpretación automática del shell. check=True genera excepción ante fallos, timeout limita la espera y text=True decodifica la salida.

No concatenes entrada del usuario con shell=True; los metacaracteres pueden inyectar comandos. Valida por allowlist y pasa elementos separados. Los batch de Windows tienen reglas específicas, por lo que debes revisar las notas oficiales.

Define cwd si usas rutas relativas, controla env y evita secretos en logs. Para salida grande, usa Popen.communicate() o archivos y evita lecturas que bloqueen pipes.

La guía de Bandit detecta patrones sospechosos, pero la revisión debe rastrear el origen de los argumentos.

La documentación oficial de subprocess, consultada el 22 de julio de 2026, recomienda run() y detalla timeout, pipes y seguridad. Prueba también error, timeout y ejecutable ausente.

Separar ejecutable y argumentos

Con shell=False, Python inicia el programa directamente. Cada elemento de la lista es un argumento, así que los espacios de un archivo no lo dividen y los metacaracteres no adquieren significado de shell. No uses command.split(): las reglas de comillas cambian por plataforma. Construye la lista con partes conocidas.

La separación no sustituye la validación. Un valor que comienza con - puede convertirse en opción, y una ruta sin límites puede exponer otro archivo. Mapea elecciones públicas a argumentos internos:

FORMATOS = {
    "corto": ["--format", "short"],
    "json": ["--format", "json"],
}

def generar_informe(formato: str) -> str:
    if formato not in FORMATOS:
        raise ValueError("formato no admitido")
    resultado = subprocess.run(
        ["/opt/acme/bin/report", *FORMATOS[formato]],
        check=True,
        capture_output=True,
        text=True,
        timeout=15,
    )
    return resultado.stdout

Una ruta absoluta evita depender de PATH. Si la búsqueda es intencional, usa shutil.which() y verifica el resultado.

Tratar los fallos esperados

check=True genera CalledProcessError para estado no cero. Un ejecutable ausente genera FileNotFoundError; un límite vencido genera TimeoutExpired:

try:
    completado = subprocess.run(
        ["git", "status", "--porcelain=v1"],
        check=True,
        capture_output=True,
        text=True,
        timeout=5,
    )
except subprocess.TimeoutExpired as exc:
    raise RuntimeError("git superó el tiempo") from exc
except subprocess.CalledProcessError as exc:
    raise RuntimeError(f"git terminó con estado {exc.returncode}") from exc
except FileNotFoundError as exc:
    raise RuntimeError("git no está instalado") from exc

No devuelvas stderr sin filtrar al navegador: puede contener rutas, entorno o tokens. Registra diagnóstico redactado bajo control de acceso y entrega un mensaje estable. Algunos programas usan estado no cero para “diferencias encontradas”; consulta su contrato antes de activar check=True.

Limitar tiempo, salida y entrada

timeout limita la espera, aunque crear el proceso puede no ser interrumpible en toda plataforma. Al vencer, run() mata al hijo, espera y genera TimeoutExpired. Prueba esto con la herramienta real, especialmente si inicia descendientes.

capture_output=True conserva ambos flujos en memoria. Sirve para respuestas pequeñas, no para copias de seguridad o vídeo. Redirige salida grande a un archivo ya abierto:

from pathlib import Path

destino = Path("/var/lib/acme/informe.json")
with destino.open("wb") as salida:
    subprocess.run(
        ["/opt/acme/bin/report", "--json"],
        stdout=salida,
        stderr=subprocess.PIPE,
        check=True,
        timeout=60,
    )

Con Popen, leer stdout mientras se ignora un stderr lleno puede bloquear. Prefiere communicate() salvo para streaming cuidadosamente probado. Nunca permitas entrada, duración o captura ilimitadas en una petición web.

Pasa contenido con input=, no en la línea de comandos visible en listados:

resultado = subprocess.run(
    ["/usr/bin/tool", "--read-stdin"],
    input=contenido,
    text=True,
    capture_output=True,
    check=True,
    timeout=10,
)

Para secretos, prefiere el mecanismo protegido de la herramienta. Las variables de entorno son menos visibles que los argumentos, pero no son una frontera universal.

Controlar directorio y entorno

Las rutas relativas dependen de cwd; el comportamiento heredado depende de env. Usa un directorio fiable y resuelto. No permitas que una entrada externa elija libremente el directorio.

Pasar env reemplaza todo el entorno del hijo. Parte de os.environ.copy() si necesita variables normales y sobrescribe solo las previstas. Para más aislamiento, crea un entorno mínimo. Elimina variables peligrosas de loaders o lenguajes al iniciar herramientas privilegiadas y no registres el mapa completo.

Usa encoding="utf-8" y errors="strict" si la herramienta garantiza UTF-8. Solo text=True usa la codificación regional, que puede variar. Conserva modo binario para bytes arbitrarios.

Reservar el shell para su sintaxis

Pipelines, redirección, comodines y comandos internos pueden requerir shell. Prefiere stdin, stdout y varios objetos Popen. Si el shell es indispensable, mantén constante el comando y evita que datos externos entren en él. shlex.quote() se dirige a shells POSIX y no sanitiza Windows de forma portátil.

Los batch de Windows pueden involucrar parsing del sistema incluso con shell=False. Sigue la nota oficial y prueba espacios y metacaracteres. No supongas que Linux y Windows se comportan igual.

Procesos largos y pruebas

Usa Popen para transmitir datos, enviar señales o gestionar al hijo. Empléalo como gestor de contexto, llama a communicate() para recogerlo y define cómo detener descendientes. No inicies trabajo separado desde una petición web sin una cola supervisada para ciclo de vida, reintentos, logs y límites.

Prueba éxito, estados documentados, ejecutable ausente, timeout, codificación inválida, stderr grande, nombres con espacios y argumentos de aspecto hostil. Comprueba que cada valor sigue siendo un argumento. Los mocks cubren ramas, pero una integración debe ejecutar la herramienta real.

El patrón seguro combina ejecutable fijo, lista explícita y validada, shell=False, tiempo y salida limitados, entorno deliberado y estado tratado. Así el proceso resulta predecible sin confundir separación de argumentos con validación semántica.

Revisión operativa

Antes del despliegue, comprueba permisos y la cuenta del sistema usada por el hijo. subprocess no crea un sandbox: el programa hereda las capacidades efectivas del padre salvo restricciones externas. Ejecuta la aplicación con privilegio mínimo, limita directorios escribibles y aplica controles del sistema para CPU, memoria y número de procesos al usar herramientas costosas.

Define el contrato de observabilidad. Registra nombre lógico de la operación, duración, estado y timeout, pero no toda la línea de comandos si puede contener datos sensibles. Un identificador de correlación relaciona el proceso con la petición. Las métricas permiten detectar que una actualización se volvió lenta sin exponer contenido.

Si muchos procesos iguales pueden ejecutarse a la vez, limita la concurrencia. El timeout individual no impide que cien hijos agoten el servidor. Una cola o semáforo ofrece presión de retorno y rechaza exceso de trabajo de forma predecible.

Al actualizar la herramienta externa, valida su versión y opciones. Un argumento antes aceptado puede cambiar de significado, y mensajes o codificación pueden variar. Fijar una versión compatible y ejecutar integraciones reduce sorpresas.

Revisa además los archivos temporales. Créelos en un directorio privado, evita nombres predecibles y elimina el contenido según la política de retención. No pases una ruta temporal aportada por el usuario sin comprobar que permanece dentro del directorio autorizado.

Documenta quién mantiene cada ejecutable y cómo se responde ante fallos repetidos. Una dependencia operativa sin propietario claro termina convirtiéndose en un riesgo de disponibilidad y seguridad.