4、Makefile函数入门:字符串处理函数与文件名函数

说实话,很多初学者看到Makefile里的函数,第一反应就是「这玩意儿有必要学吗?」。我当年也是这么想的。直到有一次,我维护一个上百个源文件的项目,手动改了半小时路径,结果编译全挂——嗯,从那以后,我再也不敢小看这些函数了。

今天咱们就聊聊Makefile里最常用的两类函数:字符串处理函数和文件名函数。它们不复杂,但用好了,能让你的Makefile从「能跑」变成「优雅」。

4.1 字符串处理三剑客:subst、patsubst、strip

先说说字符串处理。Makefile本质上就是文本处理,你想想看,变量里存的都是字符串,不处理它们处理谁?

4.1.1 subst —— 简单替换

subst 是「substitute」的缩写,意思就是替换。它的语法很简单:

$(subst 被替换的字符串, 替换成的字符串, 原始文本)

举个例子:

SRC := hello.c world.c
OBJ := $(subst .c,.o,$(SRC))
# OBJ 的值是 hello.o world.o

我在项目中遇到过一种情况:不同平台下,源文件后缀名不一样。比如Linux下是.c,Windows下是.cpp。用subst一把搞定,不用写两套规则。

注意:subst 是严格匹配,不会做模式匹配。比如你想把 abc.c 换成 abc.o,没问题。但如果你想把所有 .c 结尾的都换成 .o,用 patsubst 更合适。

4.1.2 patsubst —— 模式替换

patsubst 全称是「pattern substitution」,模式替换。它支持通配符 %,这才是真正的生产力工具。

$(patsubst 模式, 替换模式, 原始文本)

看个实际例子:

SRC := src/main.c src/utils.c lib/helper.c
OBJ := $(patsubst %.c,%.o,$(SRC))
# OBJ 的值是 src/main.o src/utils.o lib/helper.o

这里 % 匹配了文件名中 .c 前面的部分,然后原封不动地放到 .o 前面。路径信息完整保留。

我的习惯:只要涉及「把源文件转成目标文件」,我几乎只用 patsubst。因为它比 subst 更灵活,而且语义更清晰。你一看就知道是在做「模式替换」。

4.1.3 strip —— 去空格

strip 这名字很直白——去掉字符串首尾和中间多余的空格。语法最简单:

$(strip 字符串)

举个例子:

VAR :=   hello    world   !  
CLEAN := $(strip $(VAR))
# CLEAN 的值是 "hello world !"

你可能觉得这功能没啥用。但我告诉你,变量展开时经常莫名其妙多出空格,尤其是用 foreach 或者 shell 函数的时候。我曾经排查过一个编译错误,折腾了两小时,最后发现是变量末尾多了一个空格,导致链接时文件路径不对。从那以后,我养成了习惯:凡是拼接出来的路径变量,先 strip 一下。

4.2 文件名函数:dir、notdir、suffix、basename

处理文件路径,是Makefile的日常操作。这四个函数,我愿称之为「路径四件套」。

4.2.1 dir —— 取目录

dir 从文件路径中提取目录部分,包括末尾的斜杠:

$(dir src/main.c lib/utils.c)
# 结果是 "src/ lib/"

注意,如果文件就在当前目录,比如 main.c,dir 会返回 ./。这个细节容易忽略。

4.2.2 notdir —— 取文件名

notdir 和 dir 正好相反,它只取文件名部分:

$(notdir src/main.c lib/utils.c)
# 结果是 "main.c utils.c"

这个函数我用的特别多。比如你想把所有源文件放到同一个输出目录,就可以先 notdir 去掉路径,再统一加上输出路径。

4.2.3 suffix —— 取后缀

suffix 提取文件的后缀名(包括点号):

$(suffix src/main.c lib/utils.h)
# 结果是 ".c .h"

如果文件没有后缀,返回空字符串。这个函数在判断文件类型时很有用。

4.2.4 basename —— 取主文件名

basename 去掉后缀,只保留文件名主体部分:

$(basename src/main.c lib/utils.h)
# 结果是 "src/main lib/utils"

注意,它保留了路径信息,只去掉最后一个点号及其后面的内容。

实战组合拳:这四个函数经常搭配使用。比如你想把 src/main.c 变成 build/main.o,可以这样写:

OBJ := $(addprefix build/,$(notdir $(basename $(SRC)))).o

先取文件名,再去掉后缀,加上前缀,最后补上 .o。一气呵成。

4.3 知识体系一览

为了让你更直观地理解这些函数的关系,我画了一张图:

Makefile 函数入门:核心分类 Makefile 函数 字符串处理函数 文件名函数 subst patsubst strip dir notdir suffix basename 字符串处理:文本替换、模式匹配、空格清理 文件名函数:路径拆分、后缀提取、文件名剥离

4.4 避坑指南

最后,分享几个我踩过的坑:

  • 空格问题:Makefile里,逗号后面不要加空格。比如 $(subst .c, .o, $(SRC)) 和 $(subst .c,.o,$(SRC)) 结果不一样。前者会在 .o 前面多一个空格。
  • 通配符误解:subst 不支持 %,别搞混了。想用模式匹配,老老实实用 patsubst。
  • 路径斜杠:dir 返回的目录末尾带斜杠,拼接时注意不要重复。比如 $(dir src/main.c)main.o 结果是 src/main.o,没问题。但如果你自己加了个斜杠,就变成 src//main.o 了。

这些函数,说白了就是帮你省去手动拼接字符串的麻烦。刚开始可能觉得多此一举,但等你项目大了、文件多了,就知道它们有多香了。

一个小建议:别试图一次性记住所有函数。先记住 patsubst 和 notdir,这两个最常用。其他的用到再查,慢慢就熟了。

可可精品资料库