背景介绍
在日常文件管理中,我们常常需要对大量文件进行重命名(如整理照片、统一文档格式)。手动逐个修改效率低下,因此开发一个批量文件重命名工具能极大提升工作效率。本文将使用Python的Tkinter库结合正则表达式,实现一个支持预览、撤销、扩展名过滤和递归处理的重命名工具。
思路分析
我们将工具拆分为以下核心模块:
1. 文件加载:选择目标文件夹,按扩展名过滤并支持递归遍历子文件夹。
2. 正则匹配与替换:通过正则表达式捕获文件名的指定部分(如日期、前缀),结合自定义格式生成新名。
3. 预览功能:重命名前展示“原→新”文件名对比,避免误操作。
4. 重命名执行:修改文件系统中的文件名,同时记录历史用于撤销。
5. 撤销功能:通过历史记录一键恢复文件原名。
6. GUI交互:使用Tkinter构建直观的操作界面,降低使用门槛。
代码实现
下面是完整的Python代码(基于Tkinter、os、re模块):
import tkinter as tk
from tkinter import ttk, filedialog
import os
import re
class BatchRenamer(tk.Tk):
def __init__(self):
super().__init__()
self.title("批量文件重命名工具")
self.geometry("800x600")
self.files = [] # 存储加载的文件信息:path, original_name
self.preview = [] # 预览的文件重命名方案
self.history = [] # 重命名历史,用于撤销
self.init_ui()
def init_ui(self):
# 文件夹选择区域
frame_folder = ttk.LabelFrame(self, text="文件夹选择")
frame_folder.pack(fill="x", padx=10, pady=5)
ttk.Button(frame_folder, text="选择文件夹", command=self.select_folder).pack(side="left", padx=5, pady=5)
self.folder_path = tk.StringVar()
ttk.Entry(frame_folder, textvariable=self.folder_path, width=50).pack(side="left", padx=5, pady=5)
ttk.Button(frame_folder, text="加载文件", command=self.load_files).pack(side="left", padx=5, pady=5)
# 过滤与递归选项
frame_filter = ttk.LabelFrame(self, text="过滤与递归")
frame_filter.pack(fill="x", padx=10, pady=5)
ttk.Label(frame_filter, text="扩展名(逗号分隔,如 jpg,png):").pack(side="left", padx=5, pady=5)
self.extensions = tk.StringVar(value="")
ttk.Entry(frame_filter, textvariable=self.extensions, width=15).pack(side="left", padx=5, pady=5)
self.recursive = tk.BooleanVar(value=False)
ttk.Checkbutton(frame_filter, text="递归子文件夹", variable=self.recursive).pack(side="left", padx=5, pady=5)
# 正则与替换规则
frame_regex = ttk.LabelFrame(self, text="正则匹配与替换")
frame_regex.pack(fill="x", padx=10, pady=5)
ttk.Label(frame_regex, text="正则模式:").pack(side="left", padx=5, pady=5)
# 修正正则:捕获扩展名作为第4组(\4)
self.regex_pattern = tk.StringVar(value=r"^\w+_(\d{4})(\d{2})(\d{2})\.(\w+)$")
ttk.Entry(frame_regex, textvariable=self.regex_pattern, width=30).pack(side="left", padx=5, pady=5)
ttk.Label(frame_regex, text="替换规则:").pack(side="left", padx=5, pady=5)
self.replace_rule = tk.StringVar(value=r"Travel_\1-\2-\3.\4") # 示例:格式化日期+保留扩展名
ttk.Entry(frame_regex, textvariable=self.replace_rule, width=30).pack(side="left", padx=5, pady=5)
# 功能按钮
frame_buttons = ttk.Frame(self)
frame_buttons.pack(fill="x", padx=10, pady=5)
ttk.Button(frame_buttons, text="预览", command=self.preview_rename).pack(side="left", padx=5)
ttk.Button(frame_buttons, text="重命名", command=self.execute_rename).pack(side="left", padx=5)
self.undo_button = ttk.Button(frame_buttons, text="撤销", command=self.undo_rename, state="disabled")
self.undo_button.pack(side="left", padx=5)
# 预览/历史列表
frame_list = ttk.LabelFrame(self, text="预览/历史")
frame_list.pack(fill="both", expand=True, padx=10, pady=5)
self.listbox = tk.Listbox(frame_list, width=80, height=20)
self.listbox.pack(side="left", fill="both", expand=True)
scrollbar = ttk.Scrollbar(frame_list, orient="vertical", command=self.listbox.yview)
scrollbar.pack(side="right", fill="y")
self.listbox.config(yscrollcommand=scrollbar.set)
def select_folder(self):
folder = filedialog.askdirectory()
if folder:
self.folder_path.set(folder)
def load_files(self):
folder = self.folder_path.get()
if not folder:
self.listbox.insert(tk.END, "请先选择文件夹!")
return
# 处理扩展名过滤
ext_filter = self.extensions.get().strip()
exts = [e.strip().lower() for e in ext_filter.split(",")] if ext_filter else []
recursive = self.recursive.get()
self.files = []
self.listbox.delete(0, tk.END) # 清空列表
try:
if recursive:
# 递归遍历所有子文件夹
for root, _, files in os.walk(folder):
for file in files:
file_path = os.path.join(root, file)
# 过滤扩展名
if not exts or any(file.lower().endswith(ext) for ext in exts):
self.files.append({"path": file_path, "original": file})
else:
# 仅遍历当前文件夹
for file in os.listdir(folder):
file_path = os.path.join(folder, file)
if os.path.isfile(file_path):
if not exts or any(file.lower().endswith(ext) for ext in exts):
self.files.append({"path": file_path, "original": file})
self.listbox.insert(tk.END, f"已加载 {len(self.files)} 个文件")
except Exception as e:
self.listbox.insert(tk.END, f"加载文件出错:{e}")
def preview_rename(self):
if not self.files:
self.listbox.insert(tk.END, "请先加载文件!")
return
pattern = self.regex_pattern.get()
replace = self.replace_rule.get()
self.preview = []
self.listbox.delete(0, tk.END)
for f in self.files:
original = f["original"]
try:
# 正则替换生成新文件名
new_name = re.sub(pattern, replace, original)
self.preview.append({"original": original, "new": new_name, "path": f["path"]})
self.listbox.insert(tk.END, f"原文件名:{original} → 新文件名:{new_name}")
except re.error as e:
self.listbox.insert(tk.END, f"正则错误({original}):{e}")
self.preview.append({"original": original, "new": original, "path": f["path"]}) # 错误时保留原文件名
def execute_rename(self):
if not self.preview:
self.listbox.insert(tk.END, "请先预览!")
return
self.history = [] # 初始化历史记录
success = 0
fail = 0
for item in self.preview:
old_path = item["path"]
old_name = item["original"]
new_name = item["new"]
if old_name == new_name:
continue # 文件名未变化,跳过
new_path = os.path.join(os.path.dirname(old_path), new_name)
try:
os.rename(old_path, new_path)
self.history.append({
"old_path": old_path,
"old_name": old_name,
"new_path": new_path,
"new_name": new_name
})
success += 1
except Exception as e:
self.listbox.insert(tk.END, f"重命名失败({old_name}):{e}")
fail += 1
self.listbox.insert(tk.END, f"重命名完成:成功 {success} 个,失败 {fail} 个")
# 启用撤销按钮
self.undo_button.config(state="normal")
def undo_rename(self):
if not self.history:
self.listbox.insert(tk.END, "没有可撤销的操作!")
return
success = 0
fail = 0
# 倒序恢复,避免路径变化影响后续文件
for record in reversed(self.history):
old_path = record["old_path"]
old_name = record["old_name"]
new_path = record["new_path"]
try:
os.rename(new_path, old_path)
success += 1
except Exception as e:
self.listbox.insert(tk.END, f"撤销失败({new_path}):{e}")
fail += 1
self.listbox.insert(tk.END, f"撤销完成:成功 {success} 个,失败 {fail} 个")
self.history = [] # 清空历史
self.undo_button.config(state="disabled") # 禁用撤销按钮
if __name__ == "__main__":
app = BatchRenamer()
app.mainloop()
代码说明
- GUI布局:使用Tkinter的
LabelFrame、Button、Entry、Listbox等组件,将功能模块化(文件夹选择、过滤、正则设置、操作按钮、预览列表)。 - 文件加载:通过
os.walk(递归)或os.listdir(非递归)遍历文件,结合扩展名过滤(支持多扩展名,如jpg,png)。 - 正则替换:使用
re.sub捕获正则组(如示例中的\1为年份、\4为扩展名),生成新文件名。预览时展示替换效果,避免正则错误。 - 重命名与撤销:执行时记录文件的“原路径→新路径”,撤销时反向调用
os.rename恢复原名。
功能测试
以示例中的照片文件夹为例:
– 选择文件夹/Users/Documents/TravelPhotos,输入扩展名jpg,mp4,勾选递归(若有子文件夹)。
– 正则模式:^\w+_(\d{4})(\d{2})(\d{2})\.(\w+)$,替换规则:Travel_\1-\2-\3.\4。
– 点击“预览”,列表将显示IMG_20240501.jpg → Travel_2024-05-01.jpg等效果。
– 点击“重命名”完成修改,点击“撤销”可恢复原文件名。
总结与拓展
本文实现的批量重命名工具整合了文件操作、正则表达式、GUI交互和数据结构(列表/字典存储状态),适合整理照片、文档等场景。可拓展方向包括:
– 正则模板库:提供“提取数字”“替换前缀”等常用正则,降低使用门槛。
– 进度条显示:大文件量时展示重命名进度。
– 错误处理优化:如文件被占用时的重试机制。
通过本项目,开发者可强化Python文件操作、正则表达式和GUI开发能力,实现更高效的文件管理工具。