前置条件:本文需要安装 Claude Code。国内用户推荐使用 pateway.ai/?ch=bd95e5#(博主实测稳定不注水,价格为官网8折起步,支持 OpenAI 及 Anthropic 格式),配置好 API 端点后跟着做即可。
你的桌面现在长什么样?
我猜大概是这样的:
截图 2024-03-21 下午3.44.12.png
新建文档 (3).docx
微信图片_20240301.jpg
final_v3_FINAL_USE_THIS_actually_final.pdf
这些文件你知道是什么,但三个月后你不会知道。搜索的时候也找不到。这是每个人都有的隐形债务,攒够了就炸。
手动改?十几个文件还行,一个文件夹三百个文件你试试。
写规则脚本?截图 2024-03-21 下午3.44.12.png 你告诉我规则怎么写——它该叫什么,只有看过这张截图的人才知道。
所以我用 LLM 做了一个 Claude Code Skill,说一句话,批量预览重命名结果,确认后执行。
为什么 LLM 能做这件事
规则处理的是格式,LLM 处理的是语义。
同样是一张截图,截图 2024-03-21 下午3.44.12.png 经过 LLM 分析内容后可以变成 2024-03-21_xcode-build-error.png。这一步规则做不到,因为规则不知道截图里是什么。
对于纯文件名(没有内容可读),LLM 也能做的事是:
- 去掉冗余词:
final、副本、new、USE_THIS - 统一命名风格:全部 snake_case 或驼峰
- 加日期前缀:从文件名或内容里提取
核心代码
整个脚本不到 150 行,依赖只有一个:
pip install openai
完整代码如下:
#!/usr/bin/env python3
"""
smart-rename: LLM-powered file renaming tool.
Environment variables:
LLM_API_KEY - Required. Your API key.
LLM_BASE_URL - Optional. Custom API endpoint (e.g. a relay service).
LLM_MODEL - Optional. Model name (default: gpt-4o-mini).
"""
import argparse
import json
import os
import sys
from pathlib import Path
try:
from openai import OpenAI
except ImportError:
print("Error: openai package not found. Run: pip install openai", file=sys.stderr)
sys.exit(1)
def get_client() -> OpenAI:
api_key = os.environ.get("LLM_API_KEY")
if not api_key:
print("Error: LLM_API_KEY environment variable not set.", file=sys.stderr)
print(" export LLM_API_KEY=your-key", file=sys.stderr)
print(" export LLM_BASE_URL=https://your-relay-endpoint/v1 # if using a relay", file=sys.stderr)
sys.exit(1)
base_url = os.environ.get("LLM_BASE_URL")
return OpenAI(api_key=api_key, base_url=base_url) if base_url else OpenAI(api_key=api_key)
def read_content_snippet(path: Path, max_chars: int = 500) -> str:
text_extensions = {".txt", ".md", ".py", ".js", ".ts", ".java", ".go", ".rs", ".csv", ".json", ".xml", ".html"}
if path.suffix.lower() not in text_extensions:
return ""
try:
return path.read_text(encoding="utf-8", errors="ignore")[:max_chars]
except Exception:
return ""
def suggest_names(
files: list[Path],
style: str,
date_prefix: bool,
language: str,
client: OpenAI,
model: str,
) -> dict[str, str]:
file_info = []
for f in files:
info = {"original_name": f.name, "extension": f.suffix}
snippet = read_content_snippet(f)
if snippet:
info["content_snippet"] = snippet
file_info.append(info)
style_guide = {
"snake_case": "lowercase words separated by underscores (e.g. meeting_notes_2024.pdf)",
"camelCase": "camelCase (e.g. meetingNotes2024.pdf)",
"kebab-case": "lowercase words separated by hyphens (e.g. meeting-notes-2024.pdf)",
"chinese": "concise Chinese description (e.g. 会议记录_2024.pdf)",
}
prompt = f"""你是一个文件命名助手,为以下文件建议清晰的文件名。
命名风格:{style_guide.get(style, style)}
语言:{language}
日期前缀:{"有则添加 YYYY-MM-DD 前缀" if date_prefix else "不添加"}
规则:
- 保留原始扩展名
- 简洁但有描述性(不超过60字符)
- 去掉 截图/副本/copy/new/untitled 等冗余词
- 不使用空格
文件列表:
{json.dumps(file_info, ensure_ascii=False, indent=2)}
返回 JSON 对象,原文件名 -> 建议文件名。只返回 JSON,不要解释。"""
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
response_format={"type": "json_object"},
temperature=0.3,
)
return json.loads(response.choices[0].message.content)
def collect_files(paths: list[str], directory: str | None) -> list[Path]:
files = []
if directory:
d = Path(directory)
if not d.is_dir():
print(f"Error: {directory} is not a directory", file=sys.stderr)
sys.exit(1)
files = [f for f in d.iterdir() if f.is_file() and not f.name.startswith(".")]
else:
for p in paths:
path = Path(p)
if not path.exists():
print(f"Warning: {p} does not exist, skipping", file=sys.stderr)
continue
files.append(path)
return files
def main():
parser = argparse.ArgumentParser(description="Rename files using LLM")
parser.add_argument("--files", nargs="+", default=[], help="File paths to rename")
parser.add_argument("--dir", help="Directory to batch rename")
parser.add_argument("--style", default="snake_case",
choices=["snake_case", "camelCase", "kebab-case", "chinese"])
parser.add_argument("--date-prefix", action="store_true")
parser.add_argument("--language", default="english", choices=["english", "chinese"])
parser.add_argument("--dry-run", action="store_true", default=True)
parser.add_argument("--execute", action="store_true")
args = parser.parse_args()
if args.execute:
args.dry_run = False
files = collect_files(args.files, args.dir)
if not files:
print("No files to process.")
sys.exit(0)
client = get_client()
model = os.environ.get("LLM_MODEL", "gpt-4o-mini")
print(f"Analyzing {len(files)} file(s) with model {model}...\n")
suggestions = {}
batch_size = 20
for i in range(0, len(files), batch_size):
batch = files[i:i + batch_size]
suggestions.update(suggest_names(batch, args.style, args.date_prefix, args.language, client, model))
col_width = max((len(f.name) for f in files), default=20) + 2
print(f"{'原文件名':<{col_width}} → 建议名称")
print("-" * (col_width + 20))
for f in files:
suggested = suggestions.get(f.name, f.name)
print(f"{f.name:<{col_width}} → {suggested}")
if args.dry_run:
print("\n[预览模式] 未执行重命名。使用 --execute 参数以实际重命名。")
return
print("\n执行重命名...")
success, skipped = 0, 0
for f in files:
suggested = suggestions.get(f.name)
if not suggested or suggested == f.name:
skipped += 1
continue
new_path = f.parent / suggested
if new_path.exists():
print(f" 跳过 {f.name}:目标文件已存在")
skipped += 1
continue
f.rename(new_path)
print(f" ✓ {f.name} → {suggested}")
success += 1
print(f"\n完成:{success} 个文件已重命名,{skipped} 个跳过。")
if __name__ == "__main__":
main()
几个设计决策值得说一下:
temperature=0.3:文件命名不需要创意,要的是稳定输出,低温度减少随机性。
response_format={"type": "json_object"}:强制 JSON 输出,省去解析正则表达式的麻烦。
内容片段只取前 500 字:够 LLM 理解语义,不浪费 token。
默认 dry-run:永远先预览,不自动执行。对文件系统的操作,确认前不动。
包装成 Claude Code Skill
光有脚本还不够,每次还要记参数、记路径,和手动改名没差多少。
把它包装成 Skill 之后,使用方式变成这样:
帮我把 ~/Downloads 里的文件名整理一下,用 snake_case
Claude Code 识别意图,自动调用脚本,展示预览表格,等你确认。
Skill 的结构很简单:
smart-rename/
├── SKILL.md # 告诉 Claude 什么时候触发、怎么操作
└── scripts/
└── rename.py # 实际执行的脚本
SKILL.md 完整内容(用四个反引号包裹,避免与内部代码块冲突):
---
name: smart-rename
description: Intelligently rename files using LLM semantic analysis. Trigger when user says
things like "帮我重命名文件", "rename these files", "给文件起个好名字",
"整理文件名", "文件名太乱了", or pastes a list of messy filenames.
Works for single files, multiple files, or entire directories.
Supports snake_case, camelCase, Chinese naming, with optional date prefix.
---
# Smart File Renamer
## Workflow
1. **Clarify inputs**
- File paths, or a directory path
- Naming style: `snake_case` (default) | `camelCase` | `chinese` | `kebab-case`
- Date prefix: yes / no (default: no)
- Language: English (default) | Chinese
2. **Run the rename script**
```bash
python3 ~/.claude/skills/smart-rename/scripts/rename.py \
--files "path/to/file1" "path/to/file2" \
--style snake_case \
--dry-run
```
For a directory:
```bash
python3 ~/.claude/skills/smart-rename/scripts/rename.py \
--dir "path/to/directory" \
--style snake_case \
--dry-run
```
3. **Show preview, then confirm before executing**
- Always show the rename table first
- Re-run with `--execute` only after user confirms
把 smart-rename 目录放到 ~/.claude/skills/ 下,Claude Code 重启后自动识别。
效果
Analyzing 6 file(s) with model gpt-4o-mini...
原文件名 → 建议名称
────────────────────────────────────────────────────────────────
截图 2024-03-21 下午3.44.12.png → 2024-03-21_xcode-build-error.png
新建文档 (3).docx → project-requirements-draft.docx
微信图片_20240301.jpg → 2024-03-01_wechat-photo.jpg
final_v3_FINAL_USE_THIS_actually_final.pdf → graduation-thesis.pdf
meeting record.txt → 2024-03-15_team-meeting-notes.txt
Untitled.py → data-preprocessing-pipeline.py
[预览模式] 未执行重命名。确认后输入 yes 继续。
End
这是本专题的第一篇,后续会持续做其他日常痛点的 Skill。有想要的场景欢迎评论区告诉我。