一、前言
做嵌入式 Linux/RTOS、C/C++ 开发,没人能绕开 Makefile。
资深大佬能写出简洁通用、可复用的工程级 Makefile,核心秘诀之一就是吃透各类Makefile函数。
本文把 Makefile 主流常用函数分类精讲:语法详解、功能作用、返回说明、实战示例全覆盖,通俗易懂、零基础也能看懂。文末附赠工程级完整源码,看完直接就能套用到自己项目。
想获取所有示例完整源码,可文末扫码加入知识星球,进入【源码】专栏即可自取,学习过程中有任何问题,也可在星球内随时交流探讨。
Makefile核心教程总览,可查看文章Makefile核心教程总览(最通俗易懂完整版)。前十一篇是基础内容,会每周发布在公众号和知识星球等平台,后十几篇是进阶内容,只发布在知识星球,所有篇章的源码均只发布在知识星球,源码均原创,可直接编译运行。
二、Makefile 函数基础语法
1.函数调用格式
两种写法等价,推荐第一种:
$(函数名 参数1,参数2,参数3...)
${函数名 参数1,参数2,参数3...}
参数间以逗号 "," 分隔,而函数名和参数之间以 "空格" 分隔。函数调用以 $ 开头,以圆括号或花括号把函数名和参数括起。注意,逗号前后不要乱加空格,多余空格会被当成字符串内容处理。
2. 函数分类总览
字符串处理函数:subst, patsubst, strip, findstring, filter, filter-out, sort, word, wordlist, words, firstword, lastword等。
文件名操作函数:dir, notdir, suffix, basename, addsuffix, addprefix, join, wildcard, realpath, abspath。
流程控制函数:foreach 循环、if 条件、call 自定义函数。
信息与系统函数:info 打印、shell 调用系统命令、origin 变量溯源。
三、字符串处理函数(最常用)
1. subst 字符串全量替换
名称:字符串替换函数。
语法:$(subst 原字符串,新字符串,目标文本)
作用:将目标文本中所有出现的原字符串,替换成新字符串。
返回:函数返回被替换过后的字符串。
示例 demo1:
TXT := hello linux
RES := $(subst linux,makefile,$(TXT))
# 结果:hello makefile
2. patsubst 模式匹配替换(核心高频)
名称:模式字符串替换函数。
语法:$(patsubst 匹配模式,替换模式,目标列表)
作用:会先把第三个参数目标列表里用空格、换行隔开的每一个单词单独拿出来,挨个去和第一个参数匹配模式做比对,模式里的%是万能通配符,能匹配任意一段字符;如果某个单词符合匹配模式,就会按照第二个参数替换模式进行改写,替换模式里的%会自动沿用匹配模式中%抓到的原有内容,没匹配上的单词就保持原样不动,最后把所有处理完的单词重新组合成一整串内容返回。
返回:函数返回被替换过后的字符串。
示例 demo2:
# 把所有.c后缀换成.o
SRC := a.c b.c src/c.c
OBJ := $(patsubst %.c,%.o,$(SRC))
# 结果:a.o b.o src/c.o
3. filter 过滤保留匹配单词
名称:正向过滤函数。
语法:$(filter 匹配模式,文本)
作用:从文本列表中只保留符合匹配模式的内容,过滤掉不匹配的内容。
返回:符合匹配模式的单词列表。
示例 demo3:
FILES := a.c b.h c.c d.txt
C_SRC := $(filter %.c,$(FILES))
# 结果:a.c c.c
4. filter-out 反向过滤
名称:反向过滤函数。
语法:$(filter-out 排除模式,文本)
作用:从文本列表中剔除符合排除模式的内容,保留剩余内容。
返回:剔除指定内容后的剩余单词列表。
示例 demo4:
FILES := a.c b.h c.c d.txt
NO_H := $(filter-out %.h,$(FILES))
# 结果:a.c c.c d.txt
5. strip 去除首尾多余空格
名称:去空格函数。
语法:$(strip 文本)
作用:去掉字符串首尾所有空格,并将字符串中间连续多个空格合并为一个。
返回:去除多余空格后的干净字符串。
示例 demo5:
RAW := hello makefile
CLEAR := $(strip $(RAW))
# 结果:hello makefile
6. word /words/wordlist 取单词
名称:单词操作函数
语法: (word n,文本) $(wordlist s,e,文本)
作用: 对单词列表进行计数、截取、范围提取。 (word n, 文本):取出文本中第 n 个单词 $(wordlist s,e, 文本):取出文本中第 s 个到第 e 个单词
返回: words:单词数量(数字)。 word:第 n 个单词字符串。 wordlist:指定范围的单词列表。
示例 demo6:
TXT := aa bb cc dd
NUM := $(words $(TXT)) # 4
W2 := $(word 2,$(TXT)) # bb
WL := $(wordlist 2,3,$(TXT))# bb cc
7. sort 排序去重
名称:排序去重函数。
语法:$(sort 列表)
作用:对单词列表进行字母升序排序,并自动删除重复单词。
返回:排序且去重后的新列表。
示例 demo7:
LIST := zzz aa bb aa zzz
SORT_LIST := $(sort $(LIST))
# 结果:aa bb zzz
8. findstring 查找子字符串
名称:子字符串查找函数。
语法:$(findstring 查找内容,目标文本)
作用:在目标文本中精确查找是否包含指定查找内容字符串,完整匹配才命中。
返回值:找到则返回查找内容本身,没找到返回空值。
示例 demo8:
TXT := hello makefile linux
# 能找到
RES1 := $(findstring makefile,$(TXT))
# 结果:makefile
# 找不到
RES2 := $(findstring gcc,$(TXT))
# 结果:空
9. firstword 取第一个单词
名称:获取首个单词函数。
语法:$(firstword 文本列表)
作用:从文本列表中直接取出第一个单词。
返回值:列表中第一个单词。
示例 demo9:
LIST := src app utils bin
FIRST := $(firstword $(LIST))
# 结果:src
10. lastword 取最后一个单词
名称:获取末尾单词函数。
语法:$(lastword 文本列表)
作用:从文本列表中直接取出最后一个单词。
返回值:列表中最后一个单词。
示例 demo10:
LIST := src app utils bin
LAST := $(lastword $(LIST))
# 结果:bin
四、文件名操作函数(工程必备)
1. wildcard 通配符文件匹配
名称:wildcard 通配符匹配函数。
语法:$(wildcard 匹配模式)
作用:使用通配符(*、?)自动查找指定目录下符合规则的所有文件,不会匹配不存在的文件,仅返回实际存在的文件列表。
返回:空格分隔的匹配成功的文件路径列表。
示例 demo11:
# 匹配当前目录下所有 .c 文件
SRC := $(wildcard *.c)
# 匹配 src 目录下所有 .c 文件
SRC_ALL := $(wildcard src/*.c)
2. dir 提取目录部分
名称:dir 目录提取函数.
语法:$(dir 文件名/文件路径列表)
作用:从完整文件路径中,提取出文件所在的目录部分(包含末尾的 /)。
返回:每个文件对应的目录路径,多个文件返回空格分隔的目录列表。
示例 demo12:
FILE := src/main.c
DIR := $(dir $(FILE))
# 结果:src/
# 多文件示例
FILES := src/main.c inc/func.h
DIRS := $(dir $(FILES))
# 结果:src/ inc/
3. notdir 提取纯文件名(去掉路径)
名称:notdir 纯文件名提取函数。
语法:$(notdir 文件名/文件路径列表)
作用:与 dir 相反,去掉文件路径,只保留最后面的文件名本身。
返回:去掉目录后的纯文件名,多个文件返回空格分隔的纯文件列表。
示例 demo13:
FILE := src/main.c
NAME := $(notdir $(FILE))
# 结果:main.c
# 多文件示例
FILES := src/main.c inc/func.h
NAMES := $(notdir $(FILES))
# 结果:main.c func.h
4. suffix / basename 提取后缀 / 无后缀文件名
名称:suffix 后缀提取函数,basename 无后缀文件名函数。
语法: (basename 文件名/文件路径列表)
作用: suffix:提取文件的后缀名(带 .)。 basename:去掉文件后缀名,保留前面的路径 + 文件名。
返回: suffix:文件后缀(如 .c、.h)。 basename:去掉后缀的路径 + 文件名。
示例 demo14:
FILE := src/main.c
SUF := $(suffix $(FILE))
# 结果:.c
BASE := $(basename $(FILE))
# 结果:src/main
# 多文件示例
FILES := src/main.c inc/func.h
SUFS := $(suffix $(FILES))
# 结果:.c .h
BASES := $(basename $(FILES))
# 结果:src/main inc/func
5. addprefix / addsuffix 加前缀 / 加后缀
名称:addprefix 加前缀函数 ,addsuffix 加后缀函数。
语法: (addsuffix 后缀,字符串列表)
作用: addprefix:给列表中每一项统一添加前缀(常用于加目录路径)。 addsuffix:给列表中每一项统一添加后缀(常用于加文件扩展名)。
返回:添加前缀 / 后缀后的新字符串列表。
示例 demo15:
# 定义基础列表
TEMP := a b c
# 统一加前缀
PREFIX := $(addprefix src/,$(TEMP))
# 结果:src/a src/b src/c
# 统一加后缀
SUFFIX := $(addsuffix .c,$(TEMP))
# 结果:a.c b.c c.c
# 组合使用:加目录 + 加后缀
FILES := $(addsuffix .c,$(addprefix src/,$(TEMP)))
# src/a.c src/b.c src/c.c
6. join 字符串拼接函数
名称:join 成对拼接函数。
语法:$(join 字符串列表 A,字符串列表 B)
作用:将两个列表按位置一一对应拼接,第 1 个拼第 1 个,第 2 个拼第 2 个……
返回:按顺序成对拼接后的新字符串列表。
示例 demo16:
# 目录列表
DIRS := src/ inc/
# 文件名列表
NAMES := main.c func.h
# 两两拼接
FILES := $(join $(DIRS),$(NAMES))
# 结果:src/main.c inc/func.h
# 数量不匹配时,多余项直接保留
DIRS2 := a/ b/ c/
NAMES2 := 1.c 2.c
FILES2 := $(join $(DIRS2),$(NAMES2))
# 结果:a/1.c b/2.c c/
7. realpath 真实绝对路径函数
名称:realpath 真实路径解析函数。
语法:$(realpath 文件/目录路径)
作用:解析文件/目录的绝对路径,自动处理软链接、相对路径(../、./),返回系统真实物理路径。
返回:文件/目录的真实绝对路径(不存在的路径返回空)。
示例 demo17:
# 当前目录下的 main.c
FILE := main.c
REAL_FILE := $(realpath $(FILE))
# 上级目录
DIR := ..
REAL_DIR := $(realpath $(DIR))
8. abspath 绝对路径转换函数
名称:abspath 绝对路径转换函数。
语法:$(abspath 文件/目录路径)
作用:将相对路径转换为绝对路径,不处理软链接,不校验文件是否存在,纯路径格式转换。
返回:绝对路径字符串(文件不存在也返回路径,不返回空)。
示例 demo18:
# 相对路径转绝对路径
FILE := src/main.c
ABS_FILE := $(abspath $(FILE))
# 结果:/home/user/project/src/main.c
# 不存在的文件也会返回绝对路径
NO_FILE := test/abc.txt
ABS_NO := $(abspath $(NO_FILE))
# 结果:/home/user/project/test/abc.txt
# 上级目录
DIR := ..
REAL_DIR := $(abspath $(DIR))
五、流程控制函数(循环 + 条件)
1. foreach 循环遍历
详见Makefile核心教程(九) --- 一文吃透 Makefile 循环语句
2. if 条件函数
详见Makefile核心教程(八) --- 一文吃透 Makefile条件判断
3. call 自定义函数
名称:call 自定义函数调用函数。
语法:$(call 自定义函数名,参数 1, 参数 2, 参数 3...)
作用: 1、封装 Makefile 中重复使用的通用逻辑,实现代码复用,避免重复编写相同规则。 2、支持传入多个参数,在自定义函数内部用 $(1)、$(2)、$(3)... 依次对应调用时的参数 1、参数 2、参数 3... 3、是 Makefile 实现模块化、简化编写的核心函数。
返回:执行自定义函数内的逻辑处理后,返回处理完成的字符串 / 文件列表(和普通函数返回格式一致)。
示例 demo19:
# 1. 定义自定义函数:将 .c 源文件批量转换为 .o 目标文件(函数名:TO_OBJ)
# $(1) 代表调用时传入的第一个参数(所有.c文件列表)
TO_OBJ = $(patsubst %.c, %.o, $(1))
# 2. 定义源文件
SRC := a.c b.c test.c
# 3. 用 call 调用自定义函数,传入参数 $(SRC)
OBJ := $(call TO_OBJ, $(SRC))
# 最终结果:OBJ = a.o b.o test.o
补充:多参数自定义函数示例(零基础拓展)
# 自定义函数:给文件加 目录前缀 + 文件后缀(2个参数)
# $(1) = 目录前缀 $(2) = 文件名列表
ADD_PATH = $(addprefix $(1)/, $(addsuffix .c, $(2)))
# 调用:参数1=src,参数2=main func test
FILES := $(call ADD_PATH, src, main func test)
# 最终结果:FILES = src/main.c src/func.c src/test.c
六、系统与信息函数
1. info 打印调试信息
详见Makefile核心教程(五) —打印信息的几种方式:从基础 echo 到内置函数,一文全覆盖
2. shell 调用系统 Shell 命令
名称:shell 系统命令执行函数。
语法:$(shell 系统命令)
作用:在 Makefile 内部直接执行 Linux / Unix 系统终端命令,并把命令执行后的输出结果捕获回来,赋值给 Makefile 变量使用。
返回:命令在终端执行后输出的字符串结果(多行内容会自动转为空格分隔)。
示例 demo20:
# 获取当前所在目录(终端执行 pwd 命令)
PWD := $(shell pwd)
# 获取当前 gcc 编译器版本
GCC_VER := $(shell gcc --version | head -n1)
# 创建 build 文件夹
$(shell mkdir -p build)
# 列出 src 目录下所有 .c 文件(和 wildcard 效果类似)
SRC := $(shell ls src/*.c)
shell:让 Makefile 能执行 Linux 命令、拿结果、做目录 / 文件操作。
3. origin 查看变量来源
名称:origin 变量来源查询函数。
语法:$(origin 变量名)
作用:调试 Makefile 专用,查看一个变量从哪里来(是自己定义的?命令行传入的?环境变量?还是没定义?),快速解决变量被覆盖、值不对的问题。
返回: 固定返回以下几种字符串(最常用 4 个) file:变量在 Makefile 文件中定义。 command line:变量由命令行传入。 environment:变量是系统环境变量。 undefined:变量未定义。
示例 demo21:
# 1. 在 Makefile 里定义变量
VERSION := 1.0
# 查看 VERSION 的来源 → 返回 file
FROM_VER := $(origin VERSION)
# 2. 查看一个未定义的变量 → 返回 undefined
FROM_UNDEF := $(origin UNKNOWN_VAR)
# 3. 查看系统环境变量(如 HOME)→ 返回 environment
FROM_ENV := $(origin HOME)
最常用使用场景(调试)
# 不确定哪个地方改了 CC 变量,直接打印来源
$(info CC 来自:$(origin CC))
origin:专门用来调试变量,快速定位变量值不对、被覆盖的问题。
七、完整源码(可直接运行)
工程级源码demo22。
文末扫码加入知识星球,进入【源码】专栏即可自取,学习过程中有任何问题,也可在星球内随时交流探讨。
源码目录如下,都是可直接编译运行:
八、写在最后
理论看懂只是第一步,真正吃透还需要实战源码对照、工程案例仿写、疑难问题答疑。
很多小伙伴自学 Makefile 时,常会遇到函数用法混淆、工程模板不会写、复杂编译规则无从下手等问题,网上资料零散杂乱,踩坑没人指点,白白浪费大量时间。
为此我专门搭建了技术知识星球,给大家打造一个专属嵌入式 Linux 的深耕学习圈子:
✅ 专属源码库 本文配套的所有 Makefile 函数示例、工程级通用 Makefile 模板、多项目编译框架完整版,都已整理上传星球【源码】专栏,无套路直接免费自取,下载就能放到自己项目里套用。
✅ 系统化专栏教程 持续更新 Makefile 全套系列、CMake 工程构建、Linux 驱动应用开发、音视频开发、网络摄像机实战项目等专属精品教程,星球成员专属独家进阶内容,外部不公开更新。
✅ 一对一交流答疑 学习中遇到任何 Makefile 语法报错、工程编译出错、函数用法不懂、项目框架搭建难题,都可以在星球内发帖提问,我都会逐一细致解答,帮你快速避坑。
不管你是零基础入门嵌入式,还是在职工程师想精进工程能力、搭建通用项目模板,这个星球都值得你加入。用最少的时间、最低的成本,快速吃透 Makefile 与嵌入式工程开发核心技能。
想加入星球或技术交流群,可添加作者微信咨询沟通加入。