如果你也负责过项目立项、实验归档、团队知识库整理,大概体会过这种尴尬:明明只是新建几个文件夹,却要一遍遍右键、新建、改名;更难受的是,过了两个月回看,发现每个项目的目录结构都不一样,当初随手建的一个子目录,现在已经说不清它到底该装什么。
我印象最深的一次,是给团队做项目初始化模板。那个结构一共二十个目录,嵌套四层,分文档、代码、数据、输出四块。手工建第一遍时,觉得还挺利索;真正让人崩溃的是第二个项目也要同样结构,第三个、第四个、第五个都是。于是我才认真琢磨“按模板批量创建文件夹并保留结构”这件事。
这里有一个值得先想清楚的关键判断:这个问题的核心不是“批量”,而是“模板”。批量只是使用方式,真正价值在于——无论谁创建、无论创建多少次,出来的目录都是同一套结构,不缺层、不乱命名、不依赖某个人凭记忆手工点。想明白这一点,写脚本反而是最后一步。
1. 先区分两件事:批量创建不是按模板创建
1.1 批量创建通常只是“一次性建很多个平级目录”
很多人遇到的需求是:把 2024-01 到 2024-12 十二个月份的文件夹一次建好,或者给三十个客户各建一个同名目录。这种需求用循环三行就能解决,核心是“快速生成大量同层级目录”,不涉及嵌套,也不涉及结构约定。
在这个层面,一条 PowerShell 或者一个 for 循环就够了。问题在于,这类脚本一旦用完,几乎不会保留下来,下次换一个场景又要重写。它没有沉淀的价值。
1.2 按模板创建意味着“每次复制同一套固定结构”
按模板创建是另一种需求。它不是建十二个平级文件夹,而是每次新建项目时,都要长出这样一棵树:
text复制项目名/
├── 00_文档/
│ ├── 01_需求
│ ├── 02_方案
│ └── 03_评审
├── 01_代码/
│ ├── src
│ └── tests
├── 02_数据/
│ ├── raw
│ └── processed
└── 03_输出/
├── reports
└── artifacts
这时候,嵌套关系、命名顺序、空目录是否保留,都变成了必须满足的约束。你要的已经不是“建得快”,而是“每一次都长一样”。批量只是把“一次建一个”变成“一次建多个同一套结构”。
1.3 为什么这个区别决定后续怎么做
如果只做平级批量创建,随便写个 mkdir 循环都能跑,遇到覆盖风险也不大。
一旦要求保留结构,脚本或命令就必须考虑几件事:空目录怎么保留、嵌套路径怎么生成、目标已存在时怎么办、复制模板时会不会带进来一堆不该带的东西。
所以我的建议是:动手写脚本之前,先把你希望生成的目录树写出来,放在项目模板里。结构先定,脚本后写。很多人的脚本越写越乱,是因为结构从来没人定义过——脚本只能生成一堆文件夹,但生成不了“约定”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三条路线:复制模板目录、列表循环、结构定义文件
2.1 路线 A:复制一个真实存在的模板目录
这是最直接、也是最适合大多数人的做法。先在本地某个固定位置建好一个 project-template 目录,里面放好完整的嵌套文件夹,还可以顺手放上 README.md、.gitignore、配置文件样例。新建项目时,把这个目录完整复制一份,再改顶层名字。
Windows 下推荐使用 robocopy:
bash复制robocopy "D:\templates\project-template" "D:\Projects\新项目" /E
/E 表示包含空目录一起复制。Linux 或 macOS 下可以用 cp -r,但更推荐 rsync,因为排除规则更灵活:
bash复制rsync -a --exclude='.git' --exclude='node_modules' --exclude='.DS_Store' ~/templates/project-template/ ~/Projects/新项目/
注意 rsync 源目录末尾的斜杠,它决定了是复制目录本身,还是复制目录里的内容。这个细节最容易踩坑。
复制法的优势是“保留结构”成了复制命令的天然结果,不需要额外写逻辑。它最大的风险,是把模板里不该带的东西也一起带过去——比如 .git 历史、缓存目录、日志文件。所以模板目录本身要干净,或者复制时明确排除。
2.2 路线 B:用项目名列表循环生成
如果模板里的子目录相对固定,但项目名很多,可以用一个文本文件保存项目名,每行一个,然后循环生成。
PowerShell 写法示意:
powershell复制$ErrorActionPreference = "Stop"
$root = "D:\Projects"
$names = Get-Content "names.txt"
foreach ($name in $names) {
$name = $name.Trim()
if (-not $name) { continue }
$base = Join-Path $root $name
if (Test-Path $base) {
Write-Host "已存在,跳过:$base"
continue
}
New-Item -ItemType Directory -Path "$base\00_文档\01_需求" -Force | Out-Null
New-Item -ItemType Directory -Path "$base\01_代码\src" -Force | Out-Null
New-Item -ItemType Directory -Path "$base\03_输出\artifacts" -Force | Out-Null
}
Bash 写法示意:
bash复制#!/usr/bin/env bash
set -euo pipefail
root="/data/projects"
while IFS= read -r name; do
name="$(echo "$name" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
[ -z "$name" ] && continue
base="$root/$name"
[ -e "$base" ] && { echo "跳过:$base"; continue; }
mkdir -p "$base/00_文档/01_需求" \
"$base/01_代码/src" \
"$base/03_输出/artifacts"
done < names.txt
这条路线的关键点是:先对一条记录验证,再放开批量。因为一旦列表里有几十个名字,中途某个名字编码异常或者路径非法,报错时你至少要能定位是哪一行出的问题。
2.3 路线 C:把模板写成结构定义文件
如果希望模板本身能放进版本库、能被多人维护、目录名里还能带变量,那就把结构定义从“目录实体”升级成“数据文件”。
比如用 Python 脚本实现:
python复制from pathlib import Path
structure = {
"00_文档": ["01_需求", "02_方案", "03_评审"],
"01_代码": ["src", "tests"],
"02_数据": ["raw", "processed"],
"03_输出": ["reports", "artifacts"],
}
def create_tree(root: Path, tree: dict) -> None:
root.mkdir(parents=True, exist_ok=True)
for name, children in tree.items():
create_tree(root / name, {child: [] for child in children})
create_tree(Path("新项目"), structure)
也可以用同一个结构写 JSON 或 YAML,通过配置文件读入。这样的好处是:模板不再依赖某台电脑上的某个目录,想改一个子目录,只需要改一次配置,然后所有通过它生成的新项目都会保持一致。
三条路线之间没有绝对优劣。我的判断是:模板里如果已经放了默认文件,优先用复制法;只需要空结构、项目名多,优先用脚本循环;模板要长期维护、多人使用,就上结构定义文件。
3. 决定“结构能不能保留”的五个细节
3.1 空文件夹会不会被丢掉
这是“保留结构”最容易翻车的地方。
如果目录结构里有些子目录注定要存在,但很长一段时间里都是空的——比如“评审”目录,项目还没开始,里面什么都没有——复制工具或脚本一旦把空目录漏掉,整个结构就缺了一层。
复制法里,robocopy /E 会保留空目录,rsync -a 也会。但如果你把模板放到 Git 仓库里维护,Git 默认不跟踪空目录。解决办法是在必须保留的空目录里放一个占位文件,常见的是 .gitkeep 或者一个解释用途的 README.txt。
使用脚本直接创建时,空目录通常不会被漏掉,因为 mkdir -p 会如实生成。最容易丢空目录的场景反而是:模板目录、压缩包、代码仓库、第三方复制工具。
3.2 项目名、换行和编码
项目名来自 names.txt 时,最容易出问题的是编码。
Windows 上如果 PowerShell 读取一个 UTF-8 无 BOM 的文本文件,中文名可能变乱码。稳妥做法是:读文件时显式指定编码,或者先用 Get-Content 打印前几行确认。Linux 同理,脚本读取文件前先确认是 UTF-8,不要混用 GBK 和 UTF-8。
还有一个隐藏问题:文件里的行尾。Windows 的 CRLF 和 Linux 的 LF 会在 read 时带入不可见字符,导致目录名里藏着一个 \r,看起来名字正常,但路径怎么都对不上。Bash 脚本里用 sed 清理首尾空格也是一种防御手段。
3.3 Windows 路径长度和保留字符
Windows 下目录名不能包含 \ / : * ? " < > |,也不能以空格或点结尾。批量生成时,只要列表里混入一个带非法字符的名字,脚本就会中断。
另一个容易踩的是路径长度。Windows 传统路径上限是 260 个字符左右,如果模板本身嵌套得很深,加上项目名很长,就可能创建失败。常见解决办法是控制模板层级,少用超长目录名;把结构做得又深又长,看起来专业,实际只会给自己添堵。
3.4 目标目录已存在时的策略
批量脚本跑第二次时,最重要的决策是:已存在的目录是跳过,还是覆盖?
我强烈建议默认“跳过”。因为目录一旦存在,里面可能有人的产出。脚本里要做显式检查,例如 PowerShell 的 Test-Path、Bash 的 -e。更重要的是,不要在脚本里随便拼接删除命令。误删的代价远大于新建的效率收益。
3.5 不要复制进不该复制的东西
模板目录如果长期使用,一定会慢慢积累一些不属于“结构”的文件:.git 历史、node_modules、__pycache__、IDE 配置、日志、临时文件。
复制法要主动排除这些。robocopy 示例:
bash复制robocopy "D:\templates\project-template" "D:\Projects\新项目" /E /XD .git node_modules .idea /XF *.log .DS_Store
rsync 示例:
bash复制rsync -a --exclude='.git' --exclude='node_modules' --exclude='.DS_Store' 模板目录/ 新项目/
这类问题不会在第一次生成时暴露,通常要到项目冲到一半,发现新项目里带着上一轮的缓存才知道。一次批量生成后,随手检查一遍有没有“多余的东西”,能省掉很多后续清理时间。
4. 我建议的落地流程:先定结构、再单点验证、最后批量
4.1 把方法固化成三步
第一步,先手工建一次模板结构,并用一个真实项目跑一遍。目的不是建好目录看图,而是确认每个子目录真的会被用上。很多团队建模板时把结构设计得特别完整,结果三个月后三分之一目录是空的。没经过项目验证的结构,只是想象出来的规范。
第二步,根据模板特征选择实现方式:有默认文件用复制法,只需要空结构且项目名多,用脚本循环,模板要长期维护,用结构定义文件。
第三步,真正批量前,先拿一条数据做“试跑”。生成后不要急着扩大规模,先去看一次目录树,确认嵌套、命名、空目录都符合预期。单次跑通,只能说明流程没有断;不等于批量一定安全。
4.2 批量前的检查清单
| 检查项 | 重点 | 发现问题怎么查 |
|---|---|---|
| 项目名来源 | 列表文件还是手工输入,编码是否一致 | 先打印前三条记录,看有没有乱码 |
| 目标根路径 | 路径是否存在、磁盘空间是否足够 | 先尝试创建一条 |
| 同名目录策略 | 跳过还是覆盖,默认建议跳过 | 跑一条看提示是否符合预期 |
| 空目录保留 | 需要空目录时有没有占位文件 | 生成后用 tree 查看 |
| 排除规则 | 模板里的缓存、历史、日志是否会被带过去 | 对比模板目录和生成目录 |
| 脚本错误处理 | 碰到失败是继续还是中断 | 观察批量中途的报错输出 |
4.3 批量后的验证方式
生成完不要只看脚本输出里写着“成功”,要亲自确认目录树。
Windows 可以用:
bash复制tree /A 新项目
Linux 可以用:
bash复制find 新项目 -type d | sort
更严格一点,可以分别导出模板目录和生成目录的目录列表,再做一次差异对比:
bash复制cd 新项目 && find . -type d | sort > /tmp/actual.txt
cd 模板目录 && find . -type d | sort > /tmp/expected.txt
diff /tmp/expected.txt /tmp/actual.txt
这个对比看起来很笨,但非常有效。尤其是当你用复制法时,它能一眼看出是不是少了某个空目录、多了某个临时目录。
5. 批量生成时的常见报错,按这条链路逐层排查
5.1 现象先分类
脚本和命令出了问题,第一步是给出准确的现象描述:
- 某些空目录没生成,但脚本没有报错。
- 新项目里出现了模板里的缓存或
.git目录。 - 项目名全部变成乱码。
- 批量执行到一半中断,后面目录没建完。
- 目录生成了,但权限不对,其他人写不进去。
每个现象指向的检查点不同。不要一上来就怀疑工具,也不要在没确认现象时反复改脚本。
5.2 推荐的排查顺序
我一般按这个链路逐层往下走:
- 先看实际产物:打开生成目录,用
tree或find看真实情况,而不是只看脚本输出。 - 再看输入:
names.txt的编码、行尾、每一行是否有多余空格、是否有非法字符。 - 再看脚本逻辑:路径拼接是否正确,变量名有没有被转义,循环体里是不是漏了
continue。 - 再看环境:Robocopy 的版本、PowerShell 执行策略、是否开启 Windows 长路径、目录写入权限。
- 最后看模板本身:模板目录里是不是本来就混入了不该复制的文件。
这套顺序的核心逻辑是:先把“命令到底干了什么”搞清楚,再去怀疑“命令写得对不对”。很多人一报错就改参数,结果方向完全反了。
5.3 一条最重要的安全建议
批量脚本不要静默执行。
每创建一个目录,至少输出一行日志;每个名字处理完,记录是成功、跳过还是失败;全部跑完后,汇总一下成功了多少、失败了多少。将来某一天要排查问题时,这份日志就是最重要的现场证据。
写脚本时加上严格模式,PowerShell 里设置 $ErrorActionPreference = "Stop",Bash 里设置 set -euo pipefail,可以让错误尽早暴露,而不是带病往下跑。
注意:批量操作里最危险的不是创建,而是覆盖和删除。所有创建逻辑都可以靠“跳过已存在”兜底,但一旦混入了删除逻辑,就一定要先做 dry-run,再真正执行。
6. 什么时候不适合这么干
6.1 结构不稳定时,模板化会变成新负担
如果团队的项目目录结构每隔几周就要调整一次,模板化并不是好主意。你会发现,保存模板、同步模板、清理模板消耗的精力,已经超过了手工建目录的成本。
模板的前提是结构已经相对稳定。它应该来自真实项目的实践,而不是某个人脑补出来的“标准”。
6.2 项目之间差异太大时,强制统一没有意义
有些工作流,每个项目的组织方式差别远大于相同点。比如研究型探索,前期根本不知道会产出什么文件;又比如每个项目都依赖不同工具链,目录结构天然不同。
这时候强行套同一套模板,反而会让目录变成摆设。模板只适合在“大量项目长得很像”的场景里发挥价值。
6.3 模板本身也要被维护
写一次脚本、建一次模板,不等于一劳永逸。模板要像代码一样被维护:放进版本库,记录变更,至少要有人为它负责。
真正长期稳定的方案,是团队所有人都认可这套结构,愿意按它归档。如果大家只是被迫接受,后期一定会出现“有人按模板建,有人随便建”的混乱局面。
回到最初那个判断:按模板批量创建文件夹并保留结构,本质上是一次很轻量的标准化。它节省的不是那几分钟,而是让每个新项目的起点都一致,让每个人都能在陌生的项目里快速找到熟悉的位置。下次动手写脚本之前,先把你想要的结构写出来,让团队确认一遍——因为脚本能生成的只是目录,能让你长期坚持的,是大家对这套结构的共同认可。
