Python

Python

Tutorial Python: Módulo os

Domina el módulo os en Python para gestionar archivos, rutas, procesos y variables de entorno de forma multiplataforma y eficiente.

Aprende Python y certifícate

Interacción con el sistema operativo

El módulo os de Python actúa como un puente entre tu código y el sistema operativo subyacente, permitiéndote realizar operaciones que normalmente requerirían comandos específicos de shell. Esta capacidad es fundamental para crear aplicaciones que necesiten interactuar con el entorno del sistema.

La interacción con el sistema operativo mediante el módulo os te permite realizar operaciones como obtener información del sistema, ejecutar comandos y gestionar procesos de manera independiente de la plataforma. Esto significa que puedes escribir código que funcione tanto en Windows como en sistemas Unix/Linux con mínimas modificaciones.

Importación del módulo

Para comenzar a utilizar el módulo os, primero debes importarlo:

import os

Información del sistema

Una de las funcionalidades básicas es obtener información sobre el sistema operativo en el que se está ejecutando tu programa:

# Identificar el sistema operativo
print(os.name)  # Devuelve 'posix' (Unix/Linux/Mac) o 'nt' (Windows)

# Obtener información más detallada del sistema
if hasattr(os, 'uname'):  # Solo disponible en sistemas tipo Unix
    print(os.uname())

El atributo os.name proporciona una identificación básica del tipo de sistema operativo, mientras que os.uname() ofrece información más detallada en sistemas Unix.

Acceso a variables del entorno

El módulo os permite acceder y modificar las variables de entorno del sistema:

# Obtener el valor de una variable de entorno
home_dir = os.environ.get('HOME')  # En Unix/Linux/Mac
# O en Windows:
user_profile = os.environ.get('USERPROFILE')

# Establecer una nueva variable de entorno
os.environ['MI_VARIABLE'] = 'valor'

# Listar todas las variables de entorno
for key, value in os.environ.items():
    print(f"{key}: {value}")

El diccionario os.environ te permite acceder a todas las variables de entorno del sistema, lo que resulta útil para configurar aplicaciones basadas en el entorno de ejecución.

Operaciones con directorios

El módulo os proporciona funciones para trabajar con directorios:

# Obtener el directorio de trabajo actual
directorio_actual = os.getcwd()
print(f"Estás en: {directorio_actual}")

# Cambiar el directorio de trabajo
os.chdir('/ruta/a/otro/directorio')

# Crear un nuevo directorio
os.mkdir('nuevo_directorio')

# Crear directorios anidados (equivalente a mkdir -p)
os.makedirs('directorio/subdirectorio/otro', exist_ok=True)

# Eliminar un directorio
os.rmdir('directorio_vacio')  # Solo funciona si está vacío

# Eliminar directorios recursivamente
import shutil  # Módulo complementario para operaciones de archivos
shutil.rmtree('directorio_con_contenido')  # ¡Cuidado! Elimina todo el contenido

La función os.getcwd() es especialmente útil para determinar la ubicación desde la que se está ejecutando tu script, mientras que os.chdir() te permite navegar por el sistema de archivos.

Listado de archivos y directorios

Para explorar el contenido de un directorio:

# Listar archivos y directorios
contenido = os.listdir('.')  # '.' representa el directorio actual
print(f"Contenido del directorio: {contenido}")

# Filtrar solo archivos
archivos = [f for f in contenido if os.path.isfile(f)]
print(f"Archivos: {archivos}")

# Filtrar solo directorios
directorios = [d for d in contenido if os.path.isdir(d)]
print(f"Directorios: {directorios}")

# Listar con información detallada
for elemento in contenido:
    tipo = "Directorio" if os.path.isdir(elemento) else "Archivo"
    tamaño = os.path.getsize(elemento)  # Tamaño en bytes
    print(f"{elemento} - {tipo} - {tamaño} bytes")

La combinación de os.listdir() con las funciones de os.path te permite analizar y filtrar el contenido de directorios de manera eficiente.

Ejecución de comandos del sistema

El módulo os permite ejecutar comandos del sistema operativo:

# Ejecutar un comando simple
exit_code = os.system('echo "Hola desde el sistema"')
print(f"Código de salida: {exit_code}")

# Para capturar la salida del comando (recomendado en Python moderno)
import subprocess
resultado = subprocess.run(['ls', '-la'], capture_output=True, text=True)
print(f"Salida del comando:\n{resultado.stdout}")

Aunque os.system() está disponible, se recomienda usar el módulo subprocess para un control más preciso sobre la ejecución de comandos externos.

Información de archivos

Puedes obtener metadatos de archivos:

# Comprobar si un archivo existe
if os.path.exists('archivo.txt'):
    # Obtener el tamaño del archivo
    tamaño = os.path.getsize('archivo.txt')
    print(f"Tamaño: {tamaño} bytes")
    
    # Obtener la última modificación (timestamp)
    tiempo_mod = os.path.getmtime('archivo.txt')
    
    # Convertir timestamp a fecha legible
    import datetime
    fecha_mod = datetime.datetime.fromtimestamp(tiempo_mod)
    print(f"Última modificación: {fecha_mod}")
    
    # Comprobar permisos
    es_legible = os.access('archivo.txt', os.R_OK)
    es_escribible = os.access('archivo.txt', os.W_OK)
    es_ejecutable = os.access('archivo.txt', os.X_OK)
    
    print(f"Permisos - Lectura: {es_legible}, Escritura: {es_escribible}, Ejecución: {es_ejecutable}")

Las funciones de os.path y os.access() te permiten verificar la existencia y permisos de archivos antes de intentar operaciones que podrían fallar.

Operaciones con archivos

