相信不少朋友在项目初始化、课程资料归档、测试用例准备这类场景里,都被“手工创建一堆文件夹”这件事折磨过。如果只是建三五个目录还好,鼠标点几下就结束了;但当项目模板包含十几条甚至几十条目录路径,而且每个月都要按同样结构重建一遍的时候,纯手工操作就是纯粹的体力活,不仅慢,还容易漏建、建错层级。
更麻烦的是,不同项目之间的目录结构可能只有少量差异,比如只改了课程名称、项目编号、日期前缀。如果每次都手动去建,很难保证结构一致性。本文将围绕“按模板批量创建文件夹并保留结构”这个主题,完整拆解一套可落地的 Python 脚本方案。从模板设计、路径解析、缩进层级到占位符替换、安全校验都会讲到,文末还会给出常见报错排查清单和工程化建议,新手可以直接抄,有基础的开发者也能据此扩展成自己的脚手架工具。
1. 为什么要用模板批量创建文件夹
先来说说这个需求的业务背景。很多开发者在工作里会接触到以下场景:
- 新项目启动时,需要快速生成一套约定的源码目录,比如
src/main/java、src/main/resources、src/test/java、docs、scripts等。 - 培训机构或学校老师需要为新班级、新课程准备统一的资料目录,每个班级下面可能包含
课件、作业、考试、实验、参考资料等子目录。 - 测试团队在准备测试用例时,需要按模块名建一批结构相同的目录,每个模块下再分
正向用例、反向用例、测试数据。 - 运维或交付团队在交接项目时,需要按固定结构归档文档,比如
部署文档、操作手册、配置文件备份、变更记录。
这类需求的共同特点是:目录结构是相对固定的,但需要反复套用到不同的项目名、模块名或日期上。如果手动一个个建,不仅效率低,而且很难保持两次创建的结构完全一致。把目录结构沉淀成“模板文件”,用脚本去读取并批量生成,就成了一种非常自然的解法。
从专业角度定义一下:所谓“按模板批量创建文件夹”,指的是预先定义一种描述目录层级关系的文件(比如文本文件、Excel、JSON、YAML),程序读取该文件后,解析出完整的路径列表,再自动调用操作系统的目录创建接口,一次性生成全部文件夹。核心价值有两个:
- 结构复用:同一套模板可以反复使用,不会因为人工操作而产生偏差。
- 批量执行:一次脚本运行完成几十上百个目录的创建,节省时间且降低漏建概率。
简单说,这个脚本的本质就是“把建目录的规则代码化”。
2. 整体方案设计
在写代码之前,先想清楚方案怎么做。这里我推荐使用文本模板作为目录结构的定义文件,原因有几点:
- 文本文件足够简单,不需要安装额外软件,记事本、VS Code 都能改。
- 可以用缩进表达目录层级,直观易懂。
- 文本文件天生适合放进 Git 版本管理,目录结构调整时方便对比 diff。
- 解析逻辑不复杂,几百行代码就能完成。
工作流程可以分成下面几步:
text复制编写模板文件(描述目录层级)
↓
读取模板文件
↓
按行解析路径与层级
↓
处理变量替换(如课程名、项目名)
↓
安全检查(绝对路径、跨盘符、盘符范围)
↓
调用 mkdir 批量创建目录
↓
输出创建日志
这套流程既可以实现一次性的目录生成,也可以封装成可复用的工具函数,接到项目脚手架、CI 流程里。
关于“保留结构”这个说法,需要特别解释一下。它有两层含义:
- 模板文件里定义了多少层目录,脚本就原样创建多少层,不会因为某些目录已存在就跳过下级目录的创建。
- 脚本运行结束后,磁盘上的目录层级与模板定义完全一致,不存在多建、漏建、错位的情况。
这正是本方案要解决的核心问题。
3. 环境准备与工具说明
本文的示例代码使用 Python 实现。选择 Python 的主要原因是跨平台性比较好,pathlib 模块在 Windows、Linux、macOS 上都能正确处理路径分隔符。如果你有 Python 基础,可以直接复用;如果只会 Shell 或 Java,也可以参考本文的思路自行实现。
环境建议如下:
- Python 3.8 及以上版本(使用
pathlib.Path.mkdir的exist_ok参数和parents参数)。 - 无需安装第三方依赖,标准库即可完成。
- 操作系统不限,Windows / Linux / macOS 均可。
- 示例代码在 Windows 11 + Python 3.10 环境下验证通过,其他版本差异不大。
如果你的机器上还没有 Python,请先前往 Python 官方网站下载安装包。Windows 下安装时记得勾选“Add Python to PATH”。安装完成后,打开命令行窗口,输入下面的命令确认版本:
bash复制python --version
如果能看到类似 Python 3.10.xx 的输出,说明环境已经就绪。
建议的项目文件结构如下:
text复制folder-builder/
├── templates/
│ └── project_template.txt
├── build_folders.py
└── README.md
其中 templates 目录存放模板文件,build_folders.py 是主脚本,README.md 写使用说明。如果项目比较小,不建 templates 目录也行,直接在脚本里用相对路径引用模板文件即可。
4. 模板文件设计:文本缩进表达目录层级
既然要用模板描述目录结构,第一步就要确定模板的书写规则。这里我采用“缩进 + 行首符号”的方式表达层级关系。基础规则如下:
- 每个目录占一行。
- 目录名写在行首;如果该目录有下级目录,则下级目录在下一行用更多缩进表示。
- 缩进推荐使用两个空格或一个 Tab,但不要混用。解析时统一按字符宽度计算层级。
- 以
#开头的行视为注释,程序会自动忽略。 - 空行也忽略。
- 支持在目录名中嵌入
${variable}形式的占位符,运行时由用户输入替换。
下面是一个课程资料归档的模板示例。假设每个新班级需要创建相同的资料结构:
text复制# 课程资料目录模板
${course_name}
课件
作业
平时作业
阶段作业
大作业
考试
期中考试
期末考试
实验
实验指导书
实验报告模板
学生提交
参考资料
注意观察,这里的缩进层级表示目录嵌套关系:
课件是${course_name}的直接子目录,缩进 4 个空格。平时作业是作业的子目录,缩进 8 个空格。期中考试是考试的子目录,缩进 8 个空格。实验报告模板是实验的子目录,缩进 8 个空格。
如果用树状图表示,目录层级如下:
text复制course_name/
├── 课件
├── 作业
│ ├── 平时作业
│ ├── 阶段作业
│ └── 大作业
├── 考试
│ ├── 期中考试
│ └── 期末考试
├── 实验
│ ├── 实验指导书
│ ├── 实验报告模板
│ └── 学生提交
└── 参考资料
这种模板格式有两大优点:
- 可读性强,打开模板文件就能看出目录长什么样,不需要额外工具。
- 结合 Git 使用时,模板文件的改动就是目录结构的改动,代码评审时非常清楚。
也可以把模板设计成“一行一条完整路径”的格式,比如:
text复制src/main/java
src/main/resources
src/test/java
docs
scripts
这种格式不需要解析缩进,直接用 / 作为分隔符创建目录,代码写起来更简单。但缺点也很明显:当目录层级越来越深时,重复的前缀会很多,模板的可维护性比较差。所以本文重点演示“缩进表达层级”的方式,文件路径模式作为扩展思路放在后面介绍。
5. 核心代码实现
下面进入实操环节。我会从易到难,分三个版本来写代码:
- 版本一:最基础的逐行创建脚本,适合快速使用。
- 版本二:支持缩进层级与相对路径拼接的完整版。
- 版本三:支持占位符替换和安全性校验的生产级版本。
5.1 版本一:基础路径逐行创建
先看一个最简单的方案:模板文件里每行存一条完整路径,脚本读取后直接创建。
创建模板文件 templates/simple_template.txt:
text复制src/main/java
src/main/resources
src/test/java
docs
scripts
sql
init
update
第二个写法虽然第二行有缩进,但在这个简单版本里我们暂时不处理缩进。先把每行当成独立路径来创建。
创建脚本 build_folders_v1.py:
python复制# 文件路径:build_folders_v1.py
# 基本思路:逐行读取模板文件,每一行视为一条路径,直接创建。
from pathlib import Path
# 模板文件路径,按自己的实际情况调整
TEMPLATE_FILE = Path("templates/simple_template.txt")
# 创建目录的根路径,None 表示当前目录
BASE_DIR = Path.cwd()
def build_folders_from_template(template_path: Path, base_dir: Path) -> None:
"""读取模板中的每一行,并在 base_dir 下创建对应目录。"""
if not template_path.exists():
print(f"[错误] 模板文件不存在: {template_path}")
return
created_count = 0
skipped_count = 0
with open(template_path, "r", encoding="utf-8") as f:
for line in f:
# 去掉首尾空白,忽略空行和注释
cleaned = line.strip()
if not cleaned:
continue
if cleaned.startswith("#"):
continue
# 将模板中的路径解析为 Path 对象
target_dir = base_dir.joinpath(*cleaned.split("/"))
# 创建目录,已经存在的则跳过,不会报错
target_dir.mkdir(parents=True, exist_ok=True)
created_count += 1
print(f"[创建] {target_dir}")
print(f"执行完成,共处理 {created_count} 条路径(包含已存在的目录)")
if __name__ == "__main__":
build_folders_from_template(TEMPLATE_FILE, BASE_DIR)
运行脚本:
bash复制python build_folders_v1.py
在 Windows 命令行中可能需要这样输入:
bash复制python .\build_folders_v1.py
运行后,会在当前目录下创建 src/main/java、src/main/resources、docs、scripts 等目录。注意 sql/init 和 sql/update 这两行,因为在模板里写成了一行一个路径,所以最终会创建两条独立路径。
这个版本的优点是非常简单,任何会 Python 基础的人都能看懂。缺点是模板里重复的前缀太多,目录层级多的时候写起来很累。所以下面升级到缩进解析版本。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
5.2 版本二:缩进表达层级,自动拼接路径
在这个版本里,我们用“缩进长度”表达层级关系。每一行目录的完整路径,等于“当前基准路径 + 名字部分”。程序需要记住当前每一层目录的路径,当遇到下一行时,根据缩进量决定它应该挂在哪一级下面。
具体实现思路:
- 读取每一行,计算行首空格数。
- 根据空格数算出该行所处的层级。约定每增加 4 个空格(或一个 Tab),层级加 1。
- 维护一个列表
stack,stack[0]是根目录,stack[1]是第一级子目录,依此类推。 - 当新行的层级为
level时,完整父路径就是stack[level - 1],然后把新行对应的目录追加到列表的level位置,并清理列表中后面多余的层级。 - 最后基于
base_dir创建完整路径。
举个例子,模板内容:
text复制${course_name}
课件
作业
平时作业
解析过程如下:
| 行内容 | 缩进空格数 | 层级 | 栈内容(不包含根目录) | 完整路径 |
|---|---|---|---|---|
${course_name} |
0 | 0 | ["{course_name}"] |
./{course_name} |
课件 |
4 | 1 | ["{course_name}", "课件"] |
./{course_name}/课件 |
作业 |
4 | 1 | ["{course_name}", "作业"] |
./{course_name}/作业 |
平时作业 |
8 | 2 | ["{course_name}", "作业", "平时作业"] |
./{course_name}/作业/平时作业 |
从这个表格可以看得很清楚:每一行的完整路径取决于它当前的层级以及栈里保存的父级目录名。每次读到一个新行时,栈的后半部分会被截断到当前层级,这样就不会把“平时作业”错误地挂到“课件”下面去。
实现代码如下:
python复制# 文件路径:build_folders_v2.py
# 功能:根据缩进等级创建目录结构
from pathlib import Path
TEMPLATE_FILE = Path("templates/project_template.txt")
BASE_DIR = Path.cwd()
def parse_indent(line: str) -> int:
"""计算一行开头的空格数量,用于确定层级。"""
stripped = line.lstrip(" ")
indent_count = len(line) - len(stripped)
return indent_count
def build_tree_from_template(template_path: Path, base_dir: Path) -> None:
"""读取模板,按缩进构建目录树。"""
if not template_path.exists():
print(f"[错误] 模板文件不存在: {template_path}")
return
# 每一层级对应的文件夹名称,stack 的下标表示层级深度
stack: list[str] = []
with open(template_path, "r", encoding="utf-8") as f:
for raw_line in f:
# 忽略纯空白行和注释行
line = raw_line.rstrip("\n")
stripped_line = line.strip()
if not stripped_line:
continue
if stripped_line.startswith("#"):
continue
# 计算空格缩进数,换算层级
indent = parse_indent(line)
indent_unit = 4
level = indent // indent_unit
# 如果缩进不是 indent_unit 的整数倍,给出提示但继续执行
if indent % indent_unit != 0:
print(f"[警告] 行 '{stripped_line}' 缩进不是 {indent_unit} 的整数倍,按当前缩进处理")
# 如果层级比当前栈深,说明出现缺级缩进,按最深层级处理
if level > len(stack):
level = len(stack)
# 截断栈到当前层级,stack[-1] 就是父级目录名
stack = stack[:level]
# 当前目录的完整相对路径 = 栈中目录 + 当前目录名
relative_path = Path(*stack) / stripped_line
# 最终绝对路径
target_dir = base_dir / relative_path
target_dir.mkdir(parents=True, exist_ok=True)
print(f"[创建] {target_dir}")
# 将当前目录名追加进栈,作为后续子目录的父级
stack.append(stripped_line)
print("全部目录创建完成")
if __name__ == "__main__":
build_tree_from_template(TEMPLATE_FILE, BASE_DIR)
这里有一个细节值得注意:stack = stack[:level] 的作用是把栈中超过当前层级的目录全部清掉。比如处理完“作业”之后,栈是 [course_name, 作业],此时再进入“平时作业”(缩进 8,层级 2),stack[:2] 仍然是 [course_name, 作业],所以“平时作业”会正确落在 course_name/作业/ 下面。等到读取“考试”(缩进 4,层级 1)时,stack[:1] 截断为 [course_name],于是“考试”作为 course_name 的直接子目录,不再挂在“作业”下面。
关于缩进单位,我这里默认一个层级等于 4 个空格。你也可以把 indent_unit 改成 2,或者支持 Tab。不过建议团队内部统一一种习惯,混用会导致层级解析不可预测。
5.3 版本三:带变量替换与安全检查的完整版
版本二已经能处理大多数场景了。但在真实项目中,还经常需要动态替换模板里的占位符,比如 课程名称、项目编号、年份月份。另外,为了防止手误导致在磁盘根目录、系统目录或其他非预期位置创建文件夹,有必要加两道安全校验。
版本三的功能点:
- 占位符替换:模板中形如
${course_name}的内容,由用户输入或通过命令行参数传入。 - 安全校验:不允许创建绝对路径目录;不允许创建超出根目录范围之外的目录;目录名不包含危险字符(如
:、*、?、"、<、>、|等)。 - 日志输出:打印每个创建的目录路径。
- 处理 Windows 路径过长的提示。
代码如下:
python复制# 文件路径:build_folders.py
# 功能:按模板批量创建文件夹,支持占位符替换与安全检查
import re
from pathlib import Path
TEMPLATE_FILE = Path("templates/project_template.txt")
BASE_DIR = Path.cwd()
def replace_placeholders(text: str, variables: dict) -> str:
"""替换文本中的 ${var} 占位符。"""
pattern = re.compile(r"\$\{(\w+)\}")
def repl(match):
var_name = match.group(1)
if var_name not in variables:
raise ValueError(f"缺少占位符变量: {var_name}")
return str(variables[var_name])
return pattern.sub(repl, text)
def is_safe_dir_name(name: str) -> bool:
"""检查目录名是否包含常见非法字符。"""
# Windows 不允许的字符以及空格首尾等情况
if ":" in name or "*" in name or "?" in name:
return False
if '"' in name or "<" in name or ">" in name or "|" in name:
return False
if name.startswith(" ") or name.endswith(" "):
return False
return True
def build_tree_from_template(template_path: Path, base_dir: Path, variables: dict) -> None:
"""读取模板,按缩进构建目录树,并做安全校验。"""
if not template_path.exists():
print(f"[错误] 模板文件不存在: {template_path}")
return
stack: list[str] = []
with open(template_path, "r", encoding="utf-8") as f:
for raw_line in f:
line = raw_line.rstrip("\n")
stripped_line = line.strip()
if not stripped_line:
continue
if stripped_line.startswith("#"):
continue
# 先执行变量替换
expanded_line = replace_placeholders(stripped_line, variables)
# 目录名安全校验
if not is_safe_dir_name(expanded_line):
print(f"[跳过] 目录名包含非法字符: {expanded_line}")
continue
indent = len(line) - len(line.lstrip(" "))
indent_unit = 4
level = indent // indent_unit
if indent % indent_unit != 0:
print(f"[警告] 行 '{stripped_line}' 缩进不是 {indent_unit} 的整数倍")
# 层级最多不能超过栈长度,避免半格缩进引起层级错乱
if level > len(stack):
level = len(stack)
stack = stack[:level]
relative_path = Path(*stack) / expanded_line
target_dir = base_dir / relative_path
# 安全检查:目标目录必须位于 base_dir 之下,不能是绝对路径、不能跨过根目录
try:
relative_to_base = target_dir.resolve().relative_to(base_dir.resolve())
except ValueError:
print(f"[跳过] 目标路径不在根目录范围内: {target_dir}")
continue
# 创建目录
target_dir.mkdir(parents=True, exist_ok=True)
print(f"[创建] {target_dir}")
stack.append(expanded_line)
print("全部目录创建完成")
def get_user_variables() -> dict:
"""从命令行交互中获取占位符变量的值。"""
print("请输入模板变量,直接回车则使用默认值。")
variables = {}
course_name = input("请输入课程名称(默认:新课程): ").strip()
variables["course_name"] = course_name if course_name else "新课程"
return variables
if __name__ == "__main__":
vars_map = get_user_variables()
build_tree_from_template(TEMPLATE_FILE, BASE_DIR, vars_map)
运行方式:
bash复制python build_folders.py
程序会先询问课程名称,输入后执行目录创建。比如输入“Python 进阶班”,那么最终会生成 ./Python 进阶班/课件、./Python 进阶班/作业/平时作业 等目录。
这段代码里的安全校验逻辑大家要重点理解一下。target_dir.resolve().relative_to(base_dir.resolve()) 是用来判断目标路径是否真的位于基准目录内部的。如果模板里出现了 ../ 或绝对路径,这个校验会直接抛出 ValueError,脚本会跳过这一行,避免在不小心的情况下把目录创建到根目录或者其他位置。
另外,在 Windows 上如果目录层级太深,超过 Windows 系统默认的 260 字符路径限制,mkdir 可能会报错。如果你确实需要很深的目录,可以在脚本开头加上下面的代码,启用 Windows 长路径支持:
python复制import sys
if sys.platform == "win32":
import ctypes
# 启用 Windows 长路径支持(需要管理员权限或在注册表中开启)
try:
ctypes.windll.kernel32.SetDllDirectoryA(None)
except Exception:
pass
这段代码只是给出思路。更稳妥的做法是:在使用长路径的机器上通过“组策略”启用 Win32 长路径,或者将 base_dir 尽量设定在盘符根目录附近,减少前缀长度。
6. 运行效果与验证
下面用实际运行过程演示一下。假设模板文件 templates/project_template.txt 内容如下:
text复制# 项目目录模板
${course_name}
课件
作业
平时作业
阶段作业
大作业
考试
期中考试
期末考试
参考资料
执行:
bash复制python build_folders.py
输入课程名称为“Python 自动化测试班”,预期输出:
text复制请输入课程名称,直接回车则使用默认值。
请输入课程名称(默认:新课程): Python 自动化测试班
[创建] D:\workspace\folder-builder\Python 自动化测试班
[创建] D:\workspace\folder-builder\Python 自动化测试班\课件
[创建] D:\workspace\folder-builder\Python 自动化测试班\作业
[创建] D:\workspace\folder-builder\Python 自动化测试班\作业\平时作业
[创建] D:\workspace\folder-builder\Python 自动化测试班\作业\阶段作业
[创建] D:\workspace\folder-builder\Python 自动化测试班\作业\大作业
[创建] D:\workspace\folder-builder\Python 自动化测试班\考试
[创建] D:\workspace\folder-builder\Python 自动化测试班\考试\期中考试
[创建] D:\workspace\folder-builder\Python 自动化测试班\考试\期末考试
[创建] D:\workspace\folder-builder\Python 自动化测试班\参考资料
全部目录创建完成
打开资源管理器或使用命令行查看:
bash复制tree /F
在 Windows 下输出如下(示例):
text复制folder-builder
├── Python 自动化测试班
│ ├── 课件
│ ├── 作业
│ │ ├── 平时作业
│ │ ├── 阶段作业
│ │ └── 大作业
│ ├── 考试
│ │ ├── 期中考试
│ │ └── 期末考试
│ └── 参考资料
在 Linux / macOS 下可以使用 tree 命令:
bash复制tree .
到这里,模板的基本使用闭环就跑通了。如果模板文件里没有占位符,也可以直接修改 variables 为空字典运行,效果等同于版本二。
7. 高级应用:Excel 模板驱动批量创建
除了文本模板,还有一种很常见的诉求:用户手上有一份 Excel 表格,里面列出了需要创建的目录清单,希望程序读 Excel,然后批量生成文件夹。这个需求在“以 Excel 模板数据导入”之类的数据治理项目中经常碰到。虽然本文主题是文本模板,但这里也一并提供一个基于 openpyxl 的扩展方案,方便有需要的读者。
需要说明的是:Excel 驱动与数据治理项目里的“导入校验、清洗、落库”不是一回事,这里只是借助 Excel 作为配置载体,简化人工维护文件夹清单的工作。用于大批量初始化数据治理项目目录时,也同样适用。
首先安装依赖:
bash复制pip install openpyxl
假设 Excel 文件 templates/folder_list.xlsx 内容如下:
| 列 A | 列 B | 列 C |
|---|---|---|
| 一级目录 | 二级目录 | 三级目录 |
| 项目A | 文档 | 需求文档 |
| 项目A | 文档 | 设计文档 |
| 项目A | 代码 | 后端 |
| 项目A | 代码 | 前端 |
| 项目B | 测试 | 用例 |
每一行代表一条最终路径,Excel 列越多,层级越深。
读取脚本示例:
python复制# 文件路径:build_folders_from_excel.py
# 功能:从 Excel 读取目录层级,批量创建文件夹
from pathlib import Path
from openpyxl import load_workbook
EXCEL_FILE = Path("templates/folder_list.xlsx")
BASE_DIR = Path.cwd()
def build_from_excel(excel_path: Path, base_dir: Path) -> None:
wb = load_workbook(excel_path, data_only=True)
ws = wb.active
created_count = 0
for row in ws.iter_rows(min_row=2, values_only=True):
# 过滤掉整行为空的行
parts = [str(cell).strip() for cell in row if cell is not None and str(cell).strip()]
if not parts:
continue
# 将每一行的多列拼接成相对路径
relative_path = Path(*parts)
target_dir = base_dir / relative_path
target_dir.mkdir(parents=True, exist_ok=True)
created_count += 1
print(f"[创建] {target_dir}")
print(f"Excel 目录创建完成,共处理 {created_count} 行")
if __name__ == "__main__":
build_from_excel(EXCEL_FILE, BASE_DIR)
这段代码的思路正好和文本模板相反:文本模板用缩进表达层级,Excel 用列表达层级。列顺序就是目录从高到低的顺序。实际项目中,你完全可以根据需要把文本模板和 Excel 模板结合使用:公共部分放文本模板,个性化部分用 Excel 维护。
8. 常见问题与排查思路
在实际使用过程中,大概率会遇到下面这些问题。我整理成一张排查表,方便大家快速定位。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 脚本提示模板文件不存在 | 模板路径写错或文件未放置到位 | 先确认 Path 的路径是否正确,可以在脚本开头打印 template_path.exists() |
| 创建出来的目录层级错乱 | 缩进空格数不一致或缩进层级跳跃 | 统一缩进风格,建议全部使用 4 空格;打开代码编辑器显示空白字符检查 |
| Windows 下创建失败,提示路径过长 | 目录前缀太长或嵌套太深 | 缩短根目录前缀,或启用 Windows 长路径支持 |
目录名出现 : 导致报错 |
Windows 路径中的盘符符号被当作目录名 | 在脚本中增加非法字符校验,提前跳过 |
| 模板里中文乱码 | 文件编码不是 UTF-8 | 统一用 UTF-8 编码保存模板文件,脚本指定 encoding="utf-8" |
创建的目录带有 # 字符 |
注释判断只识别的行首 # |
确保模板目录名本身不含 #,注释必须单独占一行 |
| 变量替换后目录路径为空 | ${var} 变量名写错或没有输入值 |
检查模板中的占位符名称是否与 replace_placeholders 的变量字典一致 |
| 目录已存在时脚本仍然正常退出,但看不到新目录 | 这是预期行为,exist_ok=True 不会覆盖已有目录 |
如果需要确认是否新建,可以加上“已存在则跳过”的日志 |
8.1 层级错乱问题的详细排查
层级错乱是最容易翻车的点。建议先用下面的方式自查模板缩进:
在 VS Code 中打开模板文件,按下 Ctrl+Shift+P,执行“查看:显示所有字符”(Toggle Render Whitespace),就能看到每一行的空格数量。或者用一段 Python 打印每一行的缩进步数:
python复制with open("templates/project_template.txt", "r", encoding="utf-8") as f:
for idx, line in enumerate(f, start=1):
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
indent = len(line) - len(line.lstrip(" "))
print(f"第 {idx} 行,缩进 {indent},内容 {stripped}")
如果某一层的缩进数值突然从 4 跳到 16,说明中间漏写了某一级目录,检查模板补上即可。
8.2 安全风险意识
磁盘目录的创建与删除不同,一旦在错误的位置创建了大量目录,清理起来非常麻烦。所以使用脚本自动创建目录前,务必先确认以下几点:
- 脚本中的
BASE_DIR是哪个目录?建议永远使用项目专用的目录作为根路径。 - 模板文件是可信的吗?如果模板来自外部输入,建议执行前先手动查看一遍。
- 脚本是否涉及删除或覆盖操作?本文中的脚本不会删除已有目录,但如果后续你扩展了“清空旧目录再重建”的功能,要额外小心,生产环境必须经过测试并备份。
这里更推荐的方式是:脚本默认支持 --dry-run 参数,只打印将要创建的目录,不真正执行 mkdir。在正式运行前先演练一次。用法示例:
python复制import argparse
parser = argparse.ArgumentParser(description="按模板批量创建文件夹")
parser.add_argument("--dry-run", action="store_true", help="仅打印将要创建的目录,不真正创建")
args = parser.parse_args()
然后在创建逻辑里加上判断:
python复制if args.dry_run:
print(f"[演练] 将创建目录: {target_dir}")
else:
target_dir.mkdir(parents=True, exist_ok=True)
print(f"[创建] {target_dir}")
这一步对工程化落地非常重要。
9. 最佳实践与工程建议
脚本能跑通只是第一步。如果要在团队里长期维护,下面这些建议值得认真考虑。
9.1 模板进版本管理
把模板文件和脚本一起放到 Git 仓库里。这样当目录结构调整时,可以通过 Git 提交记录看到谁在什么时候改了什么目录结构。相比口头沟通、聊天记录传递,版本管理的方式更透明、可追溯。建议目录结构调整后,在提交信息里写清楚类似于“新增 reports/2025 目录层级”的说明。
9.2 统一路径分隔符
在模板文件和脚本内部,统一使用 / 作为分隔符。pathlib 会自动转换成当前系统对应的分隔符,Windows 下创建出来的目录路径仍然显示为反斜杠 \,但代码和模板可以都用正斜杠,避免转义问题。
9.3 脚本入口参数化
不要每次运行都去修改脚本里的 BASE_DIR 和 TEMPLATE_FILE。推荐用 argparse 接收参数,例如:
bash复制python build_folders.py --template templates/project_template.txt --base /data/projects --course "Python 入门"
参数化后,脚本可以很自然地接入 CI 流水线或自动化平台,不同团队创建同一套结构时只需要传不同的参数即可。
9.4 记录日志而不是只有 print
在上线到服务器自动执行时,print 输出虽然能看到,但不易回溯。建议加入标准库 logging 模块,把每次执行的文件名、创建路径、耗时记录到日志文件。关键点:
- 日志级别分为:创建成功、跳过已存在、安全校验失败、模板解析警告。
- 每次运行生成一个带时间戳的日志文件。
- 后期出现问题时,通过日志文件定位比翻终端输出靠谱得多。
9.5 幂等性设计
所谓幂等性,指的是脚本执行多次和执行一次的结果一致。上面代码中 mkdir(parents=True, exist_ok=True) 已经保证了这一点:已存在的目录不会报错,不存在的目录自动创建。所以脚本可以安全地重复执行。
不过要注意一个边界情况:如果目录创建过程中脚本崩溃,比如在创建到一半时断电或终止,再次运行脚本时已经创建好的目录会被跳过,缺失的目录会补上,最终结果仍然能收敛到模板定义的结构。这也是“保留结构”的一种体现。
9.6 与数据治理项目结合时的注意点
如果你的使用场景是数据治理项目的目录初始化,比如按 Excel 模板导入数据后自动生成治理分类目录,要注意这里只是“文件系统目录创建”,并不代表数据权限、审批流程、数据血缘这些能力。如果需要更完整的治理功能,应该把它作为前置步骤,目录创建完成后继续接数据导入和元数据登记流程。脚本本身只负责目录结构落地,职责边界要厘清。
10. 扩展方向与学习建议
最后聊一聊这个脚本还可以往哪些方向扩展,以及你接下来可以深入学习什么。
首先是功能扩展方向。当前脚本已经支持文本模板和 Excel 模板两种输入,但还可以做更多:
- 支持 YAML / JSON 格式的目录树定义,适合和前端可视化树形编辑器联动。
- 支持创建目录后自动生成 README 或占位文件(比如
README.md、.gitkeep),方便空目录进入 Git。 - 支持在创建目录的同时生成一批配置文件,比如每个目录下的
config.yaml。 - 支持目录结构差异对比,比如模板结构和现有磁盘结构不一致时,输出差异报告。
- 支持排除某些目录,比如模板中定义但本次不创建。
- 支持 Windows 快捷方式或 Linux 软链接的自动创建。
其次是原理学习方向。如果你对“解析模板格式”这部分有兴趣,可以深入研究文本解析、词法分析、递归下降解析的基础知识。目录模板的缩进解析本质上就是一种极简的“领域特定语言”。类似地,Python 本身的缩进块解析、Markdown 标题层级解析,都用了类似的思想。把这种解析能力掌握后,你写配置解析工具、脚本生成器都会游刃有余。
最后是工程化方向。这个脚本虽然简单,但它已经包含了“配置输入、数据解析、安全校验、幂等执行、日志输出”这些常见模块。你可以把它看成一个微缩版 CLI 工具的样板。继续往下学,可以了解命令行参数框架 argparse、click、单元测试框架 pytest,以及打包发布工具 PyInstaller、setuptools。把这些技能补齐后,你就能把一个几十行的小脚本扩展成团队内部通用的“项目脚手架生成器”了。
如果你在学习和使用过程中遇到模板缩进解析不准、Windows 长路径报错、Excel 读取编码异常等问题,欢迎对照文中的排查表逐项检查。实践是最好的学习方式,动手把第一个模板跑通,再慢慢往里面加功能,你会越来越得心应手。
