first commit
This commit is contained in:
155
.gitignore
vendored
Normal file
155
.gitignore
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is an error to commit Pipfile.lock with
|
||||
# cross-platform compatibility, and it should be ignored:
|
||||
Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak
|
||||
venv.bak
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Icon?
|
||||
[Dd]esktop.ini
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Batches
|
||||
*.bat
|
||||
114
README.md
Normal file
114
README.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# 文件重命名工具
|
||||
|
||||
这是一个功能强大的Python文件重命名工具,支持命令行和图形界面两种使用方式。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 批量文件重命名
|
||||
- 简单字符串替换
|
||||
- 正则表达式替换
|
||||
- 添加前缀/后缀
|
||||
- 自动编号
|
||||
- 大小写转换
|
||||
- 预览模式
|
||||
- 操作撤销
|
||||
- 日志记录
|
||||
|
||||
## 安装要求
|
||||
|
||||
- Python 3.6+
|
||||
- 标准库(无需额外安装第三方包)
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 命令行版本
|
||||
|
||||
```bash
|
||||
# 查看帮助
|
||||
python file_renamer.py -h
|
||||
|
||||
# 简单替换
|
||||
python file_renamer.py /path/to/directory replace "old_text" "new_text"
|
||||
|
||||
# 正则表达式替换
|
||||
python file_renamer.py /path/to/directory regex "\d+" "#"
|
||||
|
||||
# 添加前缀
|
||||
python file_renamer.py /path/to/directory prefix "PRE_"
|
||||
|
||||
# 添加后缀
|
||||
python file_renamer.py /path/to/directory suffix "_SUF"
|
||||
|
||||
# 自动编号
|
||||
python file_renamer.py /path/to/directory enumerate --prefix="IMG_" --start=1 --digits=3
|
||||
|
||||
# 大小写转换
|
||||
python file_renamer.py /path/to/directory case lower
|
||||
|
||||
# 预览模式(不实际重命名)
|
||||
python file_renamer.py /path/to/directory replace "old" "new" --preview
|
||||
|
||||
# 撤销上次操作
|
||||
python file_renamer.py /path/to/directory undo
|
||||
```
|
||||
|
||||
### 图形界面版本
|
||||
|
||||
```bash
|
||||
python gui_renamer.py
|
||||
```
|
||||
|
||||
图形界面提供了直观的操作方式,支持以下功能:
|
||||
|
||||
1. 选择目录
|
||||
2. 多种重命名模式切换
|
||||
3. 预览功能
|
||||
4. 实时结果显示
|
||||
5. 撤销操作
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 示例1:批量替换文件名中的日期格式
|
||||
|
||||
假设有一批文件名为 `report_2023-01-01.txt`,想改为 `report_20230101.txt`:
|
||||
|
||||
```bash
|
||||
python file_renamer.py /path/to/files replace "-" ""
|
||||
```
|
||||
|
||||
### 示例2:使用正则表达式清理文件名
|
||||
|
||||
移除文件名中的数字:
|
||||
|
||||
```bash
|
||||
python file_renamer.py /path/to/files regex "\d+" ""
|
||||
```
|
||||
|
||||
### 示例3:为照片文件添加前缀和编号
|
||||
|
||||
```bash
|
||||
python file_renamer.py /path/to/photos enumerate --prefix="PHOTO_" --start=1 --digits=4
|
||||
```
|
||||
|
||||
这会将文件重命名为:`PHOTO_0001.jpg`, `PHOTO_0002.png`, ...
|
||||
|
||||
### 示例4:统一文件名大小写
|
||||
|
||||
```bash
|
||||
python file_renamer.py /path/to/files case lower
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 操作前建议先使用预览模式查看结果
|
||||
2. 工具会自动避免文件名冲突
|
||||
3. 所有操作都会记录到日志文件中
|
||||
4. 支持通过撤销功能回退最后一次操作
|
||||
5. 正则表达式功能需要熟悉基本的正则语法
|
||||
|
||||
## 日志和历史记录
|
||||
|
||||
- 日志文件:`rename_log.txt`
|
||||
- 历史记录:`rename_history.json`
|
||||
|
||||
可以通过历史记录文件查看所有重命名操作,并在需要时手动恢复文件名。
|
||||
63
example_usage.py
Normal file
63
example_usage.py
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
文件重命名工具使用示例
|
||||
"""
|
||||
|
||||
from file_renamer import FileRenamer
|
||||
|
||||
|
||||
def main():
|
||||
"""使用示例"""
|
||||
# 创建重命名工具实例
|
||||
renamer = FileRenamer()
|
||||
|
||||
# 设置要处理的目录
|
||||
directory = "./test_files" # 请修改为实际的目录路径
|
||||
|
||||
print("文件重命名工具使用示例")
|
||||
print("=" * 30)
|
||||
|
||||
# 1. 简单字符串替换
|
||||
print("1. 简单字符串替换:")
|
||||
print("将目录中所有文件名中的 'old' 替换为 'new'")
|
||||
count = renamer.simple_rename(directory, "old", "new", preview=True)
|
||||
print(f"预览模式下将重命名 {count} 个文件")
|
||||
|
||||
# 2. 正则表达式替换
|
||||
print("\n2. 正则表达式替换:")
|
||||
print("将文件名中的数字替换为 # 符号")
|
||||
count = renamer.regex_rename(directory, r"\d+", "#", preview=True)
|
||||
print(f"预览模式下将重命名 {count} 个文件")
|
||||
|
||||
# 3. 添加前缀
|
||||
print("\n3. 添加前缀:")
|
||||
print("为所有文件添加 'PREFIX_' 前缀")
|
||||
count = renamer.add_prefix(directory, "PREFIX_", preview=True)
|
||||
print(f"预览模式下将重命名 {count} 个文件")
|
||||
|
||||
# 4. 添加后缀
|
||||
print("\n4. 添加后缀:")
|
||||
print("为所有文件添加 '_SUFFIX' 后缀")
|
||||
count = renamer.add_suffix(directory, "_SUFFIX", preview=True)
|
||||
print(f"预览模式下将重命名 {count} 个文件")
|
||||
|
||||
# 5. 自动编号
|
||||
print("\n5. 自动编号:")
|
||||
print("为文件添加编号")
|
||||
count = renamer.enumerate_files(directory, prefix="FILE_", start_number=1, digits=3, preview=True)
|
||||
print(f"预览模式下将重命名 {count} 个文件")
|
||||
|
||||
# 6. 大小写转换
|
||||
print("\n6. 大小写转换:")
|
||||
print("将所有文件名转换为小写")
|
||||
count = renamer.change_case(directory, "lower", preview=True)
|
||||
print(f"预览模式下将重命名 {count} 个文件")
|
||||
|
||||
print("\n" + "=" * 30)
|
||||
print("如需实际执行重命名,请将 preview 参数设为 False")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
422
file_renamer.py
Normal file
422
file_renamer.py
Normal file
@@ -0,0 +1,422 @@
|
||||
#!/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()
|
||||
294
gui_renamer.py
Normal file
294
gui_renamer.py
Normal file
@@ -0,0 +1,294 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
图形界面文件重命名工具
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog, messagebox, scrolledtext
|
||||
import os
|
||||
import threading
|
||||
from file_renamer import FileRenamer
|
||||
|
||||
|
||||
class GuiRenamer:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("文件重命名工具")
|
||||
self.root.geometry("800x600")
|
||||
|
||||
# 创建重命名工具实例
|
||||
self.renamer = FileRenamer()
|
||||
self.renamer.load_history()
|
||||
|
||||
# 当前选择的目录
|
||||
self.current_directory = ""
|
||||
|
||||
# 创建界面
|
||||
self.create_widgets()
|
||||
|
||||
def create_widgets(self):
|
||||
"""创建界面组件"""
|
||||
# 主框架
|
||||
main_frame = ttk.Frame(self.root, padding="10")
|
||||
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
|
||||
# 配置网格权重
|
||||
self.root.columnconfigure(0, weight=1)
|
||||
self.root.rowconfigure(0, weight=1)
|
||||
main_frame.columnconfigure(1, weight=1)
|
||||
main_frame.rowconfigure(4, weight=1)
|
||||
|
||||
# 目录选择
|
||||
ttk.Label(main_frame, text="选择目录:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||
self.dir_var = tk.StringVar()
|
||||
self.dir_entry = ttk.Entry(main_frame, textvariable=self.dir_var, width=50)
|
||||
self.dir_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||
ttk.Button(main_frame, text="浏览", command=self.browse_directory).grid(row=0, column=2, padx=(5, 0), pady=5)
|
||||
|
||||
# 分隔线
|
||||
ttk.Separator(main_frame, orient=tk.HORIZONTAL).grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
|
||||
|
||||
# 重命名选项卡
|
||||
self.notebook = ttk.Notebook(main_frame)
|
||||
self.notebook.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
|
||||
|
||||
# 简单替换选项卡
|
||||
self.create_replace_tab()
|
||||
|
||||
# 正则表达式选项卡
|
||||
self.create_regex_tab()
|
||||
|
||||
# 添加前缀/后缀选项卡
|
||||
self.create_prefix_suffix_tab()
|
||||
|
||||
# 序号选项卡
|
||||
self.create_enumerate_tab()
|
||||
|
||||
# 大小写选项卡
|
||||
self.create_case_tab()
|
||||
|
||||
# 预览按钮
|
||||
self.preview_btn = ttk.Button(main_frame, text="预览", command=self.preview_rename)
|
||||
self.preview_btn.grid(row=3, column=1, sticky=tk.E, pady=10)
|
||||
|
||||
# 执行按钮
|
||||
self.execute_btn = ttk.Button(main_frame, text="执行重命名", command=self.execute_rename)
|
||||
self.execute_btn.grid(row=3, column=2, padx=(5, 0), pady=10)
|
||||
|
||||
# 结果显示区域
|
||||
result_frame = ttk.LabelFrame(main_frame, text="结果", padding="5")
|
||||
result_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
|
||||
result_frame.columnconfigure(0, weight=1)
|
||||
result_frame.rowconfigure(0, weight=1)
|
||||
|
||||
self.result_text = scrolledtext.ScrolledText(result_frame, wrap=tk.WORD, height=15)
|
||||
self.result_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
|
||||
# 撤销按钮
|
||||
self.undo_btn = ttk.Button(main_frame, text="撤销上次操作", command=self.undo_last_rename)
|
||||
self.undo_btn.grid(row=5, column=0, pady=10)
|
||||
|
||||
# 清空结果按钮
|
||||
ttk.Button(main_frame, text="清空结果", command=self.clear_result).grid(row=5, column=2, pady=10)
|
||||
|
||||
def create_replace_tab(self):
|
||||
"""创建简单替换选项卡"""
|
||||
frame = ttk.Frame(self.notebook, padding="10")
|
||||
frame.columnconfigure(1, weight=1)
|
||||
|
||||
ttk.Label(frame, text="查找:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||
self.replace_search_var = tk.StringVar()
|
||||
ttk.Entry(frame, textvariable=self.replace_search_var, width=30).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||
|
||||
ttk.Label(frame, text="替换为:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||
self.replace_replace_var = tk.StringVar()
|
||||
ttk.Entry(frame, textvariable=self.replace_replace_var, width=30).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||
|
||||
self.notebook.add(frame, text="简单替换")
|
||||
self.replace_frame = frame
|
||||
|
||||
def create_regex_tab(self):
|
||||
"""创建正则表达式选项卡"""
|
||||
frame = ttk.Frame(self.notebook, padding="10")
|
||||
frame.columnconfigure(1, weight=1)
|
||||
|
||||
ttk.Label(frame, text="正则表达式:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||
self.regex_pattern_var = tk.StringVar()
|
||||
ttk.Entry(frame, textvariable=self.regex_pattern_var, width=30).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||
|
||||
ttk.Label(frame, text="替换为:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||
self.regex_replace_var = tk.StringVar()
|
||||
ttk.Entry(frame, textvariable=self.regex_replace_var, width=30).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||
|
||||
# 正则表达式说明
|
||||
help_text = "常用正则表达式:\n\\d+ 匹配数字\n[a-zA-Z]+ 匹配字母\n.+ 匹配任意字符\n^ 匹配开头\n$ 匹配结尾"
|
||||
ttk.Label(frame, text=help_text, foreground="gray").grid(row=2, column=0, columnspan=2, sticky=tk.W, pady=(10, 0))
|
||||
|
||||
self.notebook.add(frame, text="正则表达式")
|
||||
self.regex_frame = frame
|
||||
|
||||
def create_prefix_suffix_tab(self):
|
||||
"""创建前缀/后缀选项卡"""
|
||||
frame = ttk.Frame(self.notebook, padding="10")
|
||||
frame.columnconfigure(1, weight=1)
|
||||
|
||||
ttk.Label(frame, text="前缀:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||
self.prefix_var = tk.StringVar()
|
||||
ttk.Entry(frame, textvariable=self.prefix_var, width=30).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||
|
||||
ttk.Label(frame, text="后缀:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||
self.suffix_var = tk.StringVar()
|
||||
ttk.Entry(frame, textvariable=self.suffix_var, width=30).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||
|
||||
self.notebook.add(frame, text="前缀/后缀")
|
||||
self.prefix_suffix_frame = frame
|
||||
|
||||
def create_enumerate_tab(self):
|
||||
"""创建序号选项卡"""
|
||||
frame = ttk.Frame(self.notebook, padding="10")
|
||||
frame.columnconfigure(1, weight=1)
|
||||
|
||||
ttk.Label(frame, text="前缀:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||
self.enum_prefix_var = tk.StringVar()
|
||||
ttk.Entry(frame, textvariable=self.enum_prefix_var, width=30).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||
|
||||
ttk.Label(frame, text="起始数字:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||
self.enum_start_var = tk.StringVar(value="1")
|
||||
ttk.Entry(frame, textvariable=self.enum_start_var, width=30).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||
|
||||
ttk.Label(frame, text="数字位数:").grid(row=2, column=0, sticky=tk.W, pady=5)
|
||||
self.enum_digits_var = tk.StringVar(value="3")
|
||||
ttk.Entry(frame, textvariable=self.enum_digits_var, width=30).grid(row=2, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||
|
||||
self.notebook.add(frame, text="添加序号")
|
||||
self.enumerate_frame = frame
|
||||
|
||||
def create_case_tab(self):
|
||||
"""创建大小写选项卡"""
|
||||
frame = ttk.Frame(self.notebook, padding="10")
|
||||
|
||||
ttk.Label(frame, text="选择大小写格式:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||
|
||||
self.case_var = tk.StringVar(value="lower")
|
||||
case_frame = ttk.Frame(frame)
|
||||
case_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=5)
|
||||
|
||||
ttk.Radiobutton(case_frame, text="小写 (lowercase)", variable=self.case_var, value="lower").pack(anchor=tk.W)
|
||||
ttk.Radiobutton(case_frame, text="大写 (UPPERCASE)", variable=self.case_var, value="upper").pack(anchor=tk.W)
|
||||
ttk.Radiobutton(case_frame, text="首字母大写 (Title Case)", variable=self.case_var, value="title").pack(anchor=tk.W)
|
||||
|
||||
self.notebook.add(frame, text="大小写")
|
||||
self.case_frame = frame
|
||||
|
||||
def browse_directory(self):
|
||||
"""浏览目录"""
|
||||
directory = filedialog.askdirectory()
|
||||
if directory:
|
||||
self.dir_var.set(directory)
|
||||
self.current_directory = directory
|
||||
|
||||
def preview_rename(self):
|
||||
"""预览重命名"""
|
||||
self.perform_rename(preview=True)
|
||||
|
||||
def execute_rename(self):
|
||||
"""执行重命名"""
|
||||
self.perform_rename(preview=False)
|
||||
|
||||
def perform_rename(self, preview=True):
|
||||
"""执行重命名操作"""
|
||||
# 获取当前目录
|
||||
directory = self.dir_var.get().strip()
|
||||
if not directory:
|
||||
messagebox.showerror("错误", "请选择一个目录")
|
||||
return
|
||||
|
||||
if not os.path.exists(directory):
|
||||
messagebox.showerror("错误", "选择的目录不存在")
|
||||
return
|
||||
|
||||
self.current_directory = directory
|
||||
|
||||
# 获取当前选中的选项卡
|
||||
current_tab = self.notebook.index(self.notebook.select())
|
||||
|
||||
# 在新线程中执行重命名以避免界面冻结
|
||||
thread = threading.Thread(target=self._perform_rename_thread, args=(current_tab, preview))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def _perform_rename_thread(self, tab_index, preview):
|
||||
"""在后台线程中执行重命名"""
|
||||
try:
|
||||
# 根据选项卡索引执行不同的重命名操作
|
||||
if tab_index == 0: # 简单替换
|
||||
search_pattern = self.replace_search_var.get()
|
||||
replace_pattern = self.replace_replace_var.get()
|
||||
count = self.renamer.simple_rename(self.current_directory, search_pattern, replace_pattern, preview)
|
||||
|
||||
elif tab_index == 1: # 正则表达式
|
||||
regex_pattern = self.regex_pattern_var.get()
|
||||
replace_pattern = self.regex_replace_var.get()
|
||||
count = self.renamer.regex_rename(self.current_directory, regex_pattern, replace_pattern, preview)
|
||||
|
||||
elif tab_index == 2: # 前缀/后缀
|
||||
prefix = self.prefix_var.get()
|
||||
suffix = self.suffix_var.get()
|
||||
count = 0
|
||||
if prefix:
|
||||
count += self.renamer.add_prefix(self.current_directory, prefix, preview)
|
||||
if suffix:
|
||||
count += self.renamer.add_suffix(self.current_directory, suffix, preview)
|
||||
|
||||
elif tab_index == 3: # 序号
|
||||
prefix = self.enum_prefix_var.get()
|
||||
try:
|
||||
start = int(self.enum_start_var.get() or "1")
|
||||
digits = int(self.enum_digits_var.get() or "3")
|
||||
count = self.renamer.enumerate_files(self.current_directory, prefix, start, digits, preview)
|
||||
except ValueError:
|
||||
self.show_result("错误: 起始数字或数字位数必须是整数")
|
||||
return
|
||||
|
||||
elif tab_index == 4: # 大小写
|
||||
case_type = self.case_var.get()
|
||||
count = self.renamer.change_case(self.current_directory, case_type, preview)
|
||||
|
||||
# 显示结果
|
||||
action = "预览" if preview else "重命名"
|
||||
self.show_result(f"{action}完成,共处理了 {count} 个文件")
|
||||
|
||||
except Exception as e:
|
||||
self.show_result(f"操作失败: {str(e)}")
|
||||
|
||||
def show_result(self, message):
|
||||
"""在结果区域显示信息"""
|
||||
self.result_text.insert(tk.END, f"{message}\n")
|
||||
self.result_text.see(tk.END)
|
||||
|
||||
def clear_result(self):
|
||||
"""清空结果区域"""
|
||||
self.result_text.delete(1.0, tk.END)
|
||||
|
||||
def undo_last_rename(self):
|
||||
"""撤销上次重命名操作"""
|
||||
try:
|
||||
if self.renamer.undo_last_rename():
|
||||
self.show_result("成功撤销上次重命名操作")
|
||||
else:
|
||||
self.show_result("没有可撤销的操作")
|
||||
except Exception as e:
|
||||
self.show_result(f"撤销操作失败: {str(e)}")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
root = tk.Tk()
|
||||
app = GuiRenamer(root)
|
||||
root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
146
test_renamer.py
Normal file
146
test_renamer.py
Normal file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
文件重命名工具测试脚本
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from file_renamer import FileRenamer
|
||||
|
||||
|
||||
def create_test_files(directory):
|
||||
"""创建测试文件"""
|
||||
test_files = [
|
||||
"test_file_1.txt",
|
||||
"test_file_2.txt",
|
||||
"document_2023-01-01.pdf",
|
||||
"image_001.jpg",
|
||||
"IMAGE_002.JPG",
|
||||
"Report.DOCX"
|
||||
]
|
||||
|
||||
for filename in test_files:
|
||||
filepath = os.path.join(directory, filename)
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(f"这是测试文件 {filename} 的内容")
|
||||
|
||||
|
||||
def test_simple_rename(renamer, test_dir):
|
||||
"""测试简单重命名功能"""
|
||||
print("测试简单重命名功能...")
|
||||
|
||||
# 预览模式
|
||||
count = renamer.simple_rename(test_dir, "test", "example", preview=True)
|
||||
print(f"预览模式重命名了 {count} 个文件")
|
||||
|
||||
# 实际重命名
|
||||
count = renamer.simple_rename(test_dir, "test", "example", preview=False)
|
||||
print(f"实际重命名了 {count} 个文件")
|
||||
|
||||
# 验证结果
|
||||
files = os.listdir(test_dir)
|
||||
renamed_files = [f for f in files if f.startswith("example")]
|
||||
print(f"重命名后的文件: {renamed_files}")
|
||||
|
||||
|
||||
def test_regex_rename(renamer, test_dir):
|
||||
"""测试正则表达式重命名功能"""
|
||||
print("\n测试正则表达式重命名功能...")
|
||||
|
||||
# 预览模式
|
||||
count = renamer.regex_rename(test_dir, r"\d+", "#", preview=True)
|
||||
print(f"预览模式重命名了 {count} 个文件")
|
||||
|
||||
# 实际重命名
|
||||
count = renamer.regex_rename(test_dir, r"\d+", "#", preview=False)
|
||||
print(f"实际重命名了 {count} 个文件")
|
||||
|
||||
|
||||
def test_prefix_suffix(renamer, test_dir):
|
||||
"""测试前缀/后缀功能"""
|
||||
print("\n测试前缀/后缀功能...")
|
||||
|
||||
# 添加前缀
|
||||
count = renamer.add_prefix(test_dir, "PRE_", preview=False)
|
||||
print(f"添加前缀重命名了 {count} 个文件")
|
||||
|
||||
# 添加后缀
|
||||
count = renamer.add_suffix(test_dir, "_SUF", preview=False)
|
||||
print(f"添加后缀重命名了 {count} 个文件")
|
||||
|
||||
|
||||
def test_enumerate(renamer, test_dir):
|
||||
"""测试编号功能"""
|
||||
print("\n测试编号功能...")
|
||||
|
||||
count = renamer.enumerate_files(test_dir, prefix="FILE_", start_number=1, digits=3, preview=False)
|
||||
print(f"编号重命名了 {count} 个文件")
|
||||
|
||||
|
||||
def test_case(renamer, test_dir):
|
||||
"""测试大小写转换功能"""
|
||||
print("\n测试大小写转换功能...")
|
||||
|
||||
# 转换为小写
|
||||
count = renamer.change_case(test_dir, "lower", preview=False)
|
||||
print(f"小写转换重命名了 {count} 个文件")
|
||||
|
||||
# 转换为大写
|
||||
count = renamer.change_case(test_dir, "upper", preview=False)
|
||||
print(f"大写转换重命名了 {count} 个文件")
|
||||
|
||||
|
||||
def test_undo(renamer):
|
||||
"""测试撤销功能"""
|
||||
print("\n测试撤销功能...")
|
||||
|
||||
result = renamer.undo_last_rename()
|
||||
if result:
|
||||
print("成功撤销一次操作")
|
||||
else:
|
||||
print("撤销操作失败或无操作可撤销")
|
||||
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("开始测试文件重命名工具...")
|
||||
|
||||
# 创建临时测试目录
|
||||
test_dir = tempfile.mkdtemp(prefix="renamer_test_")
|
||||
print(f"创建测试目录: {test_dir}")
|
||||
|
||||
try:
|
||||
# 创建测试文件
|
||||
create_test_files(test_dir)
|
||||
print(f"创建测试文件: {os.listdir(test_dir)}")
|
||||
|
||||
# 创建重命名工具实例
|
||||
renamer = FileRenamer(os.path.join(test_dir, "test_rename_log.txt"))
|
||||
|
||||
# 运行各项测试
|
||||
test_simple_rename(renamer, test_dir)
|
||||
test_regex_rename(renamer, test_dir)
|
||||
test_prefix_suffix(renamer, test_dir)
|
||||
test_enumerate(renamer, test_dir)
|
||||
test_case(renamer, test_dir)
|
||||
test_undo(renamer)
|
||||
|
||||
print("\n测试完成!")
|
||||
print(f"最终文件列表: {os.listdir(test_dir)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"测试过程中出现错误: {e}")
|
||||
finally:
|
||||
# 清理测试目录
|
||||
try:
|
||||
shutil.rmtree(test_dir)
|
||||
print(f"已清理测试目录: {test_dir}")
|
||||
except Exception as e:
|
||||
print(f"清理测试目录时出错: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user