Scripts y automatización de archivos

Previsualizar un lote de cambios de nombre antes de aplicarlos

Compara los nombres antiguos y nuevos en una tabla y comprueba conflictos. Solo un --apply explícito crea copias en una carpeta nueva, manteniendo los originales.

Ver el índice

La traducción se ha realizado con IA. Comprueba el código, las unidades y los valores junto con el original. La revisión por hablantes nativos de cada idioma aún no se ha completado. English

Para quién esPrincipiantes que quieren renombrar varios archivos pero les preocupan las reglas incorrectas o sobrescribir archivos

Preparación
  • Instala Python 3.12 o posterior y comprueba la versión en una terminal con python --version.
  • Extrae el ZIP del ejemplo en una carpeta nueva. No ejecutes el ejemplo desde dentro del archivo ZIP.
  • Abre una terminal en la carpeta que contiene example.py. Si el comando python no está disponible en Windows, utiliza py. En macOS o Linux, utiliza python3 si tu entorno lo requiere.
  • No se necesitan paquetes externos ni cuentas. Los archivos incluidos son datos sintéticos creados para este tutorial.

01Empieza previsualizando solo tres nombres

En una batch operation, evitar que una rule incorrecta se aplique a todos los files es más importante que detectar un typo en un solo file. Este ejemplo valida toda la name list antes de mostrar el plan. El default command no copia files ni crea folders. Después de comprobar el plan, ejecuta la operación por separado con --apply.

  1. En la carpeta extraída, comprueba mapping.csv y los tres original files dentro de source_files.
  2. Compara old_name y new_name en mapping.csv. La column izquierda es el filename actual; la derecha es el name de la nueva copy.
  3. Ejecuta el default command siguiente y comprueba que los tres pares de names unidos por flechas sean los que querías.
  4. En esta etapa, comprueba que source_files no haya cambiado y que outputs todavía no exista.
bash
python example.py

02Leer el mapping de nombres de archivo

old_namenew_name
메모 초안.txtnote-draft.txt
견적 1.txtquote-001.txt
사진 설명.txtphoto-notes.txt

Cada mapping row vincula un original con un result. Introduce old_name exactamente como aparece el filename real, incluido el letter case. Toda la operación se detiene si omites un original o añades un name que no existe. Esta rule detecta errores como utilizar un mapping antiguo después de haber añadido un file nuevo a la carpeta.

Los values de new_name se consideran conflictivos incluso si solo difieren en letter case. Esto evita que report.txt y REPORT.txt se conviertan en results separados. También se rechazan leading or trailing spaces, trailing dots, path separators y Windows reserved names. Una persona debe realizar la comprobación final en la preview para confirmar que las naming rules se ajustan a la tarea.

03Código completo utilizado en este ejemplo

make_plan comprueba todos los inputs y conflicts. main imprime el validated plan y después comprueba si se proporcionó --apply. Incluso al aplicar el plan, copia file contents con new names en lugar de utilizar rename sobre los originals, por lo que todavía puedes inspeccionar el starting state.

example.py
"""기본은 미리보기입니다. --apply일 때만 새 이름의 복사본을 만듭니다."""

import argparse
import csv
import re
import shutil
import sys
from pathlib import Path

BASE = Path(__file__).resolve().parent
INPUT = BASE / "source_files"
MAPPING = BASE / "mapping.csv"
OUTPUT = BASE / "outputs"
RESERVED = {"CON", "PRN", "AUX", "NUL"} | {
    f"{prefix}{number}" for prefix in ("COM", "LPT") for number in range(1, 10)
}


def validate_name(name):
    # 폴더 경로, Windows 예약 이름, 제어 문자 등을 이름으로 받지 않습니다.
    if (not name or name in {".", ".."} or name != name.strip()
            or name.endswith(".") or re.search(r'[<>:"/\\|?*\x00-\x1f]', name)
            or name.split(".")[0].upper() in RESERVED):
        raise ValueError(f"사용할 수 없는 파일명: {name!r}")


