如何获得makefile中的目标列表?

我使用过一些rake(一个Ruby make程序),它有一个选项,可以获得所有可用目标的列表,例如

> rake --tasks
rake db:charset      # retrieve the charset for your data...
rake db:collation    # retrieve the collation for your da...
rake db:create       # Creates the databases defined in y...
rake db:drop         # Drops the database for your curren...
...

但是在GNU make中似乎没有这样做的选项。

显然,代码几乎有它,作为2007 - http://www.mail-archive.com/help-make@gnu.org/msg06434.html

不管怎样,我做了一个小hack来从makefile中提取目标,你可以将它包含在makefile中。

list:
@grep '^[^#[:space:]].*:' Makefile

它会给你一个已定义目标的列表。这只是一个开始——例如,它并没有过滤掉依赖关系。

> make list
list:
copy:
run:
plot:
turnin:
214092 次浏览

我把这两个答案结合起来:https://stackoverflow.com/a/9524878/86967https://stackoverflow.com/a/7390874/86967 并做了一些转义,以便可以从makefile中使用

.PHONY: no_targets__ list
no_targets__:
list:
sh -c "$(MAKE) -p no_targets__ | awk -F':' '/^[a-zA-Z0-9][^\$$#\/\\t=]*:([^=]|$$)/ {split(\$$1,A,/ /);for(i in A)print A[i]}' | grep -v '__\$$' | sort"

$ make -s list
build
clean
default
distclean
doc
fresh
install
list
makefile ## this is kind of extraneous, but whatever...
run

在Bash下(至少),这可以通过制表符自动完成:

make 空间选项卡选项卡

如果已经安装了make的bash补全,补全脚本将定义一个函数_make_target_extract_script。此函数用于创建sed脚本,该脚本可用于以列表形式获取目标。

像这样使用它:

# Make sure bash completion is enabled
source /etc/bash_completion


# List targets from Makefile
sed -nrf <(_make_target_extract_script --) Makefile

注意:这个答案已经更新到GNU make v4.3 -如果你遇到什么问题,请告诉我们。

这是对Brent Bradburn的伟大方法的改进,如下所示:

  • 使用一个更健壮的命令来提取目标名称,这有望防止任何误报(也消除了不必要的sh -c)
  • 并不总是以当前的目录中的makefile为目标;尊重使用-f <file>显式指定的makefile
  • 不包括隐藏目标——按照惯例,这些目标的名称既不是以字母开头,也不是以数字开头
  • 使用假目标
  • 使用@作为命令的前缀,以防止在执行前被回显

奇怪的是,GNU make没有列出makefile中定义的目标的只是的特性。虽然-p选项产生包含所有目标的输出,但它将这些目标隐藏在许多其他信息中,并执行默认目标(可以使用-f/dev/null来抑制)。

将以下规则放入GNU __ABC0的makefile中,以实现一个名为__ABC1的目标,该目标简单地按字母顺序列出所有目标名称——即:调用为make list:

.PHONY: list
list:
@LC_ALL=C $(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/(^|\n)# Files(\n|$$)/,/(^|\n)# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$'

重要的:在粘贴这个时,确保最后一行缩进正好是1个实际的TAB字符。(空格do not work)

注意,排序生成的目标列表是最好的选择,因为排序不会产生有用的排序,因为目标在makefile中出现的顺序是保留的。
同样,由多个目标组成的规则的子目标总是输出分别,因此,由于排序,通常出现在彼此旁边;例如,如果有额外的目标,以a z:开头的规则将的目标az在输出中列出彼此挨着

规则解释:

  • < p > .PHONY: list

    • 将目标列表声明为一个伪目标,即一个引用一个文件,因此它的配方应该调用无条件地
  • LC_ALL=C确保make的输出在英语中,因为输出的解析依赖于那个. __abc3

  • < p > $(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null

    • 再次调用make以打印和解析从makefile派生的数据库:
      • -p打印数据库
      • -Rr禁止包含内置规则和变量
      • -q只测试目标的最新状态(不重做任何东西),但它本身并不会在所有情况下阻止recipe命令的执行;因此:
      • -f $(lastword $(MAKEFILE_LIST))确保与原始调用中的目标相同的makefile,无论它是隐式地还是显式地使用-f ...作为目标。
        警告 < / em >: 如果你的makefile包含__ABC0指令,这将会中断;为了解决这个问题,在任何__ABC0指令之前定义变量__ABC1 ,并使用-f $(THIS_FILE)代替。
      • :是一个故意无效目标,它意味着确保没有执行任何命令;2>/dev/null抑制产生的错误消息。注意:尽管如此,这还是依赖于-p打印数据库,这是GNU make 3.82的情况。遗憾的是,GNU make没有提供只是打印数据库的直接选项,而没有执行默认(或给定)任务;如果你不需要针对一个特定的Makefile,你可以使用make -p -f/dev/null,正如在man页面中推荐的那样。
  • < p > -v RS=

    • 这是一种awk习惯用法,它将输入分解为连续的非空行块。
  • < p > /(^|\n)# Files(\n|$$)/,/(^|\n)# Finished Make data base/

    • 在跨段落的输出中匹配包含所有目标的行范围——通过将解析限制在这个范围内,就不需要处理来自其他输出部分的误报。
    • 注意:在make版本之间。在x和4.3中,make输出中的段落结构发生了变化,因此(^|\n) / (\n|$$)确保识别感兴趣的跨段范围的行的开始和结束的行被检测到,而不管它们是出现在开头还是出现在段内/结束。
  • < p > if ($$1 !~ "^[#.]")

    • 选择性忽略块:
      • #……忽略非目标,其块以# Not a target:开头
      • .……忽略特殊目标
    • 所有其他块都应该以只包含显式定义的目标名称的行开始,后跟:
  • egrep -v -e '^[^[:alnum:]]' -e '^$@$$'从输出中删除不需要的目标:

    • '^[^[:alnum:]]'……排除隐藏的目标,根据约定,这些目标既不是以字母开头,也不是以数字开头。
    • '^$@$$'……排除list目标本身

然后运行make list打印所有目标,每个目标在自己的行上;你可以通过管道连接到xargs来创建一个以空格分隔的列表。

@nobar的回答有用地展示了如何使用TAB补全来列出makefile的目标

  • 这适用于默认提供此功能 的平台(例如,Debian , Fedora)。

  • 在其他平台(例如,Ubuntu)上,您必须显式加载此功能,如@hek2mgl的回答所示:

    • . /etc/bash_completion安装了几个制表符完成函数,包括make的函数
    • 或者,为make安装只有选项卡补全:
      • . /usr/share/bash-completion/completions/make
      • 李< / ul > < / > 李< / ul > < / >
      • 对于完全不提供此功能的平台,比如OSX,你可以源以下命令(从在这里改编而来)来实现它:
      _complete_make() { COMPREPLY=($(compgen -W "$(make -pRrq : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($1 !~ "^[#.]") {print $1}}' | egrep -v '^[^[:alnum:]]' | sort | xargs)" -- "${COMP_WORDS[$COMP_CWORD]}")); }
      complete -F _complete_make make
      
      • 注意:这并不像Linux发行版中附带的制表符补全功能那么复杂:最值得注意的是,它始终以current目录中的makefile为目标,即使命令行使用-f <file>针对不同的makefile。

不知道之前的答案为什么这么复杂:

list:
cat Makefile | grep "^[A-z]" | awk '{print $$1}' | sed "s/://g"

这显然在很多情况下不会起作用,但如果你的Makefile是由CMake创建的,你可能能够运行make help

$ make help
The following are some of the valid targets for this Makefile:
... all (the default if no target is provided)
... clean
... depend
... install
etc

作为Mklement0指出, GNU-make中缺少列出所有Makefile目标的功能,他的回答和其他人提供了这样做的方法。

然而,最初的帖子也提到了,它的任务开关做的事情与仅仅列出rakefile中的所有任务略有不同。Rake只会给您一个有相关描述的任务列表。没有描述的任务不会被列出。这使得作者既可以提供定制的帮助描述,也可以省略某些目标的帮助。

如果你想模拟rake的行为,在这里你提供每个目标的描述,有一个简单的技术可以做到这一点:在注释中嵌入你想列出的每个目标的描述。

你可以把描述放在目标旁边,或者像我经常做的那样,放在目标上面的PHONY规范旁边,就像这样:

.PHONY: target1 # Target 1 help text
target1: deps
[... target 1 build commands]


.PHONY: target2 # Target 2 help text
target2:
[... target 2 build commands]


...


.PHONY: help # Generate list of targets with descriptions
help:
@grep '^.PHONY: .* #' Makefile | sed 's/\.PHONY: \(.*\) # \(.*\)/\1 \2/' | expand -t20

它会屈服

$ make help
target1             Target 1 help text
target2             Target 2 help text


...
help                Generate list of targets with descriptions

你也可以在这个要点在这里中找到一个简短的代码示例。

同样,这不能解决在Makefile中列出所有目标的问题。例如,如果您有一个大的Makefile,它可能是生成的或由其他人编写的,并且您想要一种快速的方法来列出它的目标,而不需要深入研究它,那么这将没有帮助。

但是,如果您正在编写Makefile,并且希望以一致的、自记录的方式生成帮助文本,则此技术可能会有用。

这个对我很有帮助,因为我想看到make目标所需的构建目标(以及它们的依赖关系)。我知道make目标不能以“。”字符开头。我不知道支持什么语言,所以我使用了egrep的括号表达式。

cat Makefile | egrep "^[[:alnum:][:punct:]]{0,}:[[:space:]]{0,}[[:alnum:][:punct:][:space:]]{0,}$"

这还远远不够干净,但对我来说还行。

make -p 2&>/dev/null | grep -A 100000 "# Files" | grep -v "^$" | grep -v "^\(\s\|#\|\.\)" | grep -v "Makefile:" | cut -d ":" -f 1

我使用make -p来转储内部数据库,丢弃stderr,使用一个快速而肮脏的grep -A 100000来保持输出的底部。然后我用一对grep -v清理输出,最后使用cut得到冒号之前的内容,即目标。

这对于我的大多数makefile上的助手脚本来说已经足够了。

编辑:添加了grep -v Makefile,这是一个内部规则

这里有很多可行的解决方案,但正如我喜欢说的,“如果值得做一次,就值得再做一次。” 我确实赞成使用(tab)(tab)的建议,但正如一些人注意到的那样,你可能没有补全支持,或者,如果你有很多包含文件,你可能想要一种更简单的方法来知道目标被定义在哪里

我还没有测试下面的子制作…我认为这行不通。我们知道,递归是有害的。

.PHONY: list ls
ls list :
@# search all include files for targets.
@# ... excluding special targets, and output dynamic rule definitions unresolved.
@for inc in $(MAKEFILE_LIST); do \
echo ' =' $$inc '= '; \
grep -Eo '^[^\.#[:blank:]]+.*:.*' $$inc | grep -v ':=' | \
cut -f 1 | sort | sed 's/.*/  &/' | sed -n 's/:.*$$//p' | \
tr $$ \\\ | tr $(open_paren) % | tr $(close_paren) % \
; done


# to get around escaping limitations:
open_paren := \(
close_paren := \)

我喜欢它是因为:

  • 通过包含文件列出目标。
  • 输出原始动态目标定义(用模替换变量分隔符)
  • 在新行上输出每个目标
  • 似乎更清楚了(主观意见)

解释:

  • MAKEFILE_LIST中的foreach文件
  • 输出文件的名称
  • 包含冒号的Grep行,不缩进,没有注释,也不以句号开头
  • 排除立即赋值表达式(:=)
  • 切、排序、缩进和切规则依赖项(冒号后)
  • 蒙格变量分隔符以防止扩展

样例输出:

 = Makefile =
includes
ls list
= util/kiss/snapshots.mk =
rotate-db-snapshots
rotate-file-snapshots
snap-db
snap-files
snapshot
= util/kiss/main.mk =
dirs
install
%MK_DIR_PREFIX%env-config.php
%MK_DIR_PREFIX%../srdb

这是对jsp非常有用的答案(https://stackoverflow.com/a/45843594/814145)的修改。我喜欢这个想法,不仅要得到目标的列表,还要得到他们的描述。jsp的Makefile将描述作为注释,我发现它经常会在目标的描述echo命令中重复。因此,我从每个目标的echo命令中提取描述。

Makefile示例:

.PHONY: all
all: build
: "same as 'make build'"


.PHONY: build
build:
@echo "Build the project"


.PHONY: clean
clean:
@echo "Clean the project"


.PHONY: help
help:
@echo -n "Common make targets"
@echo ":"
@cat Makefile | sed -n '/^\.PHONY: / h; /\(^\t@*echo\|^\t:\)/ {H; x; /PHONY/ s/.PHONY: \(.*\)\n.*"\(.*\)"/    make \1\t\2/p; d; x}'| sort -k2,2 |expand -t 20

make help的输出:

$ make help
Common make targets:
make all        same as 'make build'
make build      Build the project
make clean      Clean the project
make help       Common make targets

注:

  • jsp的答案相同,可能只列出了PHONY目标,这可能适用于您的情况,也可能不适用
  • 此外,它只列出那些有echo:命令作为recipe的第一个命令的PHONY目标。:表示“什么都不做”。我在这里将它用于那些不需要echo的目标,例如上面的all target。
  • help目标还有一个额外的技巧,可以在make help输出中添加":"。

这是对上述问题的另一个回答。

在MacOSX上测试,终端只使用cat和awk

cat Makefile | awk '!/SHELL/ && /^[A-z]/ {print $1}' | awk '{print substr($0, 1, length($0)-1)}'

将像下面这样输出make文件:

target1

target2

target3

在Makefile中,它应该是相同的语句,确保使用$$variable而不是$variable来转义变量。

解释

-吐出内容

| -管道解析输出到下一个awk

awk运行正则表达式,不包括"shell"并且只接受“;A-z"然后打印出$1的第一列

awk -再次删除最后一个字符":"从列表中

这是一个粗略的输出,你可以用AWK做更多有趣的事情。尽量避免sed,因为它在bsd变体中不一致,即一些在*nix上工作,但在MacOSX等bsd上失败。

更多的

你应该能够将此(经过修改)添加到< >强劲使< / >强的文件中,到默认的bash-补全文件夹/usr/local/etc/bash-补全.d/ 意思是当你“使<强>选项卡标签< / >强".. 它将基于一行脚本完成目标

我通常这样做:

grep install_targets制作文件

它会返回如下内容:

install_targets = install-xxx1 install-xxx2 ... etc

我希望这对你们有帮助

我最喜欢的答案是由克里斯下来在Unix &Linux Stack Exchange。我将引用。

这就是make的bash完成模块获取列表的方式:

make -qp | awk -F':' '/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}'

它输出以换行符分隔的目标列表,不进行分页。

用户Brainstone建议通过管道连接到sort -u来删除重复项:

make -qp | awk -F':' '/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}' | sort -u

来源:如何在make中列出所有目标? (Unix&Linux SE)

专注于描述make目标的简单语法,并有一个干净的输出,我选择了以下方法:

help:
@grep -B1 -E "^[a-zA-Z0-9_-]+\:([^\=]|$$)" Makefile \
| grep -v -- -- \
| sed 'N;s/\n/###/' \
| sed -n 's/^#: \(.*\)###\(.*\):.*/\2###\1/p' \
| column -t  -s '###'




#: Starts the container stack
up: a b
command


#: Pulls in new container images
pull: c d
another command


make-target-not-shown:


# this does not count as a description, so leaving
# your implementation comments alone, e.g TODOs
also-not-shown:

因此,将上面的文件作为Makefile来处理并运行它会给您带来类似于

> make help
up          Starts the container stack
pull        Pulls in new container images

命令链的解释:

Bash脚本

下面是一个在bash中非常简单的方法——基于@cibercitizen1 以上的注释:

grep : Makefile | awk -F: '/^[^.]/ {print $1;}'

另见@Marc的更权威的答案。2377也是,它说明了make的Bash补全模块是如何完成的。

对于讨厌AWK的人来说,为了简单起见,这个精巧的设计适合我:

help:
make -qpRr $(lastword $(MAKEFILE_LIST)) | egrep -v '(^(\.|:|#|\s|$)|=)' | cut -d: -f1

(对于在Makefile外部使用,只需删除$(lastword ...)或将其替换为Makefile路径)。

如果你有“兴趣”,这个方法就行不通了。规则名称,但将适用于大多数简单的设置。基于make -qp的解决方案的主要缺点是(就像这里的其他答案一样),如果Makefile使用函数定义变量值——不管-q如何,它们仍然会被执行,如果使用$(shell ...),那么shell命令仍然会被调用,并且它的副作用将会发生。在我的设置中,运行shell函数的副作用通常是不希望的输出到标准错误,所以我在make命令后添加2>/dev/null

为了扩展@jsp给出的答案,你甚至可以用$(eval)函数计算帮助文本中的变量。

下面提议的版本增强了以下属性:

  • 将扫描任何生成文件(甚至包括在内)
  • 是否会在帮助注释中引用扩展活动变量
  • 为真实目标添加文档锚(前缀为# TARGETDOC:)
  • 添加列标头

所以要记录,请使用这种形式:

RANDOM_VARIABLE := this will be expanded in help text


.PHONY: target1 # Target 1 help with $(RANDOM_VARIABLE)
target1: deps
[... target 1 build commands]


# TARGETDOC: $(BUILDDIR)/real-file.txt # real-file.txt help text
$(BUILDDIR)/real-file.txt:
[... $(BUILDDIR)/real-file.txt build commands]

然后,在你的makefile文件的某处:

.PHONY: help # Generate list of targets with descriptions
help:
@# find all help in targets and .PHONY and evaluate the embedded variables
$(eval doc_expanded := $(shell grep -E -h '^(.PHONY:|# TARGETDOC:) .* #' $(MAKEFILE_LIST) | sed -E -n 's/(\.PHONY|# TARGETDOC): (.*) # (.*)/\2  \3\\n/'p | expand -t40))
@echo
@echo ' TARGET   HELP' | expand -t40
@echo ' ------   ----' | expand -t40
@echo -e ' $(doc_expanded)'

这个help目标只会打印后面有##描述的目标。这允许同时记录公共目标和私有目标。使用.DEFAULT_GOAL可以使帮助更容易被发现。

只有sedxargsprintf被使用,这是非常常见的。

使用< $(MAKEFILE_LIST)允许makefile被称为Makefile以外的东西,例如Makefile.github

printf中,您可以自定义输出以满足您的偏好。这个例子的设置是为了匹配OP对rake样式输出的请求

在剪切和粘贴下面的make文件时,不要忘记将4个空格缩进改为制表符。

# vim:ft=make
# Makefile


.DEFAULT_GOAL := help
.PHONY: test help


help:  ## these help instructions
@sed -rn 's/^([a-zA-Z_-]+):.*?## (.*)$$/"\1" "\2"/p' < $(MAKEFILE_LIST) | xargs printf "make %-20s# %s\n"


lint: ## style, bug and quality checker
pylint src test


private: # for internal usage only
@true


test: private ## run pytest with coverage
pytest --cov test




下面是上面Makefile的输出。注意,private目标没有得到输出,因为它的注释只有一个#

$ make
make help                # these help instructions
make lint                # style, bug and quality checker
make test                # run pytest with coverage

我把上面提到的几个答案编译成这个,它也可以为每个目标生成一个很好的描述,它也适用于有变量的目标。

Makefile示例:

APPS?=app1 app2


bin: $(APPS:%=%.bin)
@# Help: A composite target that relies only on other targets


$(APPS:%=%.bin): %.bin:
@# Help: A target with variable name, value = $*


test:
@# Help: A normal target without variables


# A target without any help description
clean:


# A hidden target
.hidden:


help:
@printf "%-20s %s\n" "Target" "Description"
@printf "%-20s %s\n" "------" "-----------"
@make -pqR : 2>/dev/null \
| awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' \
| sort \
| egrep -v -e '^[^[:alnum:]]' -e '^$@$$' \
| xargs -I _ sh -c 'printf "%-20s " _; make _ -nB | (grep -i "^# Help:" || echo "") | tail -1 | sed "s/^# Help: //g"'

示例输出:

$ make help
Target               Description
------               -----------
app1.bin             A target with variable name, value = app1
app2.bin             A target with variable name, value = app2
bin                  A composite target that relies only on other targets
clean
test                 A normal target without variables

它是如何工作的:

make help目标的顶部部分与mklement0在这里发布的如何获得makefile中的目标列表?完全相同。

在获得目标列表之后,它为每个目标运行make <target> -nB作为演练,并解析以@# Help:开头的最后一行以获取目标的描述。或者一个空字符串被打印在一个格式化好的表格中。

正如你所看到的,变量甚至在描述中进行了扩展,这在我的书中是一个巨大的奖励:)。

将这个目标添加到你的Makefile:

help:
@echo "\nTARGETS:\n"
@make -qpRr | egrep -e '^[a-z].*:$$' | sed -e 's~:~~g' | sort
@echo ""


  • __abc0 = __abc1
  • egrep -e '^[a-z].*:$$':搜索以小写字母开头,以":"结尾的行;
  • sed -e 's~:~~g':删除":"

然后运行:

make help

这对我很有用😉


PD:更多信息在…

make --help

make默认情况下不支持这个,其他答案已经展示了如何自动提取可能目标的列表。

然而,如果你想在没有任何副作用的情况下对清单有更多的控制(比如使用.PHONY目标来标记文档,这阻止了使用目标名称作为Make用来决定需要重建哪些目标的实际文件的逻辑),您可以为文档创建自己的语法。我更喜欢像这样使用###:

CPUS ?= $(shell nproc)
MAKEFLAGS += -j $(CPUS) -l $(CPUS) -s


# Basic paths
PREFIX  ?= usr
BINDIR  ?= $(PREFIX)/bin
ETCDIR  ?= etc
MANDIR  ?= $(PREFIX)/share/man
# ...


### help: Show help message (default target)
# use "help" as the default target (first target in the Makefile)
.PHONY: help
help:
@printf "%s\n\n" "make: List of possible targets:"
@grep '^### .*:' $(lastword $(MAKEFILE_LIST)) | sed 's/^### \([^:]*\): \(.*\)/\1:\t\2/' | column -ts "$$(printf '\t')"


### install: Install all files in $PREFIX (used by debian binary package build scripts)
install:
install -D -o root -g root -m 755 ...
...


### release: Increase package version number
release:
debchange --release

(像往常一样,缩进文件必须精确地从一个制表器开始,但stackoverflow不能正确地再现该细节。)

输出如下所示:

$ make
make: List of possible targets:


help:      Show help message (default target)
install:   Install all files in $PREFIX (used by debian binary package build scripts)
release:   Increase package version number

这是因为只有以### 开头并具有:字符的行才被认为是要输出的文档。注意,这是有意地不提取实际的目标名称,而是完全信任文档行。这也允许对非常复杂的Makefile技巧始终发出正确的输出。还要注意,这避免了将文档行放在相对于实际规则的任何特定位置。我还有意避免对输出进行排序,因为只需按首选顺序列出文档行,就可以从Makefile本身完全控制输出的顺序。

显然,您可以发明任何其他您喜欢的语法,甚至可以做一些

### en: install: Install all files in $PREFIX
### fi: asennus: asenna kaikki tiedostot hakemistoon $PREFIX

并且只打印与当前语言环境匹配的行,以支持多种语言,并具有别名来本地化目标名称:

.PHONY: asennus
asennus: install

最重要的问题是为什么要列出目标?您想要实际的文档还是某种调试信息?

我用这个:

make -npq .DEFAULT 2> /dev/null | \
awk -v RS= -F: '$1 ~ /^[^#%]+$/ { print $1 }'

它是bash补全脚本功能的非常简化版本。

这可能会产生很多假阳性,但就我的目的而言,我宁愿有假阳性而不是假阴性。

我个人对我构建的每个Makefile都复制粘贴相同的help目标。

.SILENT:


.PHONY: help
## This help screen
help:
printf "Available targets\n\n"
awk '/^[a-zA-Z\-\_0-9]+:/ { \
helpMessage = match(lastLine, /^## (.*)/); \
if (helpMessage) { \
helpCommand = substr($$1, 0, index($$1, ":")-1); \
helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \
printf "%-30s %s\n", helpCommand, helpMessage; \
} \
} \
{ lastLine = $$0 }' $(MAKEFILE_LIST)

我也在这个Github gist中维护了它的副本: https://gist.github.com/Olshansk/689fc2dee28a44397c6e31a0776ede30 < / p >

非常简单的AWK解决方案:

all:
@awk -F'[ :]' '!/^all:/ && /^([A-z_-]+):/ {print "make " $$1}' Makefile

(注意:这并不包括所有的极端情况,如接受的答案,如这里介绍的。)