El módulo shutil reúne operaciones de alto nivel para copiar, mover y eliminar archivos y carpetas. Es más cómodo que trabajar únicamente con funciones básicas del sistema, pero también puede causar pérdidas si se usa sin validaciones. En esta guía construirás operaciones seguras, con rutas modernas, control de conflictos y registro de resultados.
Para complementar el tema, consulta las guías sobre archivos en Python, módulo os, excepciones, logging y organización automática. Como fuentes externas, revisa la documentación oficial de shutil y la documentación oficial de pathlib.
copy() y copy2()
copy() copia el contenido y los permisos básicos. copy2() intenta conservar metadatos adicionales, como tiempos de modificación. Ninguna función garantiza preservar todos los atributos de todos los sistemas.
from pathlib import Path
from shutil import copy2
origen = Path("informe.pdf")
destino = Path("copias") / origen.name
destino.parent.mkdir(parents=True, exist_ok=True)
copy2(origen, destino)Usa copy2() para respaldos comunes y copy() cuando los metadatos no sean importantes.
Copiar a otra carpeta con un nombre nuevo
destino = Path("copias") / "informe-final.pdf"
copy2(origen, destino)El segundo argumento puede ser una carpeta o una ruta completa. Para evitar ambigüedad, construye siempre la ruta final explícitamente.
Evitar sobrescrituras
Las funciones de copia pueden reemplazar un archivo existente. Valida antes y decide una política: detener, generar otro nombre o solicitar confirmación.
if destino.exists():
raise FileExistsError(f"Ya existe: {destino}")En procesos automáticos, generar un nombre único suele ser más seguro que sobrescribir.
Mover archivos
from shutil import move
resultado = move(origen, destino)
print(resultado)Dentro del mismo sistema de archivos, mover puede equivaler a renombrar y ser rápido. Entre discos distintos, puede implicar copia y eliminación. Por eso debes considerar espacio disponible y fallos intermedios.
Renombrar con pathlib
Si solo deseas cambiar el nombre dentro del mismo sistema, Path.replace() o Path.rename() son opciones claras. replace() puede sustituir un destino existente, mientras que el comportamiento de rename() depende del sistema. Verifica siempre el destino.
Copiar carpetas completas
from shutil import copytree, ignore_patterns
copytree(
"proyecto",
"respaldo/proyecto",
ignore=ignore_patterns("*.pyc", "__pycache__", ".git"),
)copytree() recrea la estructura. En versiones modernas puede combinarse con dirs_exist_ok=True, pero mezclar contenidos existentes requiere una política clara para conflictos.
Ignorar patrones
from shutil import ignore_patterns
ignorar = ignore_patterns(
".git",
".venv",
"__pycache__",
"*.log",
"*.tmp",
)Para un respaldo de proyecto, normalmente no conviene copiar entornos virtuales, cachés ni secretos. Revisa también archivos .env.
Copiar solo algunos tipos
Cuando la selección depende de reglas más complejas, recorre con rglob() y copia cada archivo conservando su ruta relativa.
origen = Path("datos")
destino = Path("respaldo")
permitidas = {".csv", ".json", ".txt"}
for ruta in origen.rglob("*"):
if not ruta.is_file() or ruta.suffix.lower() not in permitidas:
continue
relativo = ruta.relative_to(origen)
final = destino / relativo
final.parent.mkdir(parents=True, exist_ok=True)
copy2(ruta, final)Eliminar carpetas con rmtree()
shutil.rmtree() elimina una carpeta completa y no debe usarse sin controles fuertes.
from shutil import rmtree
objetivo = Path("temporal").resolve()
base_permitida = Path.cwd().resolve()
if base_permitida not in objetivo.parents:
raise ValueError("Ruta fuera del área permitida")
rmtree(objetivo)Nunca construyas el objetivo directamente desde una entrada de usuario sin normalizar y validar.
Espacio disponible
from shutil import disk_usage
total, usado, libre = disk_usage(destino.parent)
print(f"Libre: {libre / 1024**3:.2f} GB")Antes de copiar un árbol grande, compara el tamaño estimado con el espacio libre y conserva un margen.
Verificar copias
Comparar únicamente el tamaño detecta algunos fallos, pero no todos. Para archivos importantes, calcula un hash antes y después.
import hashlib
def sha256(ruta):
resumen = hashlib.sha256()
with open(ruta, "rb") as archivo:
for bloque in iter(lambda: archivo.read(1024 * 1024), b""):
resumen.update(bloque)
return resumen.hexdigest()Los hashes añaden tiempo de lectura, así que decide cuándo la integridad justifica el coste.
Copia atómica
Para no dejar un destino incompleto, copia primero a un nombre temporal en la misma carpeta, verifica y luego reemplaza.
temporal = destino.with_suffix(destino.suffix + ".tmp")
copy2(origen, temporal)
if sha256(origen) != sha256(temporal):
temporal.unlink()
raise ValueError("La verificación falló")
temporal.replace(destino)Registrar operaciones
Guarda origen, destino, tamaño, fecha y resultado. Un log facilita auditoría, pero para deshacer movimientos es mejor un historial estructurado en JSONL o una base de datos pequeña.
Manejo de errores
try:
copy2(origen, destino)
except FileNotFoundError:
logging.exception("No existe el origen")
except PermissionError:
logging.exception("Permiso denegado")
except OSError:
logging.exception("Falló la operación de archivos")Procesa cada elemento de forma independiente y muestra un resumen final. No ocultes errores con un except Exception: pass.
Enlaces simbólicos
Decide si deseas copiar el enlace o seguirlo. Seguir enlaces puede sacar el proceso del árbol esperado o crear ciclos. Para datos externos, trata los enlaces como contenido potencialmente peligroso y valida la ruta resuelta.
Pruebas
Usa tempfile.TemporaryDirectory() para crear escenarios controlados. Prueba destinos existentes, permisos, nombres Unicode, archivos vacíos, carpetas anidadas y fallos simulados. Comprueba que un error no borre el origen ni deje temporales abandonados.
Conclusión
shutil simplifica operaciones complejas, pero la seguridad depende de tus reglas. Construye rutas con pathlib, evita sobrescrituras silenciosas, valida áreas permitidas, verifica copias críticas y registra cada acción. Para eliminaciones, añade todavía más controles y utiliza primero un modo de simulación.