Aunque Python tiene funciones integradas para manejar archivos, el módulo os proporciona operaciones a nivel del sistema:

# Renombrar un archivo
os.rename('archivo_viejo.txt', 'archivo_nuevo.txt')

# Mover un archivo
os.replace('archivo.txt', 'subdirectorio/archivo.txt')

# Eliminar un archivo
os.remove('archivo_a_eliminar.txt')

# Crear un enlace simbólico (solo sistemas Unix/Linux/Mac)
if os.name == 'posix':
    os.symlink('archivo_original.txt', 'enlace.txt')

Estas operaciones te permiten manipular archivos a nivel del sistema de archivos sin necesidad de abrirlos para lectura o escritura.

Ejemplo práctico: Análisis de espacio en disco

Un caso de uso común es analizar el espacio utilizado en un directorio:

def analizar_directorio(ruta):
    """Analiza el espacio usado por un directorio y sus subdirectorios."""
    tamaño_total = 0
    archivos_count = 0
    dirs_count = 0
    
    for raiz, dirs, archivos in os.walk(ruta):
        dirs_count += len(dirs)
        archivos_count += len(archivos)
        
        for archivo in archivos:
            ruta_completa = os.path.join(raiz, archivo)
            # Ignorar enlaces simbólicos para evitar conteo duplicado
            if not os.path.islink(ruta_completa):
                tamaño_total += os.path.getsize(ruta_completa)
    
    # Convertir a MB para mejor legibilidad
    tamaño_mb = tamaño_total / (1024 * 1024)
    
    return {
        'tamaño_mb': round(tamaño_mb, 2),
        'archivos': archivos_count,
        'directorios': dirs_count
    }

# Uso
resultado = analizar_directorio('.')
print(f"Espacio utilizado: {resultado['tamaño_mb']} MB")
print(f"Archivos: {resultado['archivos']}")
print(f"Directorios: {resultado['directorios']}")

La función os.walk() es extremadamente útil para recorrer recursivamente una estructura de directorios, permitiéndote procesar todos los archivos y subdirectorios.

Identificación de archivos especiales

En sistemas Unix/Linux, puedes identificar tipos especiales de archivos:

# Solo en sistemas Unix/Linux
if os.name == 'posix':
    # Comprobar si es un dispositivo
    es_dispositivo = os.path.isblk('/dev/sda') or os.path.ischr('/dev/tty')
    
    # Comprobar si es un socket
    es_socket = os.path.issock('/var/run/docker.sock')
    
    # Comprobar si es un FIFO (named pipe)
    es_fifo = os.path.isfifo('/tmp/mi_pipe')

Estas funciones te permiten trabajar con archivos especiales del sistema, lo que es útil para aplicaciones que necesitan interactuar con dispositivos o mecanismos de comunicación entre procesos.

El módulo os proporciona una interfaz potente para interactuar con el sistema operativo, permitiéndote crear aplicaciones que puedan gestionar archivos, directorios y procesos de manera eficiente y compatible con múltiples plataformas.

Manipulación de rutas

El manejo eficiente de rutas de archivos y directorios es una tarea fundamental en cualquier aplicación que interactúe con el sistema de archivos. Python, a través del submódulo os.path y las funciones relacionadas del módulo os, proporciona herramientas robustas para manipular rutas de manera independiente de la plataforma.

La manipulación de rutas en Python debe considerar las diferencias entre sistemas operativos. Por ejemplo, Windows utiliza la barra invertida (\) como separador de directorios, mientras que Unix/Linux/macOS utilizan la barra normal (/). El módulo os.path abstrae estas diferencias, permitiéndote escribir código portable entre diferentes sistemas.

Construcción de rutas

Una de las operaciones más comunes es la construcción de rutas a partir de componentes:

import os

# Unir componentes de ruta (independiente del sistema operativo)
ruta = os.path.join('directorio', 'subdirectorio', 'archivo.txt')
print(ruta)  # En Windows: 'directorio\subdirectorio\archivo.txt'
             # En Unix/Linux: 'directorio/subdirectorio/archivo.txt'

La función os.path.join() es crucial para crear rutas compatibles con el sistema operativo actual, ya que utiliza automáticamente el separador correcto.

Para construir rutas más complejas, puedes combinar varios componentes:

# Construir ruta a partir del directorio base
directorio_base = os.path.expanduser('~')  # Expande ~ a la ruta del home del usuario
ruta_documentos = os.path.join(directorio_base, 'Documentos', 'proyecto')
print(f"Ruta a documentos: {ruta_documentos}")

# Construir ruta relativa al directorio actual
ruta_relativa = os.path.join('..', 'datos', 'archivo.csv')
print(f"Ruta relativa: {ruta_relativa}")

La función os.path.expanduser() es especialmente útil para acceder al directorio home del usuario de manera independiente de la plataforma.

Normalización y resolución de rutas

Las rutas pueden contener elementos como . (directorio actual), .. (directorio padre) o múltiples separadores que es conveniente normalizar:

# Normalizar una ruta (eliminar elementos redundantes)
ruta_desordenada = 'proyecto/./subdir/../datos/archivo.txt'
ruta_normalizada = os.path.normpath(ruta_desordenada)
print(f"Ruta normalizada: {ruta_normalizada}")  # 'proyecto/datos/archivo.txt'

# Convertir ruta relativa a absoluta
ruta_relativa = 'datos/archivo.csv'
ruta_absoluta = os.path.abspath(ruta_relativa)
print(f"Ruta absoluta: {ruta_absoluta}")

# Resolver enlaces simbólicos (en sistemas Unix/Linux/Mac)
if os.name == 'posix':
    ruta_real = os.path.realpath('enlace_simbolico.txt')
    print(f"Ruta real: {ruta_real}")

