#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 文件重命名工具 支持批量重命名、正则表达式、预览模式等功能 """ import os import re import json import logging from datetime import datetime from pathlib import Path class FileRenamer: def __init__(self, log_file="rename_log.txt"): """初始化重命名工具""" self.rename_history = [] self.setup_logging(log_file) def setup_logging(self, log_file): """设置日志记录""" logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(log_file, encoding='utf-8'), logging.StreamHandler() ] ) self.logger = logging.getLogger(__name__) def add_to_history(self, old_path, new_path): """添加重命名历史记录""" self.rename_history.append({ 'timestamp': datetime.now().isoformat(), 'old_path': old_path, 'new_path': new_path }) def save_history(self, history_file="rename_history.json"): """保存重命名历史到文件""" try: with open(history_file, 'w', encoding='utf-8') as f: json.dump(self.rename_history, f, ensure_ascii=False, indent=2) self.logger.info(f"历史记录已保存到 {history_file}") except Exception as e: self.logger.error(f"保存历史记录失败: {e}") def load_history(self, history_file="rename_history.json"): """从文件加载重命名历史""" try: if os.path.exists(history_file): with open(history_file, 'r', encoding='utf-8') as f: self.rename_history = json.load(f) self.logger.info(f"历史记录已从 {history_file} 加载") except Exception as e: self.logger.error(f"加载历史记录失败: {e}") def undo_last_rename(self): """撤销最后一次重命名操作""" if not self.rename_history: self.logger.warning("没有可撤销的操作") return False last_operation = self.rename_history.pop() old_path = last_operation['old_path'] new_path = last_operation['new_path'] # 检查文件是否存在 if not os.path.exists(new_path): self.logger.error(f"文件 {new_path} 不存在,无法撤销") return False try: os.rename(new_path, old_path) self.logger.info(f"已撤销重命名: {new_path} -> {old_path}") self.save_history() return True except Exception as e: self.logger.error(f"撤销操作失败: {e}") # 如果撤销失败,将操作重新加入历史记录 self.rename_history.append(last_operation) return False def list_files(self, directory, pattern=None): """列出目录中的文件""" try: path = Path(directory) if not path.exists(): self.logger.error(f"目录 {directory} 不存在") return [] files = [] for item in path.iterdir(): if item.is_file(): if pattern is None or re.search(pattern, item.name): files.append(item) files.sort(key=lambda x: x.name) return files except Exception as e: self.logger.error(f"列出文件时出错: {e}") return [] def simple_rename(self, directory, search_pattern, replace_pattern, preview=False): """简单的字符串替换重命名""" files = self.list_files(directory) if not files: self.logger.warning("目录中没有找到文件") return 0 renamed_count = 0 for file_path in files: old_name = file_path.name new_name = old_name.replace(search_pattern, replace_pattern) if old_name != new_name: new_path = file_path.parent / new_name # 检查目标文件是否已存在 if new_path.exists(): self.logger.warning(f"文件 {new_name} 已存在,跳过 {old_name}") continue if preview: print(f"[预览] {old_name} -> {new_name}") else: try: os.rename(file_path, new_path) self.add_to_history(str(file_path), str(new_path)) self.logger.info(f"已重命名: {old_name} -> {new_name}") renamed_count += 1 except Exception as e: self.logger.error(f"重命名 {old_name} 失败: {e}") if not preview and renamed_count > 0: self.save_history() return renamed_count def regex_rename(self, directory, regex_pattern, replace_pattern, preview=False): """使用正则表达式的重命名""" files = self.list_files(directory) if not files: self.logger.warning("目录中没有找到文件") return 0 renamed_count = 0 try: compiled_pattern = re.compile(regex_pattern) except re.error as e: self.logger.error(f"无效的正则表达式 '{regex_pattern}': {e}") return 0 for file_path in files: old_name = file_path.name # 使用正则表达式进行替换 new_name = compiled_pattern.sub(replace_pattern, old_name) if old_name != new_name: new_path = file_path.parent / new_name # 检查目标文件是否已存在 if new_path.exists(): self.logger.warning(f"文件 {new_name} 已存在,跳过 {old_name}") continue if preview: print(f"[预览] {old_name} -> {new_name}") else: try: os.rename(file_path, new_path) self.add_to_history(str(file_path), str(new_path)) self.logger.info(f"已重命名: {old_name} -> {new_name}") renamed_count += 1 except Exception as e: self.logger.error(f"重命名 {old_name} 失败: {e}") if not preview and renamed_count > 0: self.save_history() return renamed_count def add_prefix(self, directory, prefix, preview=False): """为文件名添加前缀""" files = self.list_files(directory) if not files: self.logger.warning("目录中没有找到文件") return 0 renamed_count = 0 for file_path in files: old_name = file_path.name new_name = prefix + old_name new_path = file_path.parent / new_name # 检查目标文件是否已存在 if new_path.exists(): self.logger.warning(f"文件 {new_name} 已存在,跳过 {old_name}") continue if preview: print(f"[预览] {old_name} -> {new_name}") else: try: os.rename(file_path, new_path) self.add_to_history(str(file_path), str(new_path)) self.logger.info(f"已重命名: {old_name} -> {new_name}") renamed_count += 1 except Exception as e: self.logger.error(f"重命名 {old_name} 失败: {e}") if not preview and renamed_count > 0: self.save_history() return renamed_count def add_suffix(self, directory, suffix, preview=False): """为文件名添加后缀(在扩展名之前)""" files = self.list_files(directory) if not files: self.logger.warning("目录中没有找到文件") return 0 renamed_count = 0 for file_path in files: old_name = file_path.name name_without_ext = file_path.stem ext = file_path.suffix new_name = name_without_ext + suffix + ext new_path = file_path.parent / new_name # 检查目标文件是否已存在 if new_path.exists(): self.logger.warning(f"文件 {new_name} 已存在,跳过 {old_name}") continue if preview: print(f"[预览] {old_name} -> {new_name}") else: try: os.rename(file_path, new_path) self.add_to_history(str(file_path), str(new_path)) self.logger.info(f"已重命名: {old_name} -> {new_name}") renamed_count += 1 except Exception as e: self.logger.error(f"重命名 {old_name} 失败: {e}") if not preview and renamed_count > 0: self.save_history() return renamed_count def enumerate_files(self, directory, prefix="", start_number=1, digits=3, preview=False): """为文件添加序号""" files = self.list_files(directory) if not files: self.logger.warning("目录中没有找到文件") return 0 renamed_count = 0 number = start_number for file_path in files: old_name = file_path.name ext = file_path.suffix new_name = f"{prefix}{str(number).zfill(digits)}{ext}" new_path = file_path.parent / new_name # 检查目标文件是否已存在 if new_path.exists(): self.logger.warning(f"文件 {new_name} 已存在,跳过 {old_name}") continue if preview: print(f"[预览] {old_name} -> {new_name}") else: try: os.rename(file_path, new_path) self.add_to_history(str(file_path), str(new_path)) self.logger.info(f"已重命名: {old_name} -> {new_name}") renamed_count += 1 except Exception as e: self.logger.error(f"重命名 {old_name} 失败: {e}") number += 1 if not preview and renamed_count > 0: self.save_history() return renamed_count def change_case(self, directory, case_type="lower", preview=False): """更改文件名大小写""" files = self.list_files(directory) if not files: self.logger.warning("目录中没有找到文件") return 0 renamed_count = 0 for file_path in files: old_name = file_path.name if case_type == "lower": new_name = old_name.lower() elif case_type == "upper": new_name = old_name.upper() elif case_type == "title": new_name = old_name.title() else: self.logger.error(f"不支持的大小写类型: {case_type}") return 0 if old_name != new_name: new_path = file_path.parent / new_name # 检查目标文件是否已存在 if new_path.exists(): self.logger.warning(f"文件 {new_name} 已存在,跳过 {old_name}") continue if preview: print(f"[预览] {old_name} -> {new_name}") else: try: os.rename(file_path, new_path) self.add_to_history(str(file_path), str(new_path)) self.logger.info(f"已重命名: {old_name} -> {new_name}") renamed_count += 1 except Exception as e: self.logger.error(f"重命名 {old_name} 失败: {e}") if not preview and renamed_count > 0: self.save_history() return renamed_count def main(): """主函数 - 命令行界面""" import argparse parser = argparse.ArgumentParser(description="文件重命名工具") parser.add_argument("directory", help="要处理的目录路径") parser.add_argument("-p", "--preview", action="store_true", help="预览模式,不实际重命名文件") # 子命令 subparsers = parser.add_subparsers(dest="command", help="重命名命令") # 简单替换命令 replace_parser = subparsers.add_parser("replace", help="简单字符串替换") replace_parser.add_argument("search", help="要查找的字符串") replace_parser.add_argument("replace", help="替换字符串") # 正则表达式命令 regex_parser = subparsers.add_parser("regex", help="正则表达式替换") regex_parser.add_argument("pattern", help="正则表达式模式") regex_parser.add_argument("replace", help="替换字符串") # 添加前缀命令 prefix_parser = subparsers.add_parser("prefix", help="添加前缀") prefix_parser.add_argument("prefix", help="前缀字符串") # 添加后缀命令 suffix_parser = subparsers.add_parser("suffix", help="添加后缀") suffix_parser.add_argument("suffix", help="后缀字符串") # 序号命令 enum_parser = subparsers.add_parser("enumerate", help="添加序号") enum_parser.add_argument("--prefix", default="", help="序号前缀") enum_parser.add_argument("--start", type=int, default=1, help="起始数字") enum_parser.add_argument("--digits", type=int, default=3, help="数字位数") # 大小写命令 case_parser = subparsers.add_parser("case", help="更改大小写") case_parser.add_argument("case", choices=["lower", "upper", "title"], help="大小写类型") # 撤销命令 undo_parser = subparsers.add_parser("undo", help="撤销最后一次重命名") args = parser.parse_args() # 创建重命名工具实例 renamer = FileRenamer() # 加载历史记录 renamer.load_history() # 执行相应命令 if args.command == "undo": renamer.undo_last_rename() elif args.command == "replace": count = renamer.simple_rename(args.directory, args.search, args.replace, args.preview) print(f"{'预览' if args.preview else '重命名'}了 {count} 个文件") elif args.command == "regex": count = renamer.regex_rename(args.directory, args.pattern, args.replace, args.preview) print(f"{'预览' if args.preview else '重命名'}了 {count} 个文件") elif args.command == "prefix": count = renamer.add_prefix(args.directory, args.prefix, args.preview) print(f"{'预览' if args.preview else '重命名'}了 {count} 个文件") elif args.command == "suffix": count = renamer.add_suffix(args.directory, args.suffix, args.preview) print(f"{'预览' if args.preview else '重命名'}了 {count} 个文件") elif args.command == "enumerate": count = renamer.enumerate_files(args.directory, args.prefix, args.start, args.digits, args.preview) print(f"{'预览' if args.preview else '重命名'}了 {count} 个文件") elif args.command == "case": count = renamer.change_case(args.directory, args.case, args.preview) print(f"{'预览' if args.preview else '重命名'}了 {count} 个文件") else: parser.print_help() if __name__ == "__main__": main()