一、m4 是什么? #
GUN m4 是一种通用的 宏处理器(macro processor),最初是 Unix 标准工具的一部分,后来 GNU 也实现了自己的版本(GNU m4)。
它的名字来自 “macro”,设计思想和 C 语言的预处理器(cpp)类似,但更通用、可编程、与语言无关。
🔧 简单理解: #
m4 是一个「文本宏展开器」: 它读入文本 → 按定义替换宏 → 输出展开后的文本。
但 m4 比 C 预处理器灵活得多——它本身是一种“小型解释语言”,带有:
- 宏定义与展开机制;
- 条件判断(ifdef, ifelse);
- 循环、递归;
- 参数替换、字符串操作、文件包含;
- 可生成任意文本(代码、配置、脚本)。
二、m4 解决了什么问题? #
在 1970–80 年代,Unix 世界里还没有复杂的模板引擎、元编程机制,很多项目要面对:
- 各种 平台差异(操作系统、编译器特性不同);
- 重复性代码生成;
- 配置文件或源代码模板 需要根据条件生成;
- 轻量级宏系统 需要在编译前阶段运行。
m4 的出现正是为了在「正式编译前」进行灵活的文本层面元编程。
三、历史上最典型的用途 #
1️⃣ autoconf 系统的基础 #
这是 m4 最著名的应用场景。
- GNU 的 autoconf(用于生成 configure 脚本)本质上就是一堆 .m4 宏定义。
- configure.ac 文件会通过 autoconf → m4 → 生成最终的 configure。
- 它负责检测系统特性、生成 Makefile、适配不同环境。
没有 m4,就没有 autoconf。
2️⃣ sendmail 的配置系统 #
- sendmail.cf 语法非常复杂,人类几乎无法直接写。
- 因此它使用 m4 模板(sendmail.mc)来生成最终配置。
- 管理员通过修改 .mc 文件,然后运行 m4 展开生成 .cf。
3️⃣ 早期代码生成器 / DSL #
- 有人用 m4 生成 C/C++/Fortran 代码;
- 或者生成 shell 脚本、汇编代码;
- 在没有模板引擎或 Jinja2 的时代,它是「文本元编程的瑞士军刀」。
四、语法示例 #
define(`HELLO', `Hello, world!')
HELLO
运行:
m4 hello.m4
输出:
Hello, world!
再看带参数的宏:
define(`greet', `Hello, $1!')
greet(`Alice')
greet(`Bob')
输出:
Hello, Alice!
Hello, Bob!
甚至可以写 if 逻辑:
define(`say', `ifelse($1, `yes', `You said yes', `You said no')')
say(`yes')
say(`no')
输出:
You said yes
You said no
五、现在还有实际用途吗? #
🧭 少了,但没完全消失。
✅ 仍有用的场景: #
- autoconf 系统仍在大量使用 所有基于 autotools 的开源项目(GNU coreutils、bash、curl 等)依然在用 m4 作为底层宏引擎。 👉 对这些项目贡献代码时,你会接触 .m4 文件。
- 极少数系统配置(如 sendmail)仍用 m4。
- 一些老的构建系统或科学计算代码生成场景 在 C/C++ 高性能代码生成、模板展开时还可能见到 m4 脚本。
六、现代替代与比较 #
| 功能目标 | 现代替代工具 | 相比 m4 的优劣 |
|---|---|---|
| 配置生成 / 模板化 | Jinja2、Mustache、Go template | 更直观、语法现代 |
| 构建系统适配 | CMake、Meson、Bazel | 自动化程度高、跨平台更好 |
| 代码生成 | Python、Go、Rust 脚本 | 拥有完整语言能力 |
| 轻量宏预处理 | cpp、mcpp | 更专注于 C/C++ 生态 |
**GNU m4 是一种通用宏展开器,用于在编译或配置前自动生成文本。**它是 80 年代 Unix 世界的“模板引擎”,在现代主要遗留在 autotools 体系中,但仍是一块重要的历史基石。
参考: https://segmentfault.com/a/1190000004104696
m4 有独特的“延迟展开机制”:
- 直接写 NAME → 立即展开;
- 用引号包围 `NAME’ → 阻止展开;
- 嵌套宏时,要精确控制展开顺序。