基于Python实现一个文件批量重命名工具
作者:知远漫谈
在日常开发和数据管理中,我们经常需要对大量文件进行重命名操作。手动一个个修改不仅耗时,还容易出错。而使用 Python 的标准库来构建一个文件批量重命名工具,不仅能提升效率,还能让你深入理解 os、pathlib、re、shutil 等核心模块的协作方式。本文将带你从零开始,一步步实现一个功能强大、安全可靠的文件批量重命名脚本。
为什么选择标准库?
“不要重复发明轮子。” —— 常见的开源工具虽多,但很多场景下,标准库已足够强大且无需额外依赖。
Python 的标准库是语言的核心组成部分,它自带了处理文件系统、正则表达式、路径操作等常用功能,完全满足我们构建文件重命名工具的需求。
优势一览:
- 无需安装第三方包(如
pathlib、os、re都是内置) - 跨平台兼容(Windows / macOS / Linux)
- 安全性高,无外部依赖风险
- 学习成本低,适合进阶练习
工具需求分析
我们要实现的是一款支持以下功能的命令行工具:
| 功能 | 描述 |
|---|---|
| 支持指定目录 | 用户可输入目标文件夹路径 |
| 模式匹配 | 支持正则表达式或通配符匹配文件名 |
| 批量重命名 | 根据规则替换文件名 |
| 占位符支持 | 如 {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 匹配文件名。这里支持两种方式:
- 通配符(如
*.jpg) - 正则表达式(如
^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)
关键点:
glob()支持*,?等通配符iterdir()遍历目录中的所有文件is_file()排除子目录- 异常捕获确保用户体验友好
第五步:动态占位符替换引擎
这是最核心的部分!我们要让用户写类似这样的模板:
{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=1 | photo_1.1.jpg |
backup_{year}-{month}-{day}_{name} | data.csv, 2025 | backup_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
运行脚本,输入:
- 目录:
test_folder - 模式:
photo_*.jpg - 替换模板:
pic_{index}_{name}.{ext} - 仅预览:N(执行)
输出:
📋 预览即将重命名的文件(共 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() 更安全 |
结语:你已经掌握了进阶技能!
通过这个项目,你不仅学会了:
- 如何使用
pathlib管理路径 - 如何用
re实现灵活匹配 - 如何设计安全的重命名流程
- 如何构建可读性强、可维护的命令行工具
更重要的是,你体会到了 标准库的强大威力 —— 无需安装任何包,也能写出专业级工具。
最后提醒:谨慎操作
重命名是不可逆的操作(除非有备份)。请务必:
- 先预览
- 保留备份
- 对重要文件提前手动备份
到此这篇关于基于Python实现一个文件批量重命名工具的文章就介绍到这了,更多相关Python文件批量重命名内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
