在静态站点生成器的生态系统中,主流工具如 Hugo、Next.js、Astro 等无一例外地依赖于复杂的运行时环境或庞大的依赖链。然而,有一类极其小众却值得关注的实现选择了一条截然不同的路径:完全使用 POSIX shell 编写,仅依赖极少数基础工具。这类生成器的核心目标并非功能完备,而是追求极致的可移植性、可审计性与 minimalism(极简主义)工程哲学的实践。
动机与设计背景
传统的静态站点生成器往往需要安装语言运行时(如 Node.js、Python、Go)或大量的第三方库。对于需要在受限环境中部署静态网站的场景 —— 例如嵌入式系统、Minimalist VPS(虚拟专用服务器)、或需要长期维护而不想被依赖更新困扰的长期项目 —— 这些工具显得过于笨重。POSIX shell SSG 的出现,正是为了解决这一痛点。它的设计假设是:任何符合 POSIX 标准的类 Unix 系统(包括 macOS、各种 Linux 发行版、OpenBSD、FreeBSD 等),都必然预装了一系列基础命令行工具,这些工具可以组合起来完成从源码到静态网页的完整转换。
POSIX shell SSG 的典型实现(如 Roman Zolotarev 的 ssg.sh、Aashvik 的 gen.sh)通常遵循一个共同的设计模式:使用 sed 进行文本处理和元数据提取,使用 find 和 cp 完成文件复制与目录遍历,使用 sort 进行内容排序,必要时引入一个极简的 Markdown 渲染器(如 lowdown 或 comrak),最后通过 shell 变量替换和管道完成模板渲染。这种设计使得整个生成器的代码量可以控制在 12KB 以下,源码可读性极高,任何熟悉 shell 脚本的开发者都可以在几分钟内理解其全部逻辑。
核心技术实现解析
元数据提取与前置元信息处理
POSIX shell SSG 面临的第一道技术挑战是如何从 Markdown 文件中提取前置元信息(frontmatter)。由于不能依赖 Python 的 PyYAML 或 Node.js 的 js-yaml 等重型库,开发者只能使用 sed 和 grep 等基础工具完成这一任务。一种常见的做法是定义一个严格但灵活的元信息格式 —— 例如,要求前置元信息必须位于文件的前六行,且每行以特定关键字开头:
---
date: 2025-10-22
title: 标题
desc: 描述
tags: tag1 tag2 tag3
cats: sw hw misc
---
提取这些信息的 shell 代码通常采用正则表达式匹配,例如:
post_date=$(sed -nE '2,6s/^date[[:space:]]*:[[:space:]]*(.*)/\1/p' "$md")
post_title=$(fix_xml "$(sed -nE '2,6s/^title[[:space:]]*:[[:space:]]*(.*)/\1/p' "$md")")
值得注意的是,为了兼容不同用户可能写出的变体(如 category、categories、cat、cats 等),开发者往往会在正则表达式中容纳多种拼写变体,这体现了 "约定优于配置" 的设计思想在极简工具中的延伸。
标签系统的无数组实现
POSIX shell(包括 dash、ash 等极简 shell)在早期标准中并不支持数组数据类型,这给需要处理多标签、多类别的场景带来了挑战。一种巧妙的解决方案是使用 "变量变量"(variable variables)技术,动态生成变量名来存储每个标签对应的文章列表:
for tag in $post_tags; do
tagn=$(get_tag_index $tag)
eval "tag_${tagn}=\"\${tag_${tagn}} ${post_slug}\""
done
其中 get_tag_index 函数通过遍历预定义的标签列表,返回目标标签在列表中的位置索引。这种实现方式虽然代码可读性略有下降,但完全避开了数组依赖,且在 POSIX shell 的约束下是唯一可行的方案。
模板渲染与环境变量替换
在模板渲染层面,POSIX shell SSG 通常避免发明专用的模板语法,而是直接利用 shell 原生的变量替换能力。envsubst 命令是这一环节的核心工具 —— 它能够读取模板文件,并将文件中出现的 ${variable} 形式的占位符替换为当前环境中同名的变量值:
export post_title post_desc post_tags_comma post_content
envsubst < template/index.html > "public/${post_slug}/index.html"
这种做法的好处是模板文件本身几乎就是合法的 HTML,不需要学习额外的模板语言;缺点是所有需要动态注入的内容都必须先导出为环境变量,这在处理大量数据时可能带来轻微的性能开销。
可移植性的代价与权衡
尽管 POSIX shell SSG 在理论上具有极高的可移植性,但在实际部署中仍然存在若干需要注意的陷阱。不同 Unix 系统的 date 命令行为存在差异:GNU date(Linux)和 BSD date(macOS、OpenBSD)对同一格式字符串的解析结果可能不同。例如,date -d "2025-10-22" +"%a, %d %b %Y" 在 Linux 上可以正常输出中文格式的星期,而在 BSD 系统上则可能报错或不输出预期结果。
sed 命令的扩展正则表达式支持程度也存在差异。GNU sed 支持 -r 参数启用扩展正则表达式,而 BSD sed 则需要使用 -E 参数。许多脚本在开发时使用了 GNU 扩展(如 \1、\2 等向后引用语法),在 BSD 系统上运行时可能需要额外的兼容性处理。
此外,尽管这些工具号称 "零依赖" 或 "单依赖",但 Markdown 到 HTML 的转换仍然需要一个外部渲染器。lowdown、comrak、或 Markdown.pl 等工具虽然在大多数系统的包管理器中都可以找到,但在某些极端受限的环境中仍可能成为部署的障碍。
工程适用场景与实践建议
POSIX shell SSG 最适合的场景包括:个人博客或小型文档站点,部署环境为仅预装基础 Unix 工具的 VPS(虚拟专用服务器);需要长期维护且不希望因依赖版本升级而频繁重构构建脚本的项目;对构建流程的每一个环节都需要完全理解和可控的开发者。
对于想要尝试这一技术栈的开发者,以下几点实践建议值得关注。首先,应尽量使用 -E(BSD 兼容)而非 -r(GNU 扩展)参数编写 sed 脚本,以确保在不同系统上的行为一致性。其次,虽然 eval 在某些场景下是实现复杂逻辑的便捷途径,但其带来的安全风险和调试困难不容忽视,应尽量寻找替代方案或在使用时进行严格的输入过滤。最后,考虑到没有增量构建的特性,应将构建脚本的运行时间控制在可接受范围内 —— 对于 30 篇左右的文章,整个构建过程应在 1 秒内完成。
POSIX shell 静态站点生成器代表了一种与主流工具链背道而驰的技术选择。它不追求功能的完备性或性能的极致,而是将可审计性、可理解性和环境无关性置于首位。这种设计哲学在工具日益复杂的当下显得尤为珍贵,也为那些希望完全掌控自己构建流程的开发者提供了一条值得探索的路径。
资料来源:本文核心技术细节参考自 Aashvik 的《an ssg written in shell》(2026 年 1 月)与 Roman Zolotarev 的 ssg.sh 项目。