La función os.path.normpath() simplifica las rutas eliminando elementos redundantes, mientras que os.path.abspath() convierte rutas relativas en absolutas basándose en el directorio de trabajo actual.

Descomposición de rutas

A menudo necesitamos extraer componentes específicos de una ruta:

ruta_completa = '/home/usuario/proyectos/app/datos/archivo.csv'

# Obtener el directorio y el nombre del archivo
directorio, archivo = os.path.split(ruta_completa)
print(f"Directorio: {directorio}")  # '/home/usuario/proyectos/app/datos'
print(f"Archivo: {archivo}")        # 'archivo.csv'

# Obtener el nombre base y la extensión
nombre_base, extension = os.path.splitext(archivo)
print(f"Nombre base: {nombre_base}")  # 'archivo'
print(f"Extensión: {extension}")      # '.csv'

# Obtener solo el nombre del directorio final
directorio_final = os.path.basename(directorio)
print(f"Directorio final: {directorio_final}")  # 'datos'

# Obtener el directorio padre
directorio_padre = os.path.dirname(directorio)
print(f"Directorio padre: {directorio_padre}")  # '/home/usuario/proyectos/app'

Estas funciones de descomposición son esenciales para procesar rutas y extraer información relevante sin tener que recurrir a manipulaciones de cadenas que podrían no ser compatibles entre plataformas.

Validación y comprobación de rutas

Antes de realizar operaciones con archivos, es importante verificar la existencia y naturaleza de las rutas:

ruta_a_verificar = '/ruta/a/algo'

# Comprobar si la ruta existe
existe = os.path.exists(ruta_a_verificar)
print(f"¿Existe la ruta? {existe}")

# Comprobar si es un archivo
es_archivo = os.path.isfile(ruta_a_verificar)
print(f"¿Es un archivo? {es_archivo}")

# Comprobar si es un directorio
es_directorio = os.path.isdir(ruta_a_verificar)
print(f"¿Es un directorio? {es_directorio}")

# Comprobar si es una ruta absoluta
es_absoluta = os.path.isabs(ruta_a_verificar)
print(f"¿Es una ruta absoluta? {es_absoluta}")

# Comprobar si dos rutas se refieren al mismo archivo
mismo_archivo = os.path.samefile('/ruta/a/archivo', './archivo')
print(f"¿Son el mismo archivo? {mismo_archivo}")

Estas funciones de validación te permiten tomar decisiones basadas en la naturaleza de las rutas antes de intentar operaciones que podrían fallar.

Rutas relativas entre directorios

A veces necesitamos calcular la ruta relativa entre dos directorios:

# Calcular la ruta relativa desde un directorio a otro
desde_dir = '/home/usuario/proyectos'
hasta_dir = '/home/usuario/documentos/datos'

ruta_relativa = os.path.relpath(hasta_dir, desde_dir)
print(f"Ruta relativa: {ruta_relativa}")  # '../documentos/datos'

La función os.path.relpath() es útil para crear referencias relativas entre directorios, lo que puede ser importante en aplicaciones que necesitan mantener relaciones entre diferentes ubicaciones del sistema de archivos.

Manipulación de rutas con pathlib (Python 3.4+)

A partir de Python 3.4, la biblioteca estándar incluye el módulo pathlib, que ofrece una interfaz orientada a objetos para manipular rutas:

from pathlib import Path

# Crear un objeto Path
ruta = Path('/home/usuario/documentos/archivo.txt')

# Acceder a componentes
print(f"Directorio padre: {ruta.parent}")
print(f"Nombre del archivo: {ruta.name}")
print(f"Extensión: {ruta.suffix}")
print(f"Nombre sin extensión: {ruta.stem}")

# Unir rutas
nueva_ruta = ruta.parent / 'datos' / 'nuevo.csv'
print(f"Nueva ruta: {nueva_ruta}")

# Resolver rutas relativas
ruta_actual = Path('.')
ruta_absoluta = ruta_actual.absolute()
print(f"Ruta absoluta: {ruta_absoluta}")

# Iterar sobre archivos en un directorio
for archivo in Path('.').glob('*.py'):
    print(f"Archivo Python: {archivo}")

El módulo pathlib proporciona una interfaz más intuitiva y moderna para trabajar con rutas, permitiendo operaciones como la concatenación de rutas mediante el operador / y métodos para buscar archivos con patrones.

Ejemplo práctico: Organización de archivos por extensión

Un caso de uso común es organizar archivos en directorios según su extensión:

def organizar_por_extension(directorio_origen):
    """Organiza los archivos de un directorio en subdirectorios según su extensión."""
    # Asegurar que el directorio existe
    if not os.path.exists(directorio_origen) or not os.path.isdir(directorio_origen):
        print(f"El directorio {directorio_origen} no existe o no es un directorio")
        return
    
    # Procesar cada archivo en el directorio
    for archivo in os.listdir(directorio_origen):
        ruta_completa = os.path.join(directorio_origen, archivo)
        
        # Ignorar directorios
        if os.path.isdir(ruta_completa):
            continue
        
        # Obtener la extensión (sin el punto)
        _, extension = os.path.splitext(archivo)
        extension = extension[1:].lower()  # Eliminar el punto y convertir a minúsculas
        
        if not extension:
            extension = 'sin_extension'
        
        # Crear el directorio de destino si no existe
        directorio_destino = os.path.join(directorio_origen, extension)
        if not os.path.exists(directorio_destino):
            os.mkdir(directorio_destino)
        
        # Mover el archivo
        destino = os.path.join(directorio_destino, archivo)
        os.rename(ruta_completa, destino)
        print(f"Movido: {archivo} -> {extension}/{archivo}")