def make_plan():
    if (INPUT.is_symlink() or not INPUT.is_dir()
            or getattr(INPUT, "is_junction", lambda: False)()):
        raise ValueError("source_files는 실제 폴더여야 합니다.")
    if MAPPING.is_symlink():
        raise ValueError("mapping.csv 링크는 처리하지 않습니다.")
    sources = list(INPUT.iterdir())
    if any(path.is_symlink() or not path.is_file() for path in sources):
        raise ValueError("source_files에는 일반 파일만 넣으세요.")
    if not sources:
        raise ValueError("source_files에 파일이 없습니다.")
    plan, old_keys, new_keys = [], set(), set()
    with MAPPING.open("r", encoding="utf-8-sig", newline="") as stream:
        reader = csv.DictReader(stream, strict=True)
        if reader.fieldnames != ["old_name", "new_name"]:
            raise ValueError("mapping.csv의 열은 old_name,new_name이어야 합니다.")
        for row in reader:
            if None in row or any(value is None for value in row.values()):
                raise ValueError(f"mapping.csv {reader.line_num}행의 열 수를 확인하세요.")
            old, new = row["old_name"], row["new_name"]
            validate_name(old)
            validate_name(new)
            if old.casefold() in old_keys:
                raise ValueError(f"원본이 중복 지정되었습니다: {old}")
            if new.casefold() in new_keys:
                raise ValueError(f"새 이름 충돌: {new}")
            old_keys.add(old.casefold())
            new_keys.add(new.casefold())
            plan.append((old, new))
    # 한 파일이라도 누락되거나 없는 파일을 지정하면 아무것도 복사하지 않습니다.
    if {old for old, _ in plan} != {path.name for path in sources}:
        raise ValueError("mapping.csv의 old_name은 원본 파일 전체와 정확히 일치해야 합니다.")
    if OUTPUT.exists() or OUTPUT.is_symlink():
        raise FileExistsError("outputs가 이미 있습니다. 기존 결과를 옮긴 뒤 실행하세요.")
    return plan


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--apply", action="store_true", help="새 이름의 복사본을 outputs/renamed에 만듭니다.")
    args = parser.parse_args()
    plan = make_plan()  # 충돌 검사는 출력 폴더를 만들기 전에 전체에 대해 끝냅니다.
    for old, new in plan:
        print(f"{old} → {new}")
    if not args.apply:
        print(f"미리보기: {len(plan)}개. 파일은 변경되지 않았습니다.")
        print("이름을 확인한 뒤 python example.py --apply 를 실행하세요.")
        return 0

    OUTPUT.mkdir()
    renamed = OUTPUT / "renamed"
    renamed.mkdir()
    for old, new in plan:
        # 이름 변경 대신 복사합니다. xb는 대상이 생겼으면 덮어쓰지 않고 실패합니다.
        with (INPUT / old).open("rb") as source, (renamed / new).open("xb") as target:
            shutil.copyfileobj(source, target)
    with (OUTPUT / "manifest.csv").open("x", encoding="utf-8-sig", newline="") as stream:
        writer = csv.writer(stream)
        writer.writerow(["old_name", "new_name"])
        writer.writerows(plan)
    print(f"완료: {len(plan)}개 복사본 → {renamed}")
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except (OSError, ValueError, csv.Error) as error:
        print(f"중지: {error}", file=sys.stderr)
        raise SystemExit(2)

La opción store_true de argparse hace que preview sea el comportamiento por defecto cuando se omite --apply. Los copy destinations se abren en modo xb para evitar sobrescribir un file que ya existe. El mapping completado también se registra en manifest.csv para poder rastrear un new name hasta su original name.

04Crear copias a partir del plan comprobado

Después de comprobar las tres preview lines, ejecuta el comando siguiente en la misma carpeta. Si editaste mapping.csv después de la preview, vuelve a ejecutar el default command para comprobar el nuevo plan. El apply command no recuerda la preview anterior; vuelve a leer y validar el mapping en el momento de la ejecución.

bash
python example.py --apply
LocationResultado esperado
source_files/3 original files conservados con sus nombres actuales
outputs/renamed/note-draft.txt, quote-001.txt, photo-notes.txt
outputs/manifest.csv3 rows de mapping original → new name después del header

La terminal muestra “완료: 3개 Copy본” (complete: three copies) y la ruta de la output folder. Abre cada new file para comprobar tanto su content como su name. Por ejemplo, el practice document number de note-draft.txt debería coincidir con el number de 메모 초안.txt.

