Cómo copiar y mover archivos en Python con shutil

Publicado el: 11/07/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

    Monitoramento de pastas em tempo real com Python Watchdog
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    Watchdog en Python: monitoriza carpetas en tiempo real

    Aprende Watchdog en Python para monitorizar carpetas, filtrar eventos, evitar duplicados, esperar archivos completos y automatizar procesos seguros.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Instalação offline de pacotes Python sem internet
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    Cómo instalar paquetes de Python sin conexión a Internet

    Aprende a instalar paquetes de Python sin Internet con pip download, ruedas, requirements.txt, --no-index, hashes y repositorios internos.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Como acelerar código Python usando lru cache
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    lru_cache en Python: acelera funciones con caché

    Aprende lru_cache en Python: memoización, maxsize, cache_info, cache_clear, argumentos hashables, recursión y errores frecuentes.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Uso do módulo random para gerar valores aleatórios em Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    Módulo random en Python: guía práctica para principiantes

    Aprende el módulo random en Python: enteros, decimales, choice, sample, shuffle, seeds, distribuciones y diferencias con secrets.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Uso do módulo collections para estruturas avançadas em Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    Módulo collections en Python: deque, Counter y más

    Aprende collections en Python con Counter, defaultdict, deque, namedtuple, ChainMap y buenas prácticas para elegir estructuras eficientes.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Introdução ao módulo itertools para iniciantes em Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    itertools en Python: guía práctica para iterar mejor

    Aprende itertools en Python con count, cycle, chain, islice, combinations, product, groupby, tee y zip_longest.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026