# Uso
organizar_por_extension('/ruta/a/mis/descargas')

Este ejemplo demuestra cómo combinar varias funciones de manipulación de rutas para implementar una utilidad práctica que organiza archivos según su extensión.

Compatibilidad entre sistemas operativos

Al trabajar con rutas en diferentes sistemas operativos, es importante considerar algunas diferencias:

# Determinar el separador de rutas del sistema actual
print(f"Separador de rutas: {os.path.sep}")  # '\' en Windows, '/' en Unix/Linux

# Determinar el separador de rutas en listas de rutas (PATH)
print(f"Separador de PATH: {os.path.pathsep}")  # ';' en Windows, ':' en Unix/Linux

# Convertir rutas entre formatos
ruta_windows = r'C:\Users\usuario\documentos\archivo.txt'
ruta_unix = ruta_windows.replace('\\', '/')
print(f"Ruta en formato Unix: {ruta_unix}")

# Usar os.path.normcase para normalizar según el sistema
ruta_normalizada = os.path.normcase(ruta_windows)
print(f"Ruta normalizada para el sistema actual: {ruta_normalizada}")

Conocer estas diferencias te permite escribir código que funcione correctamente en múltiples plataformas, evitando problemas comunes como rutas mal formadas o incompatibles.

La manipulación eficiente de rutas es fundamental para crear aplicaciones robustas que interactúen con el sistema de archivos. El módulo os.path y la biblioteca pathlib proporcionan las herramientas necesarias para trabajar con rutas de manera segura y compatible entre diferentes sistemas operativos.

Manejo de procesos

El módulo os de Python no solo permite interactuar con archivos y directorios, sino que también proporciona herramientas para gestionar procesos del sistema operativo. Esta capacidad es fundamental cuando necesitamos ejecutar programas externos, controlar su ejecución o comunicarnos con ellos desde nuestras aplicaciones Python.

La gestión de procesos varía significativamente entre sistemas operativos, pero el módulo os abstrae muchas de estas diferencias, permitiéndonos escribir código que funcione en múltiples plataformas.

Creación de procesos

La forma más básica de crear un proceso es mediante la función os.system(), que ejecuta un comando como si lo hubiéramos escrito en la terminal:

import os

# Ejecutar un comando simple
estado = os.system('echo "Ejecutando un comando externo"')
print(f"Estado de salida: {estado}")

Sin embargo, os.system() tiene limitaciones importantes: no permite capturar fácilmente la salida del comando y ofrece poco control sobre el proceso. Por eso, Python proporciona alternativas más potentes.

La función os.spawn

Para un mayor control sobre la creación de procesos, Python ofrece la familia de funciones os.spawn*():

# En sistemas Windows
if os.name == 'nt':
    # Ejecutar el Bloc de notas de Windows
    pid = os.spawnl(os.P_NOWAIT, 'notepad.exe', 'notepad.exe', 'documento.txt')
    print(f"Proceso iniciado con PID: {pid}")

Las funciones spawn tienen varias variantes que permiten controlar si el proceso padre espera (P_WAIT) o no (P_NOWAIT) a que el hijo termine, y cómo se pasan los argumentos.

Ejecutar procesos con subprocess

Aunque el módulo os ofrece funcionalidades básicas para manejar procesos, el módulo subprocess proporciona una interfaz más completa y flexible:

import subprocess

# Ejecutar un comando y capturar su salida
resultado = subprocess.run(['ls', '-la'], capture_output=True, text=True)

if resultado.returncode == 0:
    print("Comando ejecutado con éxito")
    print(f"Salida:\n{resultado.stdout}")
else:
    print(f"Error al ejecutar el comando: {resultado.stderr}")

El método subprocess.run() es la forma recomendada para ejecutar comandos externos en Python moderno. Permite:

  • Capturar la salida estándar y error estándar
  • Proporcionar entrada al proceso
  • Establecer variables de entorno
  • Controlar el directorio de trabajo

Comunicación con procesos

Para casos más complejos donde necesitamos comunicación bidireccional con un proceso, podemos usar subprocess.Popen:

# Iniciar un proceso con tuberías para entrada/salida
proceso = subprocess.Popen(
    ['python', '-c', 'import sys; print(sys.stdin.read().upper())'],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    text=True
)

# Enviar datos al proceso
texto_entrada = "Hola, proceso hijo!\n"
stdout_data, _ = proceso.communicate(texto_entrada)

print(f"Respuesta del proceso: {stdout_data}")

El método communicate() permite enviar datos a la entrada estándar del proceso y recibir su salida, facilitando la comunicación bidireccional.

Obtener información de procesos

El módulo os proporciona funciones para obtener información sobre el proceso actual:

# Obtener el ID del proceso actual
pid_actual = os.getpid()
print(f"PID del proceso actual: {pid_actual}")

# Obtener el ID del proceso padre
ppid = os.getppid()
print(f"PID del proceso padre: {ppid}")

# Obtener el usuario efectivo (solo en sistemas Unix)
if os.name == 'posix':
    uid = os.geteuid()
    print(f"UID efectivo: {uid}")

Esta información es útil para depuración o para implementar lógica que dependa de la identidad del proceso.

Control de procesos

Python permite controlar procesos en ejecución, aunque algunas funcionalidades están limitadas a sistemas tipo Unix:

# Enviar una señal a un proceso (solo en sistemas Unix)
if os.name == 'posix':
    import signal
    
    # Definir un manejador de señal
    def manejador_señal(signum, frame):
        print(f"Recibida señal: {signum}")
    
    # Registrar el manejador para SIGINT (Ctrl+C)
    signal.signal(signal.SIGINT, manejador_señal)
    
    print("Presiona Ctrl+C para enviar SIGINT...")
    
    # Esperar a recibir la señal
    try:
        signal.pause()
    except KeyboardInterrupt:
        print("Programa interrumpido")