05Crear un conflicto para comprobar que el script se detiene

  1. Extrae otra copia en una carpeta nueva. No mezcles error tests con una carpeta que ya contenga results.
  2. En mapping.csv, cambia el segundo new_name a note-draft.txt para que coincida con el primero y guarda.
  3. Ejecuta python example.py --apply. Debería aparecer el mensaje “새 이름 충돌” (new-name collision).
  4. Comprueba que no se haya creado ninguna carpeta outputs y que los tres originals permanezcan en source_files.
  5. Restaura los mapping values originales y vuelve a comprobar desde el paso de preview.

Si la validation se hiciera file por file dentro del copy loop, el primer file podría procesarse antes de que el segundo provocara un failure. Este ejemplo comprueba primero todos los names para que estos pre-check errors no causen partial application. También se detiene antes de empezar si outputs ya existe.

06Utilizar tus propias reglas de nombres de documentos

Empieza con una pequeña copy de una sola folder al aplicarlo a tu trabajo. Coloca copies de los originals en source_files y escribe una row por file en mapping.csv. Utiliza el full name, incluida la extension. Una extension no convierte el file format, así que no asignes un text file a un name terminado en .pdf.

Si añades numbers, utiliza una cantidad fija de dígitos como 001 y 002, y un date order coherente para facilitar la búsqueda de results. Acuerda primero qué fields deben formar parte del name y después comprueba su orden y cualquier omission en la preview. Este ejemplo no extrae titles del document content ni infiere tu intent para generar names automáticamente.

07Qué comprobar para cada mensaje de detención

MessageQué comprobarSolución
New-name collision (새 이름 충돌)Values new_name idénticos o que solo difieren en letter caseElige final names distintos.
Original specified more than onceold_name repetido en dos rowsMantén una row por original.
Must match the complete set of original filesUn missing file o un typo en old_nameCompara de nuevo el contenido de la folder con el mapping.
Invalid filenamePaths, reserved names, forbidden characters o trailing dotsIntroduce solo el filename y elimina restricted characters.
outputs already existsResults de una copy operation anteriorRenombra la result folder para conservarla y después empieza de nuevo.

Repetir el mismo command después de aplicar un plan no actualiza existing results. Incluso la preview se detiene si encuentra una carpeta outputs existente. Esta rule evita mezclar los results de operaciones separadas. No se proporciona ninguna overwrite option.

08Límites de la copia y comprobaciones que siguen siendo necesarias

Este ejemplo solo lee los original files y da new names a las copies. Mantener tanto los originals como sus copies requiere storage adicional. Conserva el document content, pero no es una herramienta para duplicar creation and modification times, access permissions ni todo el operating-system metadata.

Los errors encontrados antes de copiar, como name conflicts, detienen toda la operación. Una vez que empieza copying, un power failure o insufficient disk space no se revierte como una single transaction. Algunos files pueden permanecer en outputs, así que comprueba el completion message y los tres files. Los concurrent changes en los originals o el mapping por otro programa también requieren un diseño separado.

Registro de ejecución y verificación

2026-09-19 · Windows 11 · CPython 3.12.14 · Sin paquetes adicionales · Ejecutado en una temporary copy de la distribución

  • La default preview no crea ninguna output folder
  • Las 3 copies creadas con --apply son byte-for-byte idénticas a los originals
  • Los conflicts por identical-name y case-only se rechazan antes de crear output
  • Se rechazan duplicate u omitted originals, nonexistent originals, path traversal y reserved names
  • Una ejecución repetida se detiene conservando los existing results
Límites de la verificación
  • Los original files no se renombran.
  • El partial output no se revierte automáticamente después de un disk error.
  • No se conserva todo el file metadata.
  • La ejecución se verificó en Windows.

Criterios de redacción y verificación de todo el sitio

Archivos de ejemplo para ejecutar

Incluye código, datos de entrada e instrucciones de ejecución. Extrae el ZIP y lee primero README.txt.

Descargar ZIP de ejemplo

El código de ejemplo, los nombres de archivo y las claves de entrada se mantienen como en el original. Consulta también los comandos y los pasos de comprobación del texto traducido.

Material de práctica original · Guarda los archivos originales por separado antes de ejecutarlo.

Fuentes de referencia

Las explicaciones y los ejemplos son de elaboración propia. Puedes consultar los comportamientos y conceptos relacionados en las siguientes fuentes oficiales.