python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python文件批量重命名

基于Python实现一个文件批量重命名工具

作者:知远漫谈

在日常开发和数据管理中,我们经常需要对大量文件进行重命名操作,本文将带你从零开始,一步步实现一个功能强大、安全可靠的文件批量重命名脚本,感兴趣的小伙伴可以了解下

在日常开发和数据管理中,我们经常需要对大量文件进行重命名操作。手动一个个修改不仅耗时,还容易出错。而使用 Python 的标准库来构建一个文件批量重命名工具,不仅能提升效率,还能让你深入理解 os、pathlib、re、shutil 等核心模块的协作方式。本文将带你从零开始,一步步实现一个功能强大、安全可靠的文件批量重命名脚本。

为什么选择标准库?

“不要重复发明轮子。” —— 常见的开源工具虽多,但很多场景下,标准库已足够强大且无需额外依赖。

Python 的标准库是语言的核心组成部分,它自带了处理文件系统、正则表达式、路径操作等常用功能,完全满足我们构建文件重命名工具的需求。

优势一览:

工具需求分析

我们要实现的是一款支持以下功能的命令行工具:

功能描述
支持指定目录用户可输入目标文件夹路径
模式匹配支持正则表达式或通配符匹配文件名
批量重命名根据规则替换文件名
占位符支持如 {name}、{ext}、{index} 等动态变量
安全预览提供重命名前的预览,防止误操作
可逆操作提供“撤销”机制(通过备份原名)
日志记录记录所有操作,便于追踪

第一步:项目结构设计

虽然不涉及外部依赖,但我们仍应保持代码结构清晰。建议如下:

rename_tool/
├── main.py               # 主程序入口
├── config.py             # 配置常量(如默认备份路径)
└── utils.py              # 工具函数模块

我们只用一个 main.py 文件即可完成全部功能,但为了教学清晰,会分模块讲解。

第二步:核心模块导入与初始化

import os
import re
import shutil
from pathlib import Path
from typing import List, Dict, Tuple, Optional
import datetime

模块说明:

模块用途
os低级文件系统操作(如遍历目录)
pathlib.Path面向对象的路径操作,更优雅
re正则表达式匹配,用于高级模式识别
shutil安全复制/移动文件(可用于备份)
typing类型提示,提高可读性和维护性
datetime生成日志时间戳

第三步:用户交互设计

我们采用命令行交互方式,让用户输入必要的参数。

def get_user_input() -> Dict[str, str]:
    """获取用户输入的配置"""
    print("🎯 文件批量重命名工具启动!")
    
    directory = input("📁 请输入要处理的目录路径(回车使用当前目录): ").strip()
    if not directory:
        directory = os.getcwd()

    pattern = input("🔍 输入匹配模式(支持正则表达式,如 *.txt): ").strip()
    if not pattern:
        print("❌ 未输入有效模式,退出。")
        return None

    replacement = input("🔁 输入替换模板(如 'new_{name}_{index}.{ext}'): ").strip()
    if not replacement:
        print("❌ 未输入替换模板,退出。")
        return None

    preview_only = input("👁️ 仅预览不执行?(y/N): ").strip().lower() in ['y', 'yes', '1']
    
    return {
        "directory": directory,
        "pattern": pattern,
        "replacement": replacement,
        "preview_only": preview_only
    }

小贴士:Path 和 str 混合使用时推荐统一转为 Path,避免路径拼接错误。

第四步:文件匹配逻辑实现

我们需要根据用户输入的 pattern 匹配文件名。这里支持两种方式:

  1. 通配符(如 *.jpg)
  2. 正则表达式(如 ^photo_(\d+).jpg$)
def match_files(directory: Path, pattern: str) -> List[Path]:
    """根据模式匹配文件"""
    files = []
    dir_path = Path(directory)

    if not dir_path.exists():
        raise FileNotFoundError(f"目录不存在: {directory}")

    # 判断是否为通配符模式
    if '*' in pattern or '?' in pattern:
        # 使用 glob 模式
        files = list(dir_path.glob(pattern))
    else:
        # 否则视为正则表达式
        try:
            regex = re.compile(pattern)
            for file in dir_path.iterdir():
                if file.is_file() and regex.match(file.name):
                    files.append(file)
        except re.error as e:
            raise ValueError(f"正则表达式错误: {e}")

    return sorted(files)

关键点:

第五步:动态占位符替换引擎

这是最核心的部分!我们要让用户写类似这样的模板:

{index}_{name}_{ext}

并自动替换为实际值。

def expand_template(template: str, name: str, ext: str, index: int) -> str:
    """扩展模板中的占位符"""
    replacements = {
        "{name}": name,
        "{ext}": ext,
        "{index}": str(index),
        "{timestamp}": datetime.datetime.now().strftime("%Y%m%d_%H%M%S"),
        "{year}": datetime.datetime.now().strftime("%Y"),
        "{month}": datetime.datetime.now().strftime("%m"),
        "{day}": datetime.datetime.now().strftime("%d")
    }

    result = template
    for placeholder, value in replacements.items():
        result = result.replace(placeholder, value)

    # 处理可能存在的多余空格或非法字符
    result = re.sub(r'[<>:"/\\|?*\x00-\x1F]', '_', result)  # 过滤非法字符
    return result.strip()

示例:

模板输入输出
{name}_{index}.{ext}photo_1.jpg, index=1photo_1.1.jpg
backup_{year}-{month}-{day}_{name}data.csv, 2025backup_2025-04-05_data