Las señales son un mecanismo de comunicación entre procesos en sistemas Unix que permiten notificar eventos o solicitar acciones específicas.

Terminación de procesos

Para terminar un proceso, podemos usar os.kill() en sistemas Unix:

# Terminar un proceso (solo en sistemas Unix)
if os.name == 'posix':
    import signal
    
    # Crear un proceso hijo
    pid = os.fork()
    
    if pid == 0:  # Proceso hijo
        print(f"Proceso hijo iniciado con PID: {os.getpid()}")
        # Simular trabajo
        import time
        time.sleep(10)
    else:  # Proceso padre
        print(f"Proceso padre. Hijo PID: {pid}")
        time.sleep(2)  # Esperar un poco
        
        # Terminar el proceso hijo
        print(f"Terminando el proceso hijo {pid}")
        os.kill(pid, signal.SIGTERM)
        
        # Esperar a que el hijo termine
        _, estado = os.waitpid(pid, 0)
        print(f"Proceso hijo terminado con estado: {estado}")

La función os.waitpid() permite al proceso padre esperar a que un proceso hijo específico termine y obtener su estado de salida.

Ejecución en segundo plano

Para ejecutar procesos en segundo plano que continúen incluso después de que el programa Python termine:

# Iniciar un proceso desvinculado (solo en sistemas Unix)
if os.name == 'posix':
    # Usar nohup para que el proceso continúe después de cerrar la terminal
    subprocess.Popen(['nohup', 'python', '-c', 'import time; open("background.log", "w").write("Ejecutando..."); time.sleep(60)'],
                     stdout=subprocess.DEVNULL,
                     stderr=subprocess.DEVNULL,
                     start_new_session=True)
    
    print("Proceso iniciado en segundo plano")

El parámetro start_new_session=True crea un nuevo grupo de procesos, lo que permite que el proceso hijo se ejecute independientemente del padre.

Monitoreo de procesos hijos

En aplicaciones más complejas, podemos necesitar monitorear varios procesos hijos:

def ejecutar_tareas_paralelas(comandos):
    """Ejecuta múltiples comandos en paralelo y espera a que todos terminen."""
    procesos = []
    
    # Iniciar todos los procesos
    for cmd in comandos:
        proceso = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        procesos.append((proceso, cmd))
    
    # Esperar y recopilar resultados
    resultados = []
    for proceso, cmd in procesos:
        stdout, stderr = proceso.communicate()
        resultados.append({
            'comando': cmd,
            'exitoso': proceso.returncode == 0,
            'salida': stdout.decode('utf-8'),
            'error': stderr.decode('utf-8')
        })
    
    return resultados

# Ejemplo de uso
tareas = [
    ['echo', 'Tarea 1'],
    ['ls', '-la'],
    ['echo', 'Tarea 3']
]

resultados = ejecutar_tareas_paralelas(tareas)
for res in resultados:
    estado = "✓" if res['exitoso'] else "✗"
    print(f"{estado} {' '.join(res['comando'])}")
    if res['salida']:
        print(f"  Salida: {res['salida'].strip()}")

Esta técnica es útil para paralelizar tareas y aprovechar mejor los recursos del sistema, especialmente en aplicaciones que necesitan procesar grandes volúmenes de datos.

Ejemplo práctico: Supervisor de procesos

Un caso de uso común es crear un supervisor que reinicie procesos si fallan:

def supervisor(comando, max_intentos=3):
    """Supervisa un proceso y lo reinicia si falla."""
    intentos = 0
    
    while intentos < max_intentos:
        print(f"Iniciando proceso (intento {intentos + 1}/{max_intentos})...")
        
        # Iniciar el proceso
        inicio = time.time()
        proceso = subprocess.Popen(comando)
        
        # Esperar a que termine
        codigo_salida = proceso.wait()
        duracion = time.time() - inicio
        
        if codigo_salida == 0:
            print(f"Proceso completado correctamente después de {duracion:.2f} segundos")
            return True
        else:
            print(f"Proceso falló con código {codigo_salida} después de {duracion:.2f} segundos")
            intentos += 1
            time.sleep(2)  # Esperar antes de reintentar
    
    print(f"El proceso falló después de {max_intentos} intentos")
    return False

# Ejemplo de uso
import time
supervisor(['python', '-c', 'import random, time; time.sleep(2); exit(0 if random.random() > 0.7 else 1)'])

Este supervisor es útil para aplicaciones críticas que deben mantenerse en funcionamiento a pesar de fallos ocasionales.

Consideraciones de seguridad

Al ejecutar comandos externos, es importante tener en cuenta la seguridad:

# INSEGURO: Vulnerabilidad de inyección de comandos
usuario_input = input("Ingrese un archivo a buscar: ")
os.system(f"find / -name {usuario_input}")  # ¡PELIGROSO!

# SEGURO: Usar subprocess con argumentos separados
archivo = input("Ingrese un archivo a buscar: ")
subprocess.run(["find", "/", "-name", archivo])

Siempre que sea posible, usa subprocess.run() con una lista de argumentos en lugar de construir cadenas de comandos, para evitar vulnerabilidades de inyección.

El manejo de procesos en Python a través del módulo os y subprocess proporciona herramientas potentes para interactuar con el sistema operativo, permitiendo crear aplicaciones que puedan ejecutar y controlar otros programas de manera eficiente y segura.

Variables de entorno

Las variables de entorno son valores dinámicos que afectan el comportamiento de los procesos en ejecución en un sistema operativo. Funcionan como un mecanismo de configuración global que permite a las aplicaciones obtener información del entorno en el que se ejecutan sin necesidad de codificarla directamente en el programa.

