shutil: copia y mueve archivos en Python

Actualizado el: 20/08/2026
Tempo de leitura: 4 minutos
Copiando e movendo arquivos com Python usando shutil

El módulo shutil forma parte de la biblioteca estándar de Python y reúne funciones de alto nivel para copiar, mover, eliminar y empaquetar archivos y carpetas. Es especialmente útil en automatizaciones, instaladores, sistemas de respaldo, organizadores de documentos y tareas de mantenimiento. Aunque sus operaciones son sencillas, trabajar con datos reales exige protegerse contra sobrescrituras, permisos insuficientes, rutas equivocadas y copias incompletas.

Para aprovecharlo bien, conviene entender el manejo de rutas con pathlib, el módulo os, el tratamiento de excepciones en Python y el uso de logging. La documentación oficial de shutil contiene la referencia completa, y la documentación de pathlib explica la forma moderna de representar rutas.

Importar los módulos

import shutil
from pathlib import Path

pathlib mejora la legibilidad y permite construir rutas de forma portable. Aunque shutil acepta objetos de ruta, en algunas integraciones antiguas puede ser necesario convertirlos con str().

Copiar un archivo con copy

origen = Path("entrada/informe.pdf")
destino = Path("salida/informe.pdf")

destino.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(origen, destino)

copy() copia el contenido y los permisos básicos. Si el destino es una carpeta, conserva el nombre original. Si es una ruta de archivo, usa el nombre especificado.

Conservar más metadatos con copy2

shutil.copy2(origen, destino)

copy2() intenta conservar metadatos adicionales, como las fechas de acceso y modificación. Esto resulta útil en respaldos y migraciones. Sin embargo, no todos los sistemas de archivos admiten los mismos atributos, por lo que no debes asumir una réplica perfecta de permisos, propietarios o listas de control.

Copiar únicamente el contenido

with origen.open("rb") as entrada, destino.open("wb") as salida:
    shutil.copyfileobj(entrada, salida)

copyfile() y copyfileobj() se centran en los datos. copyfileobj() también permite trabajar con objetos de archivo y controlar el tamaño del búfer. Para la mayoría de los casos, copy2() es una elección práctica.

Evitar sobrescrituras accidentales

def destino_disponible(ruta: Path) -> Path:
    if not ruta.exists():
        return ruta

    contador = 1
    while True:
        candidato = ruta.with_name(
            f"{ruta.stem}_{contador}{ruta.suffix}"
        )
        if not candidato.exists():
            return candidato
        contador += 1

Antes de copiar:

destino = destino_disponible(Path("salida/informe.pdf"))
shutil.copy2(origen, destino)

En aplicaciones empresariales también puedes calcular un hash y evitar duplicar archivos idénticos.

Mover archivos y carpetas

shutil.move("entrada/foto.jpg", "archivo/imagenes/foto.jpg")

Si origen y destino están en el mismo sistema de archivos, la operación suele ser un renombrado rápido. Si están en discos diferentes, Python puede copiar y después eliminar el original. Esto significa que mover un archivo grande puede tardar y fallar a mitad del proceso.

Validar antes de mover

def mover_seguro(origen: Path, destino: Path) -> Path:
    if not origen.exists():
        raise FileNotFoundError(f"No existe: {origen}")
    if not origen.is_file():
        raise ValueError("El origen debe ser un archivo")

    destino.parent.mkdir(parents=True, exist_ok=True)
    destino = destino_disponible(destino)
    resultado = shutil.move(str(origen), str(destino))
    return Path(resultado)

La función evita reemplazar un archivo existente y crea la carpeta necesaria.

Copiar una carpeta completa

shutil.copytree(
    "proyecto",
    "respaldo/proyecto",
    dirs_exist_ok=False
)

Por defecto, el destino no debe existir. Desde versiones modernas de Python, dirs_exist_ok=True permite copiar dentro de una carpeta existente. Úsalo con cuidado, porque archivos con el mismo nombre pueden reemplazarse.

Excluir archivos durante copytree

shutil.copytree(
    "proyecto",
    "respaldo/proyecto",
    ignore=shutil.ignore_patterns(
        "__pycache__",
        "*.pyc",
        ".venv",
        ".git"
    )
)

Este patrón es útil para respaldar código sin copiar entornos virtuales, cachés o repositorios internos.

Eliminar carpetas con rmtree

shutil.rmtree("carpeta_temporal")

rmtree() elimina una carpeta y todo su contenido. Es una de las funciones más peligrosas del módulo. Antes de utilizarla, valida que la ruta se encuentre dentro de una ubicación permitida:

BASE_TEMP = Path("temporales").resolve()
objetivo = Path("temporales/tarea_123").resolve()

if BASE_TEMP not in objetivo.parents:
    raise ValueError("Ruta fuera del directorio temporal")

shutil.rmtree(objetivo)

No construyas rutas de borrado directamente con entradas no confiables.

Comprobar espacio disponible

total, usado, libre = shutil.disk_usage(Path.home())
print(f"Libre: {libre / 1024**3:.2f} GB")

