1. 批量重命名文件并生成映射日志

批量重命名文件并生成映射日志 学习卡片

任务拆解:遍历 → 规则改名 → 写映射日志

本案例要完成三件事:用 pathlib 遍历目标目录中的文件;按统一规则生成新文件名并执行重命名;把「旧名 → 新名」逐行写入日志文件,使任何一次批量操作都能被反向还原。核心难点不在改名本身,而在于冲突检测与可回滚:如果新名已存在,直接覆盖会造成数据丢失;如果没有日志,回滚就无从下手。

命名规则与冲突处理约定

示例采用一条简单且可验证的规则:把文件名统一为 前缀_序号.扩展名,例如 IMG_0421.JPG 变为 photo_0001.JPG。序号按文件名排序后从 1 开始递增,扩展名保持原样(含大小写)。冲突处理策略如下:

  • 目标名已存在于目录中 → 跳过该文件,并在日志中标记 SKIP。
  • 源文件与目标名相同 → 视为无需处理,标记 SKIP。
  • 正常改名 → 标记 OK,并记录旧名与新名。
日志采用 旧名\t新名\t状态 的制表符分隔格式,既方便人阅读,也方便后续用脚本解析回滚。

关键实现代码

下面脚本可直接运行。它先收集文件、排序、计算新名,再统一执行重命名并写日志,避免边遍历边改名导致的顺序混乱。

from pathlib import Path

TARGET_DIR = Path("./demo_files")   # 待处理目录
PREFIX = "photo"                    # 新文件名前缀
LOG_FILE = Path("./rename_log.tsv") # 映射日志

def build_plan(files, prefix):
    """按排序结果生成 (旧路径, 新路径) 计划,并检测冲突。"""
    plan = []
    for index, old_path in enumerate(files, start=1):
        new_name = f"{prefix}_{index:04d}{old_path.suffix}"
        new_path = old_path.with_name(new_name)
        plan.append((old_path, new_path))
    return plan

def execute(plan, log_path):
    """执行重命名并写日志,返回统计结果。"""
    ok = skip = 0
    with log_path.open("w", encoding="utf-8") as log:
        log.write("old_name\tnew_name\tstatus\n")
        for old_path, new_path in plan:
            if old_path == new_path or new_path.exists():
                log.write(f"{old_path.name}\t{new_path.name}\tSKIP\n")
                skip += 1
                continue
            old_path.rename(new_path)
            log.write(f"{old_path.name}\t{new_path.name}\tOK\n")
            ok += 1
    return ok, skip

if __name__ == "__main__":
    files = sorted(p for p in TARGET_DIR.iterdir() if p.is_file())
    plan = build_plan(files, PREFIX)
    ok, skip = execute(plan, LOG_FILE)
    print(f"完成:成功 {ok} 个,跳过 {skip} 个,日志:{LOG_FILE}")

执行流程与日志结构

下图展示脚本从目录扫描到日志落盘的完整数据流,以及日志中三种状态字段的含义。注意 new_path.exists() 的检测发生在真正 rename 之前,这是防止覆盖的关键判断点。

TARGET_DIR iterdir() 扫描文件 sorted() 排序 保证序号稳定可复现 build_plan() photo_0001.JPG … 冲突检测 exists()? 同名? rename() 执行 状态 = OK 跳过 状态 = SKIP rename_log.tsv old_name → new_name → status IMG_0421.JPG → photo_0001.JPG → OK 回滚依据 读取日志 OK 行 new → old 反向改名

运行与结果判读

准备一个测试目录,放入若干文件后运行脚本。示例目录内容与预期日志如下(以下为教学示例数据,非真实运行结果):

目录初始文件排序后序号新文件名日志状态
IMG_0421.JPG1photo_0001.JPGOK
IMG_0422.JPG2photo_0002.JPGOK
photo_0003.JPG(已存在)3photo_0003.JPGSKIP
notes.txt4photo_0004.txtOK

判读要点:日志中 OK 行数应等于实际被改名的文件数;SKIP 行说明该文件未被动过,可安全忽略。若发现某个预期改名的文件出现在 SKIP 中,应检查是否与已有文件重名,或源文件本身已符合目标命名。

回滚操作

回滚只需读取日志中状态为 OK 的行,按「新名 → 旧名」反向执行 rename。由于日志记录了完整映射,即使目录中新增了其他文件,也不会误伤。

def rollback(log_path):
    with log_path.open(encoding="utf-8") as log:
        next(log)  # 跳过表头
        for line in log:
            old_name, new_name, status = line.rstrip("\n").split("\t")
            if status != "OK":
                continue
            new_path = TARGET_DIR / new_name
            old_path = TARGET_DIR / old_name
            if new_path.exists():
                new_path.rename(old_path)
回滚前请确认目标目录中没有与旧名冲突的新文件,否则 rename 可能覆盖已有内容。生产环境中建议先做一次 dry-run 检查。

可复用检查清单

  • 运行前备份目录,或先在副本上验证规则。
  • 确认 PREFIX 与序号位数不会与现有文件冲突。
  • 日志文件不要放在被扫描的目录内,否则可能被自身遍历到。
  • 回滚前检查日志完整性,确保没有中途中断导致的半截记录。