En Python, el módulo os proporciona una interfaz completa para trabajar con estas variables, permitiéndote acceder, modificar y gestionar el entorno de ejecución de tus aplicaciones de manera eficiente.

Acceso a variables de entorno

El diccionario os.environ es la principal herramienta para acceder a las variables de entorno del sistema:

import os

# Listar todas las variables de entorno disponibles
for clave, valor in os.environ.items():
    print(f"{clave}: {valor}")

Para acceder a una variable específica, puedes usar la sintaxis de diccionario:

# Acceder a variables de entorno comunes
python_path = os.environ.get('PYTHONPATH')
usuario = os.environ.get('USER')  # En Unix/Linux
# o en Windows:
usuario_windows = os.environ.get('USERNAME')

print(f"Usuario actual: {usuario or usuario_windows}")

El método get() es preferible a la indexación directa (os.environ['VARIABLE']), ya que devuelve None si la variable no existe en lugar de lanzar una excepción KeyError.

Modificación de variables de entorno

Puedes modificar variables de entorno para el proceso actual y sus procesos hijos:

# Establecer una nueva variable de entorno
os.environ['APP_MODE'] = 'development'

# Modificar una variable existente
os.environ['PATH'] = f"{os.environ.get('PATH', '')}:/ruta/adicional"

# Eliminar una variable de entorno
if 'TEMPORAL_VAR' in os.environ:
    del os.environ['TEMPORAL_VAR']

Es importante entender que estos cambios solo afectan al proceso actual y sus procesos hijos. No modifican permanentemente las variables de entorno del sistema operativo.

Uso de variables de entorno para configuración

Una práctica común es utilizar variables de entorno para configurar aplicaciones:

# Configuración basada en variables de entorno
def obtener_configuracion():
    """Obtiene la configuración de la aplicación desde variables de entorno."""
    config = {
        'debug': os.environ.get('APP_DEBUG', 'False').lower() in ('true', '1', 't'),
        'host': os.environ.get('APP_HOST', 'localhost'),
        'port': int(os.environ.get('APP_PORT', '8000')),
        'db_url': os.environ.get('DATABASE_URL', 'sqlite:///app.db'),
        'log_level': os.environ.get('LOG_LEVEL', 'INFO').upper(),
    }
    return config

# Uso
config = obtener_configuracion()
print(f"Ejecutando en modo debug: {config['debug']}")
print(f"Servidor: {config['host']}:{config['port']}")

Este enfoque permite cambiar el comportamiento de la aplicación sin modificar el código, lo que es especialmente útil en entornos de contenedores o despliegues en la nube.

Variables de entorno temporales para subprocesos

Cuando ejecutas un subproceso, puedes proporcionarle un entorno específico:

import subprocess

# Crear un entorno personalizado para un subproceso
env_personalizado = os.environ.copy()  # Copia el entorno actual
env_personalizado['DEBUG'] = '1'       # Añade o modifica variables

# Ejecutar un proceso con el entorno personalizado
resultado = subprocess.run(
    ['python', '-c', 'import os; print(f"Debug: {os.environ.get(\'DEBUG\')}")'],
    env=env_personalizado,
    text=True,
    capture_output=True
)

print(f"Salida del subproceso: {resultado.stdout.strip()}")

La función os.environ.copy() es crucial aquí, ya que crea una copia del entorno actual que puedes modificar sin afectar al proceso principal.

Carga de variables desde archivos .env

Un patrón común en el desarrollo moderno es almacenar configuraciones en archivos .env:

def cargar_env(ruta_archivo='.env'):
    """Carga variables de entorno desde un archivo .env"""
    if not os.path.exists(ruta_archivo):
        return False
    
    with open(ruta_archivo, 'r') as archivo:
        for linea in archivo:
            linea = linea.strip()
            if not linea or linea.startswith('#'):
                continue
                
            clave, valor = linea.split('=', 1)
            os.environ[clave.strip()] = valor.strip()
    
    return True

# Uso
if cargar_env():
    print("Variables de entorno cargadas desde .env")
else:
    print("Archivo .env no encontrado, usando valores predeterminados")

Aunque esta implementación básica funciona, para proyectos reales se recomienda usar bibliotecas como python-dotenv o python-decouple que manejan casos más complejos como comillas, espacios y valores multilínea.

Expansión de variables en rutas

Las variables de entorno son útiles para construir rutas dinámicas:

# Expandir variables de entorno en rutas
ruta_con_variables = '$HOME/proyectos/${APP_NAME}/datos'

# Expandir las variables en la ruta
ruta_expandida = os.path.expandvars(ruta_con_variables)
print(f"Ruta expandida: {ruta_expandida}")

La función os.path.expandvars() reemplaza referencias a variables de entorno (con formato $VARIABLE o ${VARIABLE}) con sus valores correspondientes.

Gestión de variables de entorno en diferentes plataformas

Las variables de entorno pueden variar entre sistemas operativos:

# Detectar el directorio home del usuario de forma multiplataforma
home_dir = os.environ.get('HOME')  # Unix/Linux/Mac

if home_dir is None:  # En Windows
    home_dir = os.environ.get('USERPROFILE')
    
print(f"Directorio home: {home_dir}")

# Alternativa usando os.path.expanduser
home_dir_alt = os.path.expanduser('~')
print(f"Directorio home (alternativa): {home_dir_alt}")

La función os.path.expanduser('~') es una forma multiplataforma de obtener el directorio home del usuario, independientemente del sistema operativo.

Variables de entorno para seguridad

Las variables de entorno son ideales para almacenar información sensible:

# Obtener credenciales desde variables de entorno
def obtener_credenciales():
    """Obtiene credenciales de API desde variables de entorno."""
    api_key = os.environ.get('API_KEY')
    api_secret = os.environ.get('API_SECRET')
    
    if not api_key or not api_secret:
        raise ValueError("Credenciales de API no configuradas en variables de entorno")
    
    return {
        'api_key': api_key,
        'api_secret': api_secret
    }

# Uso con manejo de errores
try:
    credenciales = obtener_credenciales()
    print("Credenciales obtenidas correctamente")
except ValueError as e:
    print(f"Error: {e}")
    print("Configure API_KEY y API_SECRET en las variables de entorno")

Este enfoque evita incluir información sensible directamente en el código fuente, reduciendo riesgos de seguridad como la exposición accidental de credenciales en repositorios de código.

Ejemplo práctico: Configuración basada en entorno

Un patrón común es tener diferentes configuraciones según el entorno de ejecución:

def configurar_app():
    """Configura la aplicación según el entorno."""
    # Determinar el entorno
    entorno = os.environ.get('APP_ENV', 'development').lower()
    
    # Configuración base
    config = {
        'debug': False,
        'timeout': 30,
        'max_connections': 100,
        'log_level': 'INFO'
    }
    
    # Ajustar según el entorno
    if entorno == 'development':
        config.update({
            'debug': True,
            'log_level': 'DEBUG',
            'db_url': 'sqlite:///dev.db'
        })
    elif entorno == 'testing':
        config.update({
            'debug': True,
            'db_url': 'sqlite:///:memory:',
            'timeout': 5
        })
    elif entorno == 'production':
        # En producción, todas las configuraciones críticas deben venir de variables de entorno
        config.update({
            'db_url': os.environ.get('DATABASE_URL'),
            'secret_key': os.environ.get('SECRET_KEY'),
            'max_connections': int(os.environ.get('MAX_CONNECTIONS', '500'))
        })
        
        # Verificar configuraciones críticas
        if not config['db_url'] or not config['secret_key']:
            raise ValueError("Faltan variables de entorno críticas para producción")
    
    return config

# Uso
try:
    config = configurar_app()
    print(f"Aplicación configurada para entorno: {os.environ.get('APP_ENV', 'development')}")
    print(f"Debug: {config['debug']}")
    print(f"Nivel de log: {config['log_level']}")
except ValueError as e:
    print(f"Error de configuración: {e}")

Este patrón permite mantener una configuración flexible que se adapta automáticamente al entorno de ejecución, facilitando el despliegue en diferentes escenarios.

Herencia de variables de entorno en procesos hijos

Cuando creas procesos hijos, estos heredan las variables de entorno del proceso padre:

# Demostración de herencia de variables de entorno
import subprocess

# Establecer una variable en el proceso actual
os.environ['MENSAJE'] = 'Hola desde el proceso padre'

# Ejecutar un proceso hijo que accede a la variable
resultado = subprocess.run(
    ['python', '-c', 'import os; print(f"Variable heredada: {os.environ.get(\'MENSAJE\')}")'],
    text=True,
    capture_output=True
)

print(f"Salida del proceso hijo: {resultado.stdout.strip()}")

Esta característica permite propagar configuraciones a través de la jerarquía de procesos sin necesidad de pasarlas explícitamente como argumentos.

Las variables de entorno proporcionan un mecanismo flexible y potente para configurar aplicaciones y compartir información entre procesos. El módulo os de Python ofrece todas las herramientas necesarias para trabajar con ellas de manera eficiente, permitiéndote crear aplicaciones adaptables a diferentes entornos de ejecución.

Aprende Python online

Otros ejercicios de programación de Python

Evalúa tus conocimientos de esta lección Módulo os con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Módulo math

Python
Puzzle

Reto herencia

Python
Código

Excepciones

Python
Test

Introducción a Python

Python
Test

Reto variables

Python
Código

Funciones Python

Python
Puzzle

Reto funciones

Python
Código

Módulo datetime

Python
Test

Reto acumulación

Python
Código

Reto estructuras condicionales

Python
Código

Polimorfismo

Python
Test

Módulo os

Python
Test

Reto métodos dunder

Python
Código

Diccionarios

Python
Puzzle

Reto clases y objetos

Python
Código

Reto operadores

Python
Código

Operadores

Python
Test

Estructuras de control

Python
Puzzle

Funciones lambda

Python
Test

Reto diccionarios

Python
Código

Reto función lambda

Python
Código

Encapsulación

Python
Puzzle

Reto coleciones

Python
Proyecto

Reto funciones auxiliares

Python
Código

Crear módulos y paquetes

Python
Puzzle

Módulo datetime

Python
Puzzle

Excepciones

Python
Puzzle

Operadores

Python
Puzzle

Diccionarios

Python
Test

Reto map, filter

Python
Código

Reto tuplas

Python
Código

Proyecto gestor de tareas CRUD

Python
Proyecto

Tuplas

Python
Puzzle

Variables

Python
Puzzle

Tipos de datos

Python
Puzzle

Conjuntos

Python
Test

Reto mixins

Python
Código

Módulo csv

Python
Test

Módulo json

Python
Test

Herencia

Python
Test

Análisis de datos de ventas con Pandas

Python
Proyecto

Reto fechas y tiempo

Python
Proyecto

Reto estructuras de iteración

Python
Código

Funciones

Python
Test

Reto comprehensions

Python
Código

Variables

Python
Test

Reto serialización

Python
Proyecto

Módulo csv

Python
Puzzle

Reto polimorfismo

Python
Código

Polimorfismo

Python
Puzzle

Clases y objetos

Python
Código

Reto encapsulación

Python
Código

Estructuras de control

Python
Test

