CAP 14 · LEC 05·Bonus prácticos

subprocess y os: ejecutar comandos del sistema desde Python

subprocess.run() ejecuta comandos del sistema y captura su salida. os provee variables de entorno, rutas del sistema y señales. Juntos permiten automatizar tareas que normalmente harías en la terminal.

● INTERMEDIO8 min lectura3 ejerciciospor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

subprocess.run() básico

subprocess.run() es la función de alto nivel para ejecutar comandos externos. Espera a que termine y retorna un objeto CompletedProcess con el código de salida y la salida capturada.

import subprocess # Ejecutar un comando simple result = subprocess.run(["echo", "Hola desde subprocess"]) print(result.returncode) # 0 (éxito) # Capturar la salida con capture_output=True result = subprocess.run( ["python3", "--version"], capture_output=True, text=True # stdout/stderr como str, no bytes ) print(result.stdout.strip()) # Python 3.12.x # Capturar stdout y stderr por separado result = subprocess.run( ["ls", "-la", "/tmp"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) if result.returncode == 0: lineas = result.stdout.strip().split(" ") print(f"{len(lineas)} entradas en /tmp") else: print(f"Error: {result.stderr}") # Pasar input al proceso con stdin result = subprocess.run( ["sort"], input="banana manzana fresa ", capture_output=True, text=True ) print(result.stdout) # fresa manzana banana (ordenado)
Salida0 Python 3.12.3 12 entradas en /tmp banana fresa manzana

capture_output y check para manejo de errores

check=True lanza CalledProcessError si el comando falla (returncode != 0). Es mejor que verificar manualmente returncode cuando quieres que un fallo sea siempre un error.

import subprocess # check=True: lanza excepción si el comando falla try: result = subprocess.run( ["ls", "/directorio/que/no/existe"], capture_output=True, text=True, check=True # lanza CalledProcessError si returncode != 0 ) except subprocess.CalledProcessError as e: print(f"Comando falló con código {e.returncode}") print(f"stderr: {e.stderr.strip()}") # Función helper para ejecutar comandos de forma segura def run_command(cmd: list[str], cwd: str = None) -> str: """Ejecuta un comando y retorna su salida. Lanza si falla.""" result = subprocess.run( cmd, capture_output=True, text=True, check=True, cwd=cwd # directorio de trabajo ) return result.stdout.strip() # Ejecutar git log try: git_log = run_command(["git", "log", "--oneline", "-5"]) for linea in git_log.split(" "): print(linea) except subprocess.CalledProcessError: print("No es un repositorio git o git no está instalado") except FileNotFoundError: print("git no encontrado en el PATH") # timeout: matar el proceso si tarda demasiado try: result = subprocess.run( ["sleep", "10"], timeout=2, # segundos capture_output=True ) except subprocess.TimeoutExpired: print("El proceso tardó demasiado — cancelado")
SalidaComando falló con código 1 stderr: ls: /directorio/que/no/existe: No such file or directory abc1234 feat: add feature X def5678 fix: correct bug Y El proceso tardó demasiado — cancelado
Nunca uses shell=True con input del usuario

subprocess.run(cmd, shell=True) pasa el comando al shell del sistema — si cmd contiene input del usuario, es una vulnerabilidad de inyección de comandos. Pasa siempre una lista de strings en vez de un string con shell=True.

os.environ para variables de entorno

os.environ es un diccionario con todas las variables de entorno. os.environ.get() es la forma segura de leerlas con valor por defecto.

import os # Leer variables de entorno home = os.environ.get("HOME", "/tmp") path = os.environ.get("PATH", "") debug = os.environ.get("DEBUG", "false").lower() == "true" print(f"HOME: {home}") print(f"DEBUG activo: {debug}") # Leer con valor requerido (lanzar si no existe) def require_env(name: str) -> str: value = os.environ.get(name) if value is None: raise EnvironmentError(f"Variable de entorno requerida: {name}") return value try: db_url = require_env("DATABASE_URL") except EnvironmentError as e: print(f"Config incompleta: {e}") db_url = "sqlite:///local.db" # fallback local # Modificar el entorno para el proceso actual (no persiste) os.environ["APP_VERSION"] = "1.2.3" print(os.environ["APP_VERSION"]) # 1.2.3 # Pasar entorno personalizado a subprocess import subprocess env = {**os.environ, "MY_VAR": "valor_custom"} result = subprocess.run( ["python3", "-c", "import os; print(os.environ.get('MY_VAR'))"], capture_output=True, text=True, env=env, ) print(result.stdout.strip()) # valor_custom
SalidaHOME: /Users/usuario DEBUG activo: False Config incompleta: Variable de entorno requerida: DATABASE_URL 1.2.3 valor_custom

os y shutil para operaciones de archivos

os y shutil complementan a pathlib con operaciones que involucran al sistema operativo: obtener el directorio actual, listar archivos, copiar árboles de directorios.

import os import shutil import tempfile from pathlib import Path # Directorio actual print(os.getcwd()) # Listar archivos (os.listdir vs pathlib.iterdir) entries = os.listdir("/tmp") print(f"Entradas en /tmp: {len(entries)}") # os.walk: recorrer árbol de directorios base = Path(tempfile.mkdtemp()) (base / "a").mkdir() (base / "b").mkdir() (base / "a" / "file1.txt").write_text("uno") (base / "b" / "file2.txt").write_text("dos") for root, dirs, files in os.walk(base): nivel = Path(root).relative_to(base) print(f" {nivel}/ → archivos: {files}") # shutil: operaciones de alto nivel con archivos # copiar un archivo src = base / "a" / "file1.txt" dst = base / "file1_copia.txt" shutil.copy2(src, dst) # copy2 preserva metadatos print(f"Copiado: {dst.exists()}") # copiar árbol entero destino_arbol = Path(tempfile.mkdtemp()) shutil.copytree(base / "a", destino_arbol / "a_backup") print(f"Árbol copiado: {(destino_arbol / 'a_backup').is_dir()}") # mover (rename entre discos — shutil.move funciona siempre) shutil.move(str(base / "b" / "file2.txt"), str(base / "file2_movido.txt")) # obtener espacio en disco total, used, free = shutil.disk_usage("/") print(f"Disco /: {free // (1024**3)} GB libres") shutil.rmtree(base) shutil.rmtree(destino_arbol)
Salida/Users/usuario/proyecto Entradas en /tmp: 23 ./ → archivos: [] a/ → archivos: ['file1.txt'] b/ → archivos: ['file2.txt'] Copiado: True Árbol copiado: True Disco /: 120 GB libres

Practica