模块:M02 规则语法精读
篇号:M02-01 / 41
预计阅读:35 分钟
主线:GNU Make
文章目录
- 本篇目标
- 30 秒速览
- 正文
- 1. 显式规则的标准形态
- 2. 多个目标、多个依赖(显式写法)
- 3. Tab 纪律:不是审美,是语法
- 3.1 正确 vs 错误
- 3.2 编辑器与「看不见的字符」
- 3.3 哪些行需要 Tab?
- 4. 多行配方:一行一个 shell
- 5. 配方前缀:`@`、`-`、`+`
- 6. 注释、空行与可读性
- 7. 无配方规则:只挂依赖
- 8. 对照表:显式规则要素
- 读 Makefile 检查清单
- 练习
- 判断题(对 / 错)
- 改错题
- 动手题
- 下一篇预告
本篇目标
把「一条显式规则」的语法钉死:目标、依赖、配方如何书写;多行配方如何执行;@/-/+前缀做什么;Tab 与空格为何是纪律问题而非风格偏好。学完本篇,你能正确写出并读懂最常见的文件规则,并为 M02-02 的多目标、同目标多规则打下基础。
30 秒速览
- 显式规则基本形:
目标: 依赖…,下一行起Tab+ 配方命令。 - 一条规则可以有多个目标、多个依赖;依赖可为文件或其它目标名。
- 配方可多行:默认每一行单独启动一次 shell;要用同一 shell 会话需用
\续行或写在一行用;连接。 - 配方前缀:
@不回显、-忽略错误、+在-n时仍执行(少见但要认得)。 - 配方缩进必须是 Tab;空格会触发
*** missing separator。 - 注释用
#;规则行也可用\续行把长依赖列表拆开。
正文
1. 显式规则的标准形态
目标: 依赖1 依赖2 配方命令1 配方命令2最小完整例:
hello: hello.c gcc -Wall -o hello hello.c拆解:
| 部分 | 本例 | 说明 |
|---|---|---|
| 目标 | hello | 冒号左侧;要更新的名字 |
| 依赖 | hello.c | 冒号右侧;可空(无依赖目标也合法) |
| 配方 | gcc ... | 必须以 Tab 开头 |
无依赖的动作目标(呼应 M01-03):
.PHONY: clean clean: rm -f hello *.oclean:右侧为空:没有文件依赖,只靠.PHONY保证每次执行。
2. 多个目标、多个依赖(显式写法)
app: main.o util.o gcc -o app main.o util.o main.o: main.c util.h gcc -c main.c util.o: util.c util.h gcc -c util.c要点:
- 依赖列表用空格分隔。
- 长列表可用反斜杠续行:
app: main.o util.o extra.o \ net.o ui.o gcc -o app main.o util.o extra.o net.o ui.o- 「一行写多个目标」的显式规则(共用同一套依赖与配方)合法,但语义要小心——M02-02专讲;本篇先认单目标为主的常见写法。
3. Tab 纪律:不是审美,是语法
3.1 正确 vs 错误
# 正确:配方前是 Tab hello: hello.c gcc -o hello hello.c # 错误:四个空格 hello: hello.c gcc -o hello hello.c错误时 GNU Make 典型报错:
Makefile:3: *** missing separator. Stop.「separator」在这里主要指:规则头与配方之间,配方行必须以 Tab 作为分隔/引导。
3.2 编辑器与「看不见的字符」
高频事故:
| 原因 | 现象 |
|---|---|
| 编辑器把 Tab 转成空格 | missing separator |
| 从网页复制 Makefile | 空格冒充缩进 |
| 「显示空白」未开,肉眼看不出来 | 本地好好的,CI 挂了 |
建议:
- 编辑 Makefile 时关闭「Tab 转空格」,或为
Makefile/makefile/*.mk单独配置。 - 用能显示 Tab/空格的编辑器视图检查。
- 可疑时:
cat -A Makefile(Tab 常显示为^I)。
3.3 哪些行需要 Tab?
| 行类型 | 是否 Tab |
|---|---|
目标: 依赖 | 否(顶格或仅因风格缩进——不要用 Tab 当「美观缩进」去缩规则头) |
| 配方命令行 | 必须 Tab 开头 |
变量 = 值、include、条件指令 | 否 |
空行、#注释 | 否 |
规则头若被误加了 Tab,也可能让 make 把整行当成配方或解析失败——规则头顶格写最省心。
4. 多行配方:一行一个 shell
默认情况下,配方的每一行各自调用一次 shell:
wrong: cd build pwd第二行的pwd不会在build/里执行:cd只影响第一个短暂 shell,进程结束即丢。
正确写法(同一 shell):
ok: cd build && pwd # 或续行(反斜杠后换行,仍属「逻辑一行」配方的常见写法) ok2: cd build && \ pwd也可用分号:
ok3: cd build; pwd经验:
- 需要保留目录/变量状态 → 写在同一配方行的同一 shell里(
&&/;/\)。 - 彼此独立的命令 → 分行即可。
配方里也可以写多条独立命令:
hello: hello.c gcc -Wall -c hello.c -o hello.o gcc -o hello hello.o rm -f hello.o每行仍是独立 shell,但这里不依赖「上一行 cd 的状态」,所以没问题。
5. 配方前缀:@、-、+
写在配方命令最前面(Tab 之后):
demo: @echo "不回显 echo 这一行命令本身" -rm -f maybe-missing.txt +echo "即使 make -n 也可能执行(少见用途)"| 前缀 | 含义 |
|---|---|
@ | 不向终端回显该命令行 |
- | 该命令失败(非零退出)时,不让 make 因此失败 |
+ | 在make -n/某些「不执行」模式下,仍执行该行(给递归$(MAKE)等场景用;M08 再遇) |
可叠用,顺序惯例为@、-、+的组合,例如:
clean: -@rm -f *.o hello含义:尝试删除;失败不致命;且少回显。
慎用-:忽略错误容易掩盖真问题。清理缺失文件时用-rm较常见;编译步骤一般不要随意加-。
6. 注释、空行与可读性
# 这是注释:生成演示程序 hello: hello.c # 行尾注释也可以 gcc -o hello hello.c注意:
- 配方行里的
#是否启动注释,取决于传给 shell 的内容——配方交给/bin/sh后,按shell 规则解释。Make 层对规则头/变量行的#注释更直观。 - 空行可以分隔规则,增强可读性;空行不会结束「当前规则的配方」——只要下一非空行仍是 Tab 配方,就仍属于上一规则(直到下一个规则头或非配方指令)。
hello: hello.c gcc -c hello.c -o hello.o gcc -o hello hello.o上面两行配方仍都属于hello(中间空行无妨)。若误把第二段写成无 Tab 的裸命令,才会解析错乱。
7. 无配方规则:只挂依赖
all: hello libutil.a hello: main.o gcc -o hello main.oall只有依赖、没有配方:请求all时,Make 负责更新依赖目标。这是all的常见写法(M01-03)。
也可以给文件目标「只追加依赖、配方在别处」——这是M02-02「同一目标多条规则」的话题,本篇只要求你认得:规则可以没有配方。
8. 对照表:显式规则要素
| 要素 | 写法要点 |
|---|---|
| 规则头 | 目标: 依赖…,建议顶格 |
| 依赖分隔 | 空格;长列表用\续行 |
| 配方 | 每行以 Tab 开头 |
| 多行 shell 状态 | &&/;/\,勿假设跨行共享 cwd |
@-+ | Tab 后、命令前的前缀 |
| 注释 | # |
| 无配方 | 仅表达依赖聚合(如all) |
读 Makefile 检查清单
看到一条显式规则时:
- 冒号左侧目标、右侧依赖是否分得清
- 配方是否Tab缩进(必要时
cat -A) - 多行配方是否错误地假设「上一行
cd仍然有效」 - 是否滥用
-前缀忽略了不该忽略的错误 @是否导致排错时看不到关键命令(可临时去掉@或去掉make -s)
练习
判断题(对 / 错)
- 配方行用四个空格缩进,在 GNU Make 中与 Tab 完全等价。
- 默认情况下,配方的每一行各自在一个新的 shell 中执行。
- 前缀
-表示该行命令失败时不让 make 失败退出。 - 规则头
hello: hello.c必须以 Tab 开头。
- 错。配方必须 Tab;空格会
missing separator。 - 对。
- 对。
- 错。规则头不应(也不必)用 Tab 开头;Tab 用于配方行。
改错题
指出问题并改正:
hello: hello.c gcc -o hello hello.c cd out cp hello out/参考答案问题:
- 第一行配方用了空格,应改为 Tab。
cd out与下一行分属两个 shell,cp不一定在out/下执行;且hello产物路径与cp逻辑也混乱。
一种改正思路(按你真实布局调整):
hello: hello.c mkdir -p out gcc -o out/hello hello.c或:
hello: hello.c gcc -o hello hello.c mkdir -p out && cp hello out/动手题
- 故意把配方改成空格缩进,观察完整报错行号。
- 写一个含
cd的错误多行配方,再用&&改正确,对比pwd输出。 - 给
clean加上-@rm -f ...,观察回显与「文件不存在」时是否仍成功。
下一篇预告
M02-02:《多目标规则、同一配方与「一个目标多条规则」》— 一行多个目标时 Make 如何解释、同一目标拆成「依赖规则 + 配方规则」怎么合并,以及常见误读。学完 M02-02,你读拆得很碎的工程 Makefile 时不会懵。