注意:< > : " / \ | ? * 是操作系统保留字符,必须替换为 _ 或删除。

第六步:预览与执行分离

为了防止误操作,我们将“预览”和“执行”分开。

def preview_renames(files: List[Path], template: str) -> None:
    """预览重命名结果"""
    print("\n📋 预览即将重命名的文件(共 {} 个):".format(len(files)))
    print("-" * 60)
    
    for i, file_path in enumerate(files, start=1):
        name = file_path.stem
        ext = file_path.suffix
        new_name = expand_template(template, name, ext, i)
        new_path = file_path.parent / new_name
        
        print(f"{i:2d}. {file_path.name} → {new_name}")
    
    print("-" * 60)

 提示:如果用户选择“仅预览”,就直接返回,不执行任何更改。

第七步:安全重命名执行 

一旦确认,才真正执行重命名。我们加入备份机制以防止意外。

def safe_rename_file(old_path: Path, new_path: Path, backup_dir: Path) -> bool:
    """安全重命名:先备份再移动"""
    try:
        # 1. 创建备份目录
        if not backup_dir.exists():
            backup_dir.mkdir(parents=True, exist_ok=True)

        # 2. 备份原始文件(避免覆盖)
        backup_path = backup_dir / f"{old_path.name}.backup"
        if backup_path.exists():
            # 如果已有备份,跳过或覆盖(这里选择覆盖)
            backup_path.unlink(missing_ok=True)
        shutil.copy2(old_path, backup_path)

        # 3. 执行重命名
        old_path.rename(new_path)
        
        print(f"✅ 成功重命名: {old_path.name} → {new_path.name}")
        return True

    except Exception as e:
        print(f"❌ 重命名失败: {old_path.name} - 错误: {e}")
        return False

为什么备份?

第八步:主逻辑整合

现在,把所有部分串联起来:

def main():
    """主函数入口"""
    config = get_user_input()
    if not config:
        return

    directory = Path(config["directory"])
    pattern = config["pattern"]
    template = config["replacement"]
    preview_only = config["preview_only"]

    # 获取文件列表
    try:
        files = match_files(directory, pattern)
    except Exception as e:
        print(f"❌ 错误: {e}")
        return

    if not files:
        print("📭 没有匹配到任何文件。")
        return

    # 显示预览
    if preview_only:
        preview_renames(files, template)
        print("👀 已完成预览,未执行重命名。")
        return

    # 创建备份目录
    backup_dir = directory / ".rename_backup"
    print(f"📁 备份目录: {backup_dir}")

    # 开始重命名
    success_count = 0
    failed_count = 0

    for i, file_path in enumerate(files, start=1):
        name = file_path.stem
        ext = file_path.suffix
        new_name = expand_template(template, name, ext, i)
        new_path = file_path.parent / new_name

        # 防止重名冲突
        if new_path.exists():
            print(f"⚠️ 目标文件已存在: {new_name},跳过...")
            failed_count += 1
            continue

        if safe_rename_file(file_path, new_path, backup_dir):
            success_count += 1
        else:
            failed_count += 1

    # 总结报告
    print("\n📊 重命名完成统计:")
    print(f"✅ 成功: {success_count}")
    print(f"❌ 失败: {failed_count}")
    print(f"📂 备份路径: {backup_dir}")

第九步:测试验证

让我们创建一个测试目录:

test_folder/
├── photo_01.jpg
├── photo_02.jpg
├── report_2024.pdf
├── data.csv
└── temp.txt

运行脚本,输入:

输出:

📋 预览即将重命名的文件(共 2 个):
------------------------------------------------------------
 1. photo_01.jpg → pic_1_photo_01.jpg
 2. photo_02.jpg → pic_2_photo_02.jpg
------------------------------------------------------------
✅ 成功重命名: photo_01.jpg → pic_1_photo_01.jpg
✅ 成功重命名: photo_02.jpg → pic_2_photo_02.jpg

📊 重命名完成统计:
✅ 成功: 2
❌ 失败: 0
📂 备份路径: test_folder/.rename_backup

高级功能拓展

1. 支持递归子目录搜索

files = list(dir_path.rglob(pattern))  # rglb 递归搜索

2. 自定义排序方式

files.sort(key=lambda x: x.stat().st_mtime, reverse=True)  # 按修改时间倒序

3. 支持 JSON 配置文件(可选)

{
  "directory": "/home/user/docs",
  "pattern": "*.pdf",
  "template": "{name}_v{index}",
  "preview": false
}

加载方式:

import json
with open('config.json') as f:
    config = json.load(f)

4. 命令行参数支持(argparse)

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--dir", required=True, help="目标目录")
parser.add_argument("--pattern", required=True, help="匹配模式")
parser.add_argument("--template", required=True, help="替换模板")
args = parser.parse_args()

实用技巧总结

技巧说明
使用 Path 而非字符串拼接更安全,跨平台兼容
用 glob 匹配通配符比手动遍历快且准确
正则表达式尽量简洁避免复杂嵌套
重命名前检查同名冲突防止覆盖
所有操作都应有日志便于排查问题
不要直接 os.rename推荐用 Path.rename() 更安全

结语:你已经掌握了进阶技能!

通过这个项目,你不仅学会了:

更重要的是,你体会到了 标准库的强大威力 —— 无需安装任何包,也能写出专业级工具。

最后提醒:谨慎操作

重命名是不可逆的操作(除非有备份)。请务必:

到此这篇关于基于Python实现一个文件批量重命名工具的文章就介绍到这了,更多相关Python文件批量重命名内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文