Importar módulos y paquetes

Python
Test

Módulo math

Python
Test

Funciones lambda

Python
Puzzle

Reto excepciones

Python
Código

Listas

Python
Puzzle

Reto archivos

Python
Proyecto

Encapsulación

Python
Test

Reto conjuntos

Python
Código

Clases y objetos

Python
Test

Instalación de Python y creación de proyecto

Python
Test

Reto listas

Python
Código

Tipos de datos

Python
Test

Crear módulos y paquetes

Python
Test

Tuplas

Python
Test

Herencia

Python
Puzzle

Reto acceso a sistema

Python
Proyecto

Proyecto sintaxis calculadora

Python
Proyecto

Importar módulos y paquetes

Python
Puzzle

Clases y objetos

Python
Puzzle

Módulo os

Python
Puzzle

Listas

Python
Test

Conjuntos

Python
Puzzle

Reto tipos de datos

Python
Código

Reto matemáticas

Python
Proyecto

Módulo json

Python
Puzzle

Todas las lecciones de Python

Accede a todas las lecciones de Python y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Python

Python

Introducción

Instalación Y Creación De Proyecto

Python

Introducción

Tema 2: Tipos De Datos, Variables Y Operadores

Python

Introducción

Instalación De Python

Python

Introducción

Tipos De Datos

Python

Sintaxis

Variables

Python

Sintaxis

Operadores

Python

Sintaxis

Estructuras De Control

Python

Sintaxis

Funciones

Python

Sintaxis

Estructuras Control Iterativo

Python

Sintaxis

Estructuras Control Condicional

Python

Sintaxis

Testing Con Pytest

Python

Sintaxis

Listas

Python

Estructuras De Datos

Tuplas

Python

Estructuras De Datos

Diccionarios

Python

Estructuras De Datos

Conjuntos

Python

Estructuras De Datos

Comprehensions

Python

Estructuras De Datos

Clases Y Objetos

Python

Programación Orientada A Objetos

Excepciones

Python

Programación Orientada A Objetos

Encapsulación

Python

Programación Orientada A Objetos

Herencia

Python

Programación Orientada A Objetos

Polimorfismo

Python

Programación Orientada A Objetos

Mixins Y Herencia Múltiple

Python

Programación Orientada A Objetos

Métodos Especiales (Dunder Methods)

Python

Programación Orientada A Objetos

Composición De Clases

Python

Programación Orientada A Objetos

Funciones Lambda

Python

Programación Funcional

Aplicación Parcial

Python

Programación Funcional

Entrada Y Salida, Manejo De Archivos

Python

Programación Funcional

Decoradores

Python

Programación Funcional

Generadores

Python

Programación Funcional

Paradigma Funcional

Python

Programación Funcional

Composición De Funciones

Python

Programación Funcional

Funciones Orden Superior Map Y Filter

Python

Programación Funcional

Funciones Auxiliares

Python

Programación Funcional

Reducción Y Acumulación

Python

Programación Funcional

Archivos Comprimidos

Python

Entrada Y Salida Io

Entrada Y Salida Avanzada

Python

Entrada Y Salida Io

Archivos Temporales

Python

Entrada Y Salida Io

Contexto With

Python

Entrada Y Salida Io

Módulo Csv

Python

Biblioteca Estándar

Módulo Json

Python

Biblioteca Estándar

Módulo Datetime

Python

Biblioteca Estándar

Módulo Math

Python

Biblioteca Estándar

Módulo Os

Python

Biblioteca Estándar

Módulo Re

Python

Biblioteca Estándar

Módulo Random

Python

Biblioteca Estándar

Módulo Time

Python

Biblioteca Estándar

Módulo Collections

Python

Biblioteca Estándar

Módulo Sys

Python

Biblioteca Estándar

Módulo Statistics

Python

Biblioteca Estándar

Módulo Pickle

Python

Biblioteca Estándar

Módulo Pathlib

Python

Biblioteca Estándar

Importar Módulos Y Paquetes

Python

Paquetes Y Módulos

Crear Módulos Y Paquetes

Python

Paquetes Y Módulos

Entornos Virtuales (Virtualenv, Venv)

Python

Entorno Y Dependencias

Gestión De Dependencias (Pip, Requirements.txt)

Python

Entorno Y Dependencias

Python-dotenv Y Variables De Entorno

Python

Entorno Y Dependencias

Acceso A Datos Con Mysql, Pymongo Y Pandas

Python

Acceso A Bases De Datos

Acceso A Mongodb Con Pymongo

Python

Acceso A Bases De Datos

Acceso A Mysql Con Mysql Connector

Python

Acceso A Bases De Datos

Novedades Python 3.13

Python

Características Modernas

Operador Walrus

Python

Características Modernas

Pattern Matching

Python

Características Modernas

Instalación Beautiful Soup

Python

Web Scraping

Sintaxis General De Beautiful Soup

Python

Web Scraping

Tipos De Selectores

Python

Web Scraping

Web Scraping De Html

Python

Web Scraping

Web Scraping Para Ciencia De Datos

Python

Web Scraping

Autenticación Y Acceso A Recursos Protegidos

Python

Web Scraping

Combinación De Selenium Con Beautiful Soup

Python

Web Scraping

Accede GRATIS a Python y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender cómo utilizar el módulo os para interactuar con el sistema operativo de forma multiplataforma.
  • Aprender a manipular rutas de archivos y directorios de manera segura y portable.
  • Gestionar procesos del sistema operativo, incluyendo creación, comunicación y control.
  • Acceder, modificar y utilizar variables de entorno para configurar aplicaciones.
  • Aplicar buenas prácticas y técnicas para la ejecución segura y eficiente de comandos y procesos externos.