四个空格,换不来一个 Tab
我一直觉得 Makefile 是最没门槛的东西。
目标、依赖、命令,三行写完,make 一敲,完事。能有多难?
直到那行 missing separator,让我对着一个五行的文件看了一整个下午。
事情的起因
那天给项目加个构建目标,往 Makefile 里补了几行:
保存,make build 一跑——
missing separator,缺分隔符。
我盯着那两行看了十秒:冒号有,命令有,缩进有,哪里缺分隔符了?
我自信满满地开始排查。
- 是不是冒号后面漏了东西?目标行
build:后面本来就可以不跟依赖,语法没问题。
- 是不是命令行少了个什么符号?翻了别的 Makefile 对照,结构一模一样。
- 是不是 make 版本太老、语法不支持?
make --version看了,GNU Make 4.x,新得很。
- 是不是文件编码有问题?查了,UTF-8,没 BOM。
- 是不是换行符是 CRLF?被之前踩过的坑教育过,我特意
dos2unix过了一遍,还是报一样的错。
- 是不是行尾有多余空格、或者有什么看不见的字符?
cat -A看了,行尾干干净净。
- 是不是目标名和某个内置规则冲突了?换成
mybuild,还是missing separator。
到这里我已经开始怀疑人生了:五行文件、三行有效内容、语法对照过、编码查过、换行符转过、看不见的字符也排查了,它就是不认。
一个下午就在这种"文件简单到不可能出错、但它就是报错"里过去了。中间我甚至把整段删了手敲一遍——手敲完,居然能跑了。然后我对比了一下能跑和不能跑的两版,肉眼完全看不出区别。
快下班的时候,我终于想起 cat -A 有个用法我刚才没细看——它会把 Tab 显示成 ^I,把空格显示成普通的空格。
重新跑一遍 cat -A Makefile:
那行命令前面,是四个空格。
而我手敲能跑的那一版,前面是 ^I——一个 Tab。
熟悉 Makefile 的人这时候大概要笑出声了。
Makefile 有一条硬到不讲道理的语法规定:每条命令行的开头,必须是一个真正的 Tab 字符,不能是空格。 这不是代码风格偏好,不是"建议用 Tab",是 make 解析器认死的分隔符——它靠行首那个 \t 来区分"这是命令行"和"这是别的东西"。用四个空格、八个空格、一个 Tab 转成空格,make 一概不认,直接甩你一句 missing separator。
那句报错其实说得很直白:它没找到那个分隔符。只是我一开始完全没往"缩进字符的种类"上想。
至于为什么会有四个空格——我的编辑器配了"Tab 自动转空格"(写 Go / Python 时的习惯),全局生效。我粘贴进去的命令行,Tab 被悄悄换成了四个空格。而我手敲那一版能跑,是因为手敲时编辑器对 Makefile 类型用了不同的缩进策略。
解决简单到令人窒息:把那四个空格删掉,敲一个真正的 Tab。
几条血泪教训
missing separator九成就是行首用了空格而不是 Tab。 看到这个报错,别查语法、别查版本、别查编码,第一件事cat -A Makefile看命令行开头有没有^I。Makefile 是极少数"缩进字符种类属于语法一部分"的文件格式。
- 给 Makefile 单独关掉"Tab 转空格"。 编辑器的这个设置对绝大多数语言是好事,对 Makefile 是灾难。在
.editorconfig里单独给 Makefile 写一条indent_style = tab,或者在编辑器里按文件类型覆盖缩进设置。这属于一次配置、永久省心。
- "看不见的差异"要用能显形的工具看。 空格和 Tab 肉眼几乎分不出来,但
cat -A、sed -n l、xxd能让它们现形。排查"两版文件看起来一样但行为不同"这类问题,别用眼睛比,用工具比。
- 从网页、文档、聊天记录里粘 Makefile 片段,默认缩进是坏的。 复制粘贴时 Tab 经常会被转成空格或丢掉。粘完先
cat -A扫一眼,比对着报错发呆强。
- 点赞
- 收藏
- 关注作者
评论(0)