新项目启动时,最先做的往往不是写代码,而是建文件夹。很多团队都有过这样的“返工瞬间”:产品要从三个模块扩成十个模块,于是在文件管理器里一个一个新建目录;文档规范要求 docs 下面必须同时有 design、api、manual 三个子目录,结果漏建了几个;微服务拆分后,十几个服务要使用同一套目录规范,手工创建既慢又不统一。等第一版目录建完,review 的同事一看,层级错位、命名粗细不一、空目录丢失,又得推倒重来。
按模板批量创建文件夹并保留结构,正是为了解决这类问题。它的核心不是“省几次鼠标点击”,而是把目录结构变成可复用的模板,把创建过程变成可执行的脚本,让每一次新建都能得到完全一致、符合规范的结果。这篇文章我会先讲清楚“模板化批量创建”到底在解决什么,然后给出三种由浅入深的方案:Shell 一行命令、JSON 模板加 Python 递归创建、复制模板目录保留完整结构。读完你可以直接用这些脚本跑通自己的场景。
1. 为什么要按模板批量创建文件夹
批量创建文件夹听起来没有技术含量,但真正到项目里,它往往是规范执行最容易出问题的环节。
先看几个典型场景。
第一个是模块化工程初始化。比如你要开发一个多模块的 Java 项目,约定每个模块下都有 src/main/java、src/test/java、src/main/resources、docs、scripts 五个目录。十个模块就是五十个目录,人工创建时只要少一个,后续构建脚本、自动扫描、文档发布都会踩坑。
第二个是数据治理类项目。数据目录一般有严格的层级约定,比如 data/{来源系统}/{数据域}/{数据表}/{snapshot|archive|output}。如果数据表有几十张,每一张都要建三个子目录,手工创建又累又容易漏。模板脚本可以先把层级定义好,一次生成。
第三个是课程、文档体系、模板仓库这类“以目录为资产”的项目。目录结构本身就是交付物,每次新建课程或内容专题都要重现同一套结构。这时候你需要的不是复制上次的文件夹,而是有一个标准模板,能生成新的独立目录,并且把需要替换的名称自动填进去。
我们常说的“按模板批量创建文件夹”,本质上包含三个关键要求:
- 模板化:目录结构不再靠记忆,而是记录在模板文件里,人人可见,纳入版本管理。
- 批量创建:一套结构可以重复生成,循环或递归执行,而不是手工点鼠标。
- 保留结构:不仅是“目录存在”,还包括层级关系、空目录、隐藏文件,甚至在必要时保留文件权限。
注意,很多人会误以为“批量创建文件夹”只要会 mkdir 就够了。实际上,如果只创建单个目录,那确实不需要模板;一旦目录树是多层嵌套、多个分支、多套实例,就需要用模板去描述这棵树,用脚本去生成这棵树。真正难的不是创建目录本身,而是让生成结果每次都能保持一致。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心概念:模板、批量创建与保留结构
进入实操之前,先把三个容易混淆的概念说清楚。
目录模板不是文件夹快照,而是一份结构描述。最简单的模板是一棵目录树,由节点和父子关系组成。每个节点有一个名字,节点下面可以有零个或多个子节点。模板与普通目录的区别在于:模板里允许存在占位符,例如 ${PROJECT_NAME},创建时这些占位符会被替换成实际值。
“批量创建文件夹”指的是根据这份描述,自动生成多级目录。实现方式有两种路线。第一种是“执行命令”,比如 mkdir -p a/b/c,适合固定层级、一次性的场景。第二种是“运行脚本”,脚本读取模板文件,递归创建目录树,适合需要复用、需要变量替换、需要控制流程的场景。这里的关键不是命令有多复杂,而是创建逻辑是否可重复、可审计。
“保留结构”则是最容易被忽略的部分。它至少包含四个方面:
- 保留层级关系:src/main/java 不能变成 src/main 下直接丢文件。
- 保留空目录:很多模板里会有空的 scripts、docs 目录,建完不能消失。
- 保留隐藏文件和文件占位:比如 .gitignore、.env.example、.gitkeep,它们是结构的一部分,不能被过滤掉。
- 保留权限和属性:在 Linux 环境,可执行脚本目录或文件需要有合适的权限;在 Windows 环境,可能要保留只读属性。
还有一个概念值得提前说明:占位符格式。你可以用 ${PROJECT_NAME}、{{PROJECT_NAME}}、%(PROJECT_NAME)s,解析脚本支持哪种就用哪种。这不重要,重要的是团队内统一。文章后面的示例统一使用 ${PROJECT_NAME} 作为目录名变量,使用 {{PROJECT_NAME}} 作为文件内容变量,目的是让两种场景区分开,不会混淆。
从实现层次看,模板化批量创建是一个典型的三层结构:模板层负责描述“长什么样”,解析层负责把变量替换成实际值,执行层负责创建目录并打印结果。理解了这个分层,无论用什么语言写脚本,思路都是相通的。
3. 环境准备与前置条件
这套方案对运行环境没有苛刻要求,主要准备三样东西。
操作系统方面,Windows、Linux、macOS 都可以。文章里的示例会分别给出 bash 版和 PowerShell 版,以及 Python 版。跨平台场景下,推荐把 Python 作为主力方案。
运行环境方面,建议提前准备 Python 3。版本不需要写死,文章代码使用的是标准库 pathlib、argparse、shutil、json,不依赖第三方包。如果你的机器上已经有 Python 3.8 或更高版本,直接就能运行。检查方式:
bash复制python --version
如果命令不存在,试试 python3 --version:
bash复制python3 --version
开发工具上,一个顺手的编辑器就够了。命令行终端是必须的,因为所有示例都是命令行脚本。Windows 用户如果使用 PowerShell,注意脚本运行策略;如果使用 Windows Terminal 加 Git Bash,也可以运行 bash 示例。
还需要准备一个临时测试目录,建议在 /tmp 或 ~/projects/demo 下操作,避免在项目根目录乱建目录后不好清理。所有示例都是为了验证结构是否正确,请在一个不重要的目录里测试跑通,再正式使用。
4. 方法一:Shell 一行命令批量创建固定目录结构
如果你的目录结构固定、层级不深,而且不需要变量替换,最快的方法是 Shell 命令。
在 Linux 或 macOS 的 bash/zsh 环境中,使用大括号扩展可以一条命令创建多个目录层级:
bash复制mkdir -p my_project/{src/main/java,src/test/java,docs/{api,design},scripts}
这行命令执行后会创建以下结构:
code复制my_project/
├── src/
│ ├── main/
│ │ └── java/
│ └── test/
│ └── java/
├── docs/
│ ├── api/
│ └── design/
└── scripts/
这个示例的关键在 -p 参数,它表示“没有父目录就创建父目录”,避免一层层 mkdir。大括号内的逗号分隔多个分支,大括号之间可以嵌套。bash 会把每个组合展开成一条路径,交给 mkdir 创建。
现实生活中,我见过很多开发者用 mkdir -p a b c 创建平铺目录,这没错,但一旦目录有分支,大括号扩展才是真正节约时间的写法。要注意,大括号写法只在 bash/zsh 下可用,纯 sh 环境不一定支持。需要验证是否生效时,先加 echo 看展开结果:
bash复制echo my_project/{src/main/java,src/test/java,docs/{api,design},scripts}
如果输出的列表是五条完整路径,说明环境支持大括号扩展;再换成 mkdir -p 执行,就比较稳妥。
Windows 的 PowerShell 也支持批量创建,思路是构造路径数组,再交给 New-Item:
powershell复制$dirs = @(
"src\main\java",
"src\test\java",
"docs\api",
"docs\design",
"scripts"
)
New-Item -ItemType Directory -Path $dirs -Force
注意两个细节:一是路径分隔符在 Windows 下习惯用反斜杠,二是 -Force 的作用是“目录已存在时不报错”,类似 Linux 的 -p。这段 PowerShell 会创建当前目录下的这些层级,但不会自动加上 my_project 根目录,所以如果你需要外层根目录,就把 my_project 写到路径数组里,或者在执行前先 New-Item -ItemType Directory -Path "my_project" -Force。
Shell 方案的边界在于:它适合一次性、固定目录结构。想让它“按模板可复用”,你可以包一层函数,把路径数组固化在脚本里:
bash复制create_project_dirs() {
local project_name="$1"
mkdir -p "$project_name/src/main/java" \
"$project_name/src/test/java" \
"$project_name/docs/api" \
"$project_name/docs/design" \
"$project_name/scripts"
}
这样每次只需执行 create_project_dirs demo_project。但这个方案对变量替换、文件占位、权限保留的支持都比较弱,当结构变成 JSON 描述时,我们就要进入下一个方法。
5. 方法二:JSON 模板 + Python 递归创建
如果目录结构复杂、需要复用、还要做变量替换,我推荐用 JSON 模板加 Python 脚本。这套方案的思路是:用 JSON 准确描述目录树,用 Python 递归读取并创建目录,整个过程可以先 --dry-run 预览,再真正执行。
5.1 模板文件结构设计
新建一个文件 project_template.json,内容如下:
json复制{
"name": "${PROJECT_NAME}",
"children": [
{
"name": "src",
"children": [
{
"name": "main",
"children": [
{ "name": "java", "children": [] },
{ "name": "resources", "children": [] }
]
},
{
"name": "test",
"children": [
{ "name": "java", "children": [] }
]
}
]
},
{
"name": "docs",
"children": [
{ "name": "design", "children": [] },
{ "name": "api", "children": [] }
]
},
{
"name": "scripts",
"children": []
}
]
}
模板中的 name 是当前目录名,children 是子目录列表。没有子节点时写空数组 [],千万不要把 children 写成 null,否则 Python 代码里要单独处理。
这种设计的优势是目录树可以被任何语言解析,不绑定 Python。你可以把模板文件放到项目的 templates/ 目录,也可以纳入 Git 仓库,和代码一起评审。
5.2 Python 递归创建脚本
新建文件 create_dirs.py,内容如下:
python复制#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
按模板批量创建文件夹并保留结构。
用法示例:
python create_dirs.py project_template.json --project demo_project --dry-run
python create_dirs.py project_template.json --project demo_project
"""
import argparse
import json
import re
from pathlib import Path
PLACEHOLDER_PATTERN = re.compile(r"\$\{(.*?)\}")
def read_template(template_path):
with open(template_path, "r", encoding="utf-8") as f:
return json.load(f)
def render_name(name, variables):
"""将目录名中的 ${VAR} 替换为实际值。"""
def replace(match):
key = match.group(1).strip()
return variables.get(key, match.group(0))
return PLACEHOLDER_PATTERN.sub(replace, name)
def create_tree(node, parent, variables, dry_run=False):
raw_name = node.get("name", "")
if not raw_name:
return
name = render_name(raw_name, variables)
current = parent / name
if dry_run:
print("create:", current)
else:
current.mkdir(parents=True, exist_ok=True)
children = node.get("children") or []
for child in children:
create_tree(child, current, variables, dry_run)
def main():
parser = argparse.ArgumentParser(description="按模板批量创建文件夹并保留结构")
parser.add_argument("template", help="JSON 模板文件路径")
parser.add_argument("--project", help="项目名,对应 ${PROJECT_NAME}")
parser.add_argument("--base-dir", default=".", help="创建根目录,默认当前目录")
parser.add_argument("--dry-run", action="store_true", help="只输出计划,不实际创建")
args = parser.parse_args()
template = read_template(args.template)
base_dir = Path(args.base_dir).resolve()
if not base_dir.exists():
raise SystemExit(f"base dir not exists: {base_dir}")
variables = {
"PROJECT_NAME": args.project or "untitled_project",
}
create_tree(template, base_dir, variables, dry_run=args.dry_run)
if args.dry_run:
print("dry run finished, no directory created.")
if __name__ == "__main__":
main()
脚本的核心逻辑很容易读懂:read_template 读取 JSON 文件;render_name 用正则匹配 ${VAR} 并替换;create_tree 递归遍历,对每个节点先创建当前目录,再递归处理子节点。
这里有两个设计值得注意。
第一,current.mkdir(parents=True, exist_ok=True) 是保留结构的关键。parents=True 会创建路径中的所有父级目录,exist_ok=True 则让重复执行不会报错。整个脚本因此是幂等的,同一个项目名可以多次运行,不会崩溃。
第二,--dry-run 参数提供了安全网。它只打印计划创建的路径,不真正写磁盘。对任何涉及批量创建的操作,我都建议先跑一遍 dry run,确认路径没问题再真正执行。
5.3 变量替换:让模板真正可复用
目录名中的 ${PROJECT_NAME} 是模板字符串的一种常见写法。如果你愿意,也可以用 {{PROJECT_NAME}}、%(PROJECT_NAME)s,只要脚本和模板统一即可。这里选择 ${VAR} 是因为 bash 时代大家已经熟悉这种占位符风格,视觉上也不会和正则表达式混淆。
变量替换真正要注意的是替换顺序。假设你有一个目录叫 ${MODULE}-${PROJECT_NAME},脚本会在渲染目录名时一次性替换全部变量。如果你在文件名中也要替换,比如 application-${ENV}.yml,建议把变量表做成 dict,在同一个渲染函数里处理,避免某个变量先替换后改变后续匹配。
脚本目前只支持 ${PROJECT_NAME} 一种变量。你可以扩展 variables 字典,增加 MODULE_NAME、ENV、TEAM_NAME 等键,然后模板中写 ${MODULE_NAME} 即可。变量名格式建议统一为大写下划线风格,和 Linux 环境变量保持一致,降低阅读成本。
5.4 运行与验证
先在模板和脚本所在的测试目录执行 dry run:
bash复制python create_dirs.py project_template.json --project demo_project --dry-run
预期输出是一串 create: ... 路径,最后一行是 dry run finished。确认没有异常后去掉 --dry-run:
bash复制python create_dirs.py project_template.json --project demo_project
运行完成后,用 tree 命令验证结构:
bash复制tree demo_project
如果没有 tree 命令,Linux 可以使用 find demo_project -type d | sort,Windows 可以使用:
powershell复制Get-ChildItem -Recurse -Directory demo_project | Select-Object FullName
只要目录树和 JSON 模板一致,说明这次批量创建成功。这个方法的优势是模板文件可以被版本管理,脚本可以放进 CI 流程,团队里任何人创建新模块时都能得到相同结构。
6. 方法三:复制现有模板目录,完整保留结构
有时候模板不只是“目录骨架”,还包括初始化文件,比如 README.md、pom.xml、.gitignore、统一的配置样例。这类场景用 JSON 描述会非常费劲,因为你还得维护每个文件的内容。更直接的办法是:维护一个真实存在的模板目录,通过复制目录树来保留全部结构。
直接用文件管理器复制文件夹当然可以,但缺点是会带回不需要的历史文件、缓存、临时文件,而且项目名无法自动替换。更稳的做法是用 Python 的 shutil.copytree 做受控复制。
6.1 使用 shutil.copytree 复制目录树
新建脚本 copy_template.py:
python复制#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
复制模板目录,保留完整结构。
用法示例:
python copy_template.py project_template my_project
"""
import argparse
import shutil
import sys
from pathlib import Path
DEFAULT_IGNORE = shutil.ignore_patterns(
"__pycache__",
"*.pyc",
"*.pyo",
".git",
".DS_Store",
)
def main():
parser = argparse.ArgumentParser(description="复制模板目录并保留结构")
parser.add_argument("source", help="模板目录路径")
parser.add_argument("target", help="目标目录路径")
parser.add_argument("--ignore", help="额外的忽略模式,逗号分隔", default="")
args = parser.parse_args()
source = Path(args.source)
target = Path(args.target)
if not source.exists():
raise SystemExit(f"template dir not exists: {source}")
if target.exists():
raise SystemExit(f"target already exists: {target}")
extra_ignore = tuple(item.strip() for item in args.ignore.split(",") if item.strip())
ignore_pattern = None
if extra_ignore:
ignore_pattern = shutil.ignore_patterns(*extra_ignore)
else:
ignore_pattern = DEFAULT_IGNORE
try:
shutil.copytree(
source,
target,
ignore=ignore_pattern,
dirs_exist_ok=False,
)
print(f"template copied: {source} -> {target}")
except shutil.Error as exc:
print("copy failed:")
for message in exc.args if hasattr(exc, "args") else [exc]:
print(" -", message)
sys.exit(1)
if __name__ == "__main__":
main()
这段代码的亮点在于安全边界:目标目录已存在时直接退出,避免覆盖现有文件。这比某些“先删除再复制”的做法安全得多,不会误删数据。shutil.ignore_patterns 用来排除模板中不需要拷贝到新项目的内容,例如 __pycache__、.git 等。
运行方式:
bash复制python copy_template.py project_template my_project
只有打印出 template copied: project_template -> my_project 才代表成功。复制后,my_project 的目录层级、文件结构、隐藏文件都与模板一致。
6.2 忽略规则与结构保留的权衡
忽略规则是个双刃剑。忽略得太少,会把缓存和本地配置复制进新项目;忽略得太多,可能把必要的初始化文件漏掉。我的建议是:模板目录里不要放缓存文件,模板本身要保持干净;.git 一定要忽略,否则会把模板的提交历史带进新项目。
如果模板目录里需要空目录,也需要谨慎。Git 本身不跟踪空目录,所以你的模板仓库里如果有空目录,一定要在每个空目录里放一个 .gitkeep 文件。这样模板被 Git 管理后空目录不会丢,复制到新项目时 .gitkeep 也一并保留,结构就完整了。
dirs_exist_ok=False 参数在 Python 3.8 及以上版本可用,用来明确禁止往已存在目录里复制内容。如果你的 Python 版本较低,需要在调用前手动检查 target.exists(),脚本里已经写了这个检查,所以逻辑是安全的。
6.3 复制模板文件并渲染占位符
复制目录解决的是“结构保留”,但很多情况下模板文件里也写死了项目名。复制完后还需要做一次内容渲染,把文件中的 {{PROJECT_NAME}} 替换成真实项目名。
可以写一个独立的渲染脚本,在复制完成后执行:
python复制#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
在目标目录中渲染模板文件的占位符。
用法示例:
python render_files.py my_project --project demo_project
"""
import argparse
import re
from pathlib import Path
PLACEHOLDER_PATTERN = re.compile(r"\{\{(.*?)\}\}")
def render_path(source: Path, variables: dict):
for target_file in source.rglob("*"):
if not target_file.is_file():
continue
content = target_file.read_text(encoding="utf-8")
new_content = PLACEHOLDER_PATTERN.sub(
lambda m: variables.get(m.group(1).strip(), m.group(0)),
content,
)
if new_content != content:
target_file.write_text(new_content, encoding="utf-8")
print("rendered:", target_file)
def main():
parser = argparse.ArgumentParser(description="渲染模板文件占位符")
parser.add_argument("target", help="渲染目录路径")
parser.add_argument("--project", help="项目名,对应 {{PROJECT_NAME}}")
args = parser.parse_args()
target = Path(args.target)
if not target.exists():
raise SystemExit(f"target dir not exists: {target}")
variables = {
"PROJECT_NAME": args.project or "untitled_project",
}
render_path(target, variables)
if __name__ == "__main__":
main()
这个脚本用 target.rglob("*") 递归扫描所有文件,遇到包含 {{PROJECT_NAME}} 的文件就替换并写回。注意它只替换文本文件,二进制文件用 read_text 可能出问题,建议模板目录里的二进制资源单独放置,不参与渲染。
完整的工作流是:先执行复制脚本,再执行渲染脚本:
bash复制python copy_template.py project_template my_project
python render_files.py my_project --project demo_project
这样既保留了模板目录的完整结构,又让项目名真正落到了文件内容里。
7. 三种方案对比与选型建议
三种方法没有绝对的优劣,适合的场景完全不同。可以看下面的对比表:
| 维度 | Shell 大括号扩展 | JSON + Python 递归 | 复制模板目录 |
|---|---|---|---|
| 结构描述 | 写在命令参数里 | JSON 模板文件 | 基于现有目录树 |
| 目录层级复杂度 | 适合层级可控 | 适合任意层级 | 适合任意层级 |
| 变量替换 | 弱,需包函数处理 | 支持目录名和文件名 | 目录结构保留,内容需额外渲染 |
| 空目录保留 | 依赖命令写法 | 支持 | 支持,推荐配合 .gitkeep |
| 文件权限保留 | 一般 | 一般 | 相对更好 |
| 是否适合版本管理 | 脚本可入库 | 模板和脚本都可入库 | 模板目录可直接入库 |
| 上手成本 | 最低 | 中等 | 低 |
| 推荐场景 | 一次性、临时、固定结构 | 团队长期复用、复杂树形结构 | 模板包含初始化文件、需要完整样板 |
如果你只需要临时在本地建几个固定目录,用 Shell 就够了。如果团队要统一规范,每次新建模块都生成同一棵树,推荐 JSON + Python,因为模板文件清晰、可评审、可复用。如果模板不仅包含目录,还包含 README、配置样例、CI 文件,那就用复制模板目录的方式,复制后渲染变量。
实际项目中,我见过很多团队用“JSON 模板 + 复制模板目录”结合的方式:基础目录树用 JSON 定义,作为骨架创建;初始化文件单独维护一个模板目录,用 copytree 复制。这样兼顾灵活性和可维护性。
8. 常见问题与排查思路
批量创建文件夹的脚本本身不难,但真正用起来,常见的坑集中在模板描述、编码、变量替换和目录已存在这几个方向。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| JSON 模板解析失败 | 手写 JSON 时多了逗号或引号不对称 | 运行 python -m json.tool project_template.json |
用工具校验模板,或用编辑器 JSON 格式化 |
| 创建的目录层级比预期多一层 | 根节点名称与 base-dir 重复 | 打印创建路径,对比模板结构 | 确认模板根节点不需要再加 base-dir |
${PROJECT_NAME} 没有被替换 |
模板中写错占位符,例如少写了 $ |
检查 name 字段原始值 | 统一用 ${VAR} 格式,并检查变量字典 |
| 目标目录已存在,无法复制 | 复用同一项目名导致目标冲突 | 检查 target 路径是否存在 | 不要覆盖,换新名称;必要时先移动旧目录备份 |
| 空目录在 Git 仓库中消失 | Git 不跟踪空目录 | 查看模板仓库中的空目录 | 每个空目录放 .gitkeep 文件 |
| 文件内容中的项目名没有替换 | 只跑了复制脚本,忘了渲染脚本 | 打开复制后的文件查看占位符 | 执行渲染脚本,确认正则格式一致 |
| 中文目录名或内容乱码 | 终端编码与文件编码不一致 | 检查终端编码,检查文件的 encoding 参数 | 统一使用 UTF-8,脚本中显式写 encoding="utf-8" |
| Windows 下路径分隔符问题 | 脚本里混用了 / 和 \ |
打印创建路径检查 | 使用 pathlib.Path 统一处理路径 |
这里的第一个问题最容易被新手忽略:JSON 模板是给人读的,但机器解析时非常严格,一个多余逗号就会让整个脚本停摆。每次修改模板后,先跑校验再执行创建,是最省时间的习惯。
还有一点需要特别提醒:不要在生产目录、数据目录上直接测试脚本。所有示例先在临时目录或测试目录跑通,再应用到真实项目。涉及批量创建目录的脚本,必须加 --dry-run 参数,先看计划再执行。
9. 最佳实践:把模板当成工程资产
批量创建文件夹的脚本很小,但它解决的是“规范落地”的问题。要让这套方案真正有价值,建议按工程资产的标准来管理它。
第一,模板要纳入版本管理。无论选 JSON 模板还是模板目录,都要放进 Git 仓库,和项目代码一起维护。目录规范一旦形成,就成为团队约定的一部分。每次调整目录结构,应该通过代码评审,而不是某个人默默改了一个文件夹。
第二,占位符格式要统一并在 README 中写清楚。目录名和文件名使用 ${VAR},文件内容使用 {{VAR}},这个约定要在模板注释或 README 中说明。如果不统一,团队成员理解成本会很高。
第三,运行前先做 dry run。可以把 --dry-run 作为默认提示,甚至在脚本里加一个交互确认:执行前打印将要创建的目录列表,输入 y 再真正创建。这种机制能有效防止手滑创建错误结构。
第四,空目录用 .gitkeep 占位。这不仅是复制模板目录的问题,也是任何模板仓库的问题。没有 .gitkeep 的空目录提交到 Git 后会消失,下次复制模板时就少了一个目录。
第五,避免用覆盖式方案。我强烈不建议用“先删除目标再复制”的方式来处理目录冲突,风险太大。更安全的做法是:先创建临时目录,确认无误后 rename。在同一个文件系统内,rename 是原子操作,不会出现复制到一半的中间状态。Python 中可以在临时目录里执行完创建或复制,最后通过 Path.rename 替换目标。
第六,脚本要支持参数校验。项目名如果包含空格、斜杠、特殊符号,目录创建可能出错或留下奇葩路径。建议在脚本里增加正则校验,例如只允许字母、数字、下划线、中划线:
python复制import re
def validate_project_name(name):
if not re.fullmatch(r"[A-Za-z0-9_-]+", name):
raise SystemExit(f"invalid project name: {name}")
第七,把脚本封装成交互式 CLI 是自然的下一步。你可以在此基础上增加多个模板选择、项目名提示、创建后输出 tree my_project 等能力。但不要为了“脚手架”而过度设计,批量创建文件夹的需求规模很小,保持脚本简单、透明、可靠,比做一个复杂工具更有价值。
第八,日志要可追溯。脚本打印的每一个 created 路径,既是运行记录,也是后续排错的依据。如果脚本要接入 CI 或自动化流程,建议把输出写进日志文件。
10. 总结
按模板批量创建文件夹并保留结构,这个需求听起来简单,做起来有几个很实际的分寸要把握好:模板描述要清晰,创建过程要可复用,变量替换要统一,空目录和隐藏文件不能丢,目标已存在时不能乱删。我用三种方案分别覆盖了不同复杂度:Shell 适合临时快速创建,JSON 加 Python 适合团队长期复用,复制模板目录适合需要完整初始文件的场景。
下一步建议你做三件事:第一,把文中的 Python 脚本拿下来,在自己的临时目录里跑通一次;第二,结合你的实际项目,定义一套目录模板,纳入 Git 仓库;第三,把 dry-run 变成使用习惯,再逐步把脚本扩展成交互式命令。这样,批量创建文件夹就不会是新项目开始时的意外事故,而是一次可控、可复验的规范执行。