Antes de copiar grandes volúmenes, compara el tamaño estimado con el espacio libre. Mantén un margen adicional para archivos temporales.

Crear archivos ZIP

shutil.make_archive(
    base_name="respaldo_2026_07_11",
    format="zip",
    root_dir="datos"
)

Para extraer:

shutil.unpack_archive("respaldo.zip", "restaurado")

No extraigas archivos recibidos de fuentes desconocidas sin validar sus rutas internas, porque un archivo malicioso puede intentar escribir fuera de la carpeta de destino.

Comparar tamaños después de copiar

shutil.copy2(origen, destino)

if origen.stat().st_size != destino.stat().st_size:
    raise IOError("La copia no conserva el tamaño esperado")

Comparar tamaños detecta fallos evidentes, pero no garantiza que el contenido sea idéntico. Para una verificación más fuerte, calcula un hash:

import hashlib

def sha256(ruta: Path) -> str:
    digest = hashlib.sha256()
    with ruta.open("rb") as archivo:
        for bloque in iter(lambda: archivo.read(1024 * 1024), b""):
            digest.update(bloque)
    return digest.hexdigest()

Registrar cada operación

import logging

logging.basicConfig(
    filename="archivos.log",
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
    encoding="utf-8"
)

try:
    nuevo = mover_seguro(origen, destino)
    logging.info("Movido %s a %s", origen, nuevo)
except OSError:
    logging.exception("Error moviendo %s", origen)

El registro facilita restaurar movimientos y entender errores de permisos o disco lleno.

Ejemplo: clasificar archivos por extensión

CATEGORIAS = {
    ".pdf": "documentos",
    ".docx": "documentos",
    ".jpg": "imagenes",
    ".png": "imagenes",
    ".zip": "comprimidos",
}

def organizar(carpeta: Path):
    for archivo in carpeta.iterdir():
        if not archivo.is_file():
            continue

        categoria = CATEGORIAS.get(
            archivo.suffix.lower(),
            "otros"
        )
        destino = carpeta / categoria / archivo.name
        mover_seguro(archivo, destino)

Prueba primero con copias de archivos. Añade exclusiones para descargas incompletas, logs y el propio script.

Modo simulación

def mover_seguro(origen, destino, simulacion=True):
    destino = destino_disponible(destino)
    if simulacion:
        print(f"[SIMULACIÓN] {origen} -> {destino}")
        return destino
    destino.parent.mkdir(parents=True, exist_ok=True)
    return Path(shutil.move(str(origen), str(destino)))

El modo simulación es una de las mejores defensas antes de ejecutar una automatización sobre miles de archivos.

Errores frecuentes

Los problemas más comunes son sobrescribir sin aviso, eliminar una ruta equivocada con rmtree(), ignorar el espacio disponible, copiar carpetas recursivamente dentro de sí mismas, mover archivos todavía abiertos y asumir que una operación entre discos es instantánea. También debes manejar PermissionError, FileNotFoundError y errores generales de entrada y salida.

Conclusión

shutil simplifica tareas que serían más complejas con operaciones de bajo nivel. copy2() copia archivos conservando metadatos, move() traslada elementos, copytree() replica carpetas y make_archive() crea paquetes comprimidos. Para usar estas funciones de forma confiable, valida rutas, evita sobrescrituras, registra acciones, comprueba resultados y ofrece un modo simulación. Así podrás automatizar copias, migraciones y organización de documentos sin poner los datos en riesgo.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Documento y bandeja de entrada que representan buzones de correo con mailbox en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    mailbox en Python: buzones de correo

    Aprende mailbox en Python para leer, crear y migrar Maildir, mbox y MH con locking, flags, mensajes y manejo seguro

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026
    Editor de texto que representa formato con textwrap en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    textwrap en Python: formatea textos

    Aprende textwrap en Python para dividir, rellenar, acortar, indentar y quitar sangrías con control de ancho, espacios y palabras largas.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Carpeta y lupa que representan filtros de nombres con fnmatch en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    fnmatch en Python: filtra nombres de archivos

    Aprende fnmatch en Python para filtrar nombres de archivos con comodines, controlar mayúsculas, excluir patrones y distinguir glob de regex.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Monitor con datos binarios que representa arrays numéricos compactos en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    array en Python: números compactos

    Aprende array en Python para almacenar números compactos, usar archivos binarios, byte order, memoryview y buffers seguros.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Círculo cromático que representa conversiones RGB, HSV y HLS con colorsys en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    colorsys en Python: RGB, HSV y HLS

    Aprende colorsys en Python para convertir colores entre RGB, HSV, HLS y YIQ, crear paletas y evitar errores de escala

    Ler mais

    Tempo de leitura: 5 minutos
    09/08/2026
    Icono de configuración que representa archivos plist con plistlib en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    plistlib: lee y escribe archivos plist

    Aprende plistlib en Python para leer y escribir archivos plist XML y binarios, validar datos y manejar fechas, bytes y

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026