写在前面:这篇文章适合谁?
如果你是第一次接触 AtomCode,想在不折腾复杂工程的前提下,用 30 分钟做出一个"能跑、可复现、可扩展"的小工具,那么这篇教程适合你。
本文将带你用 AtomCode 完成一个常见需求:
- 扫描指定目录下的文件(仅当前层,不递归)
- 按规则批量重命名(安全、可预演)
- 输出一份整理报告(Markdown)
你将得到什么(最终效果)
最终你会得到一个 Python 脚本 file_organizer.py:
-
支持
dry-run预演:只生成计划与报告,不改文件 -
支持
apply执行:真正改名,并生成报告 -
报告包含统计与明细表格:原名 → 新名、状态、原因
0. 准备工作(3 分钟)
必备条件
- 已安装 Python 3.9+(建议 3.11)
- 已安装并能打开 AtomCode(或 AtomCodeAir)
新建练习目录(避免误操作)
建议新建一个干净目录,确保不会误改重要文件:
atomcode-file-organizer/
samples/
raw/
(放一些你要整理的测试文件)
file_organizer.py
在 `samples/raw` 里放 5~10 个测试文件(图片、压缩包、文档都行),用于验证脚本效果。
---
1. 在 AtomCode 里创建脚本文件(2 分钟)
- 用 AtomCode 打开
atomcode-file-organizer/ - 新建文件:
file_organizer.py
接下来让 AtomCode 帮你"搭骨架 + 补细节",你只需要把规则讲清楚并做最终确认。
2. 给 AtomCode 一段"可执行、可测试"的需求描述(5 分钟)
在 AtomCode 对话框里输入(建议直接复制):
请为我生成一个 Python 脚本
file_organizer.py,要求:
- 使用 argparse 接收参数:
--root(必填,待整理目录)、--mode(dry-run/apply)、--report(可选,输出报告路径,默认 report.md)- 扫描 root 目录(只处理当前层,不递归),对文件做"安全重命名":
- 新文件名规则:
YYYYMMDD_序号_原始文件名清理版.原扩展名(序号从 001 开始)- 清理规则:去掉多余空格;把非字母数字、非中文、非
._-的字符替换为_;连续_合并为一个;文件名最长 80- 冲突处理:若目标文件名已存在,则在序号后追加
-1、-2直到不冲突- dry-run 只打印与写报告,不改文件;apply 才执行重命名
- 报告输出 Markdown:包含统计信息(总数、成功、跳过、失败)和详细表格(原名、新名、状态、原因)
- 仅使用标准库,兼容 Windows 路径
- 提供最少但关键的异常处理与可读输出
请先给出完整代码,再给出 2 组运行命令示例。
这一步的核心是:把规则写死。新手最常踩的坑就是"需求太口语",导致脚本看似能用,实际会在边界条件翻车。
3. 把 AtomCode 生成的代码写入文件(1 分钟)
将 AtomCode 输出的完整代码粘贴到 file_organizer.py 并保存。
如果它漏了某个函数或输出不全,不要让它重写一遍整个文件,推荐补充式提问:
- "请补全缺失的
main()并保持已有结构不变" - "请补全报告 Markdown 表格输出部分,保持参数不变"
4. 一份可直接运行的参考实现(仅标准库)
下面是一份满足上述需求的实现,你可以直接使用;如果你已经让 AtomCode 生成了代码,也可以用它来对照检查关键点是否一致(参数、模式、冲突处理、报告输出等)。
安全提醒:首次运行务必用
--mode dry-run。
python
import argparse
import datetime as dt
import os
import re
from dataclasses import dataclass
from pathlib import Path
from typing import List, Tuple
@dataclass
class ItemResult:
src_name: str
dst_name: str
status: str # success / skipped / failed
reason: str
def sanitize_stem(stem: str, max_len: int = 80) -> str:
s = stem.strip()
s = re.sub(r"\s+", " ", s)
s = re.sub(r"[^0-9A-Za-z\u4e00-\u9fff\.\_\- ]+", "_", s)
s = s.replace(" ", "_")
s = re.sub(r"_+", "_", s)
s = s.strip("._-")
if not s:
s = "unnamed"
if len(s) > max_len:
s = s[:max_len].rstrip("._-")
return s if s else "unnamed"
def resolve_conflict(dst: Path) -> Path:
if not dst.exists():
return dst
parent = dst.parent
stem = dst.stem
suffix = dst.suffix
i = 1
while True:
candidate = parent / f"{stem}-{i}{suffix}"
if not candidate.exists():
return candidate
i += 1
def build_target_name(date_prefix: str, index: int, src: Path) -> str:
idx = f"{index:03d}"
clean_stem = sanitize_stem(src.stem, max_len=80)
return f"{date_prefix}_{idx}_{clean_stem}{src.suffix}"
def list_files_non_recursive(root: Path) -> List[Path]:
items: List[Path] = []
for p in root.iterdir():
if p.is_file():
items.append(p)
return sorted(items, key=lambda x: x.name.lower())
def to_markdown_report(root: Path, mode: str, results: List[ItemResult]) -> str:
total = len(results)
success = sum(1 for r in results if r.status == "success")
skipped = sum(1 for r in results if r.status == "skipped")
failed = sum(1 for r in results if r.status == "failed")
lines: List[str] = []
lines.append("# 文件整理报告")
lines.append("")
lines.append(f"- 根目录:`{root}`")
lines.append(f"- 模式:`{mode}`")
lines.append(f"- 生成时间:`{dt.datetime.now().isoformat(timespec='seconds')}`")
lines.append("")
lines.append("## 统计")
lines.append("")
lines.append(f"- 总数:{total}")
lines.append(f"- 成功:{success}")
lines.append(f"- 跳过:{skipped}")
lines.append(f"- 失败:{failed}")
lines.append("")
lines.append("## 明细")
lines.append("")
lines.append("| 原文件名 | 新文件名 | 状态 | 原因 |")
lines.append("|---|---|---|---|")
for r in results:
lines.append(f"| `{r.src_name}` | `{r.dst_name}` | {r.status} | {r.reason} |")
lines.append("")
return "\n".join(lines)
def organize(root: Path, mode: str) -> Tuple[List[ItemResult], List[str]]:
logs: List[str] = []
results: List[ItemResult] = []
if not root.exists():
raise FileNotFoundError(f"root not found: {root}")
if not root.is_dir():
raise NotADirectoryError(f"root is not a directory: {root}")
files = list_files_non_recursive(root)
date_prefix = dt.datetime.now().strftime("%Y%m%d")
logs.append(f"[INFO] found {len(files)} file(s) in {root}")
index = 1
for src in files:
try:
target_name = build_target_name(date_prefix, index, src)
dst = root / target_name
dst = resolve_conflict(dst)
if src.name == dst.name:
results.append(ItemResult(src.name, dst.name, "skipped", "same name"))
logs.append(f"[SKIP] {src.name} -> {dst.name} (same name)")
else:
if mode == "apply":
os.replace(str(src), str(dst))
results.append(ItemResult(src.name, dst.name, "success", "renamed"))
logs.append(f"[OK] {src.name} -> {dst.name}")
else:
results.append(ItemResult(src.name, dst.name, "success", "dry-run planned"))
logs.append(f"[PLAN] {src.name} -> {dst.name}")
index += 1
except Exception as e:
results.append(ItemResult(src.name, "", "failed", repr(e)))
logs.append(f"[ERR] {src.name} failed: {e!r}")
return results, logs
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="Batch rename files and generate a report.")
p.add_argument("--root", required=True, help="Target directory to organize (non-recursive).")
p.add_argument("--mode", choices=["dry-run", "apply"], default="dry-run", help="dry-run or apply.")
p.add_argument("--report", default="report.md", help="Report output path (Markdown).")
return p.parse_args()
def main() -> None:
args = parse_args()
root = Path(args.root).expanduser()
results, logs = organize(root=root, mode=args.mode)
report_path = Path(args.report).expanduser()
report_text = to_markdown_report(root=root, mode=args.mode, results=results)
report_path.write_text(report_text, encoding="utf-8")
for line in logs:
print(line)
print(f"[INFO] report written to: {report_path.resolve()}")
if __name__ == "__main__":
main()
5. 运行与检查(8 分钟)
第一次:dry-run 预演(不改文件)
bash
python file_organizer.py --root "D:\atomcode-file-organizer\samples\raw" --mode dry-run --report "D:\atomcode-file-organizer\report.md"
检查点:
- 终端输出是否出现
[PLAN] ... -> ... report.md是否生成- 报告"明细表格"里是否每行都有原名、新名、状态、原因
第二次:apply 执行(真正改名)
确认预演无误后执行:
bash
python file_organizer.py --root "D:\atomcode-file-organizer\samples\raw" --mode apply --report "D:\atomcode-file-organizer\report.md"
检查点:
- 目录里文件名是否已变为
YYYYMMDD_001_...的形式 - 冲突文件是否自动追加
-1/-2 - 报告统计(成功/跳过/失败)是否符合实际
6. 新手常见问题(以及如何让 AtomCode 解决)
6.1 最容易翻车:直接 apply
建议养成固定流程:
dry-run生成计划与报告- 你确认映射没问题
- 再
apply
你也可以让 AtomCode 增强安全性,例如新增 --confirm yes 才允许执行 apply。
6.2 清理规则不明确会导致"看似能用,实际混乱"
文件名清理涉及大量边界情况(空格、特殊字符、全是符号、超长文件名)。本文把规则写进代码里,并且:
- 为空时兜底为
unnamed - 连续
_合并 - 统一截断到 80 字符
6.3 冲突处理要可预期
真实目录里经常出现重复文件名或相近文件名。冲突策略要稳定且可解释:本文通过 -1/-2/... 处理,报告也会记录最终名称,方便追溯。
7. 下一步怎么练(可选拓展)
当你跑通本教程后,推荐用 AtomCode 再做 1 个小迭代(保持增量修改):
- 增加
--recursive:递归整理子目录 - 增加
--csv:输出 CSV 报告便于 Excel 查看 - 增加
--rollback-script:生成回滚脚本,避免误操作无法恢复
结语
用 AtomCode 学习上手的关键不是"让它写更多代码",而是:
- 把需求约束讲清楚
- 用
dry-run做可验证闭环 - 小步增量迭代
只要你能把"规则"和"验证方式"写出来,AtomCode 就能稳定地帮你把想法落地为可运行代码。