传递参数给"make run"命令

512

我使用Makefile。

我有一个名为run的目标,它运行构建目标。简化后,它看起来像以下内容:

prog: ....
  ...

run: prog
  ./prog

是否有方法可以传递参数?这样就能

make run asdf --> ./prog asdf
make run the dog kicked the cat --> ./prog the dog kicked the cat

4
类似问题:如何从命令行向Makefile传递参数? - kenorb
15个回答

422

我不知道如何完全实现你想要的,但有一个变通的方法可能会有帮助:

run: ./prog
    ./prog $(ARGS)

然后:

make ARGS="asdf" run
# or
make run ARGS="asdf"

37
@Rob:$() 更具可移植性,在 Nmake 和 make 中都可以使用。 - John Knoeller
9
@Rob:Nmake从未支持${}进行宏展开,现在看来这似乎是一种过时的形式。每个我查看过的在线教程都建议使用$()。$()还更加符合其他工具(如bash)的一致性。 - John Knoeller
10
也许这种用法已经过时了。我一直使用${},但GNU Make的手册指出:“要替换变量的值,请在括号或花括号中写入变量名称后面加上美元符号:$(foo)'或${foo}'均可引用变量foo'。”并给出了只使用$()的示例。好吧。 - Jakob Borg
7
为 John 和 calmh 加油,我回去看了看建议来自我的第一版 OReilly 书籍“使用 Make 管理项目”。作者阐述了关于档案替换使用括号和宏的规则,能够使用两者,但建议使用 {} 进行区分。不过......新版书现在更名为“使用 GNU Make 管理项目”,在整个书中都使用了括号。想想看....猜我得现代化了!(-:然而,我仍然很惊讶 MS 的 NMake 没有接受 {}。 - Rob Wells
1
如果有人能编写适用于nmake和POSIX make的任何复杂度的可移植makefile,我会感到惊讶。 nmake实际上与POSIX标准不太兼容,因此我不认为限制使用$()以尝试获得对nmake的可移植性有多大意义。自1970年代以来,所有POSIX make的实现都支持$()${},并且它们在每个方面都完全相同。从未有过任何建议认为其中一个比另一个更好。通常,如果内容包含()字符,则选择${}以消除歧义。 - MadScientist
显示剩余3条评论

301

这个问题已经存在近三年了,但无论如何...

如果你正在使用GNU make,这很容易实现。唯一的问题是make会将命令行中的非选项参数解释为目标。解决方法是将它们转换为无操作的目标,这样make就不会抱怨:

# If the first argument is "run"...
ifeq (run,$(firstword $(MAKECMDGOALS)))
  # use the rest as arguments for "run"
  RUN_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
  # ...and turn them into do-nothing targets
  $(eval $(RUN_ARGS):;@:)
endif

prog: # ...
    # ...

.PHONY: run
run : prog
    @echo prog $(RUN_ARGS)

运行此命令会得到以下结果:

$ make run foo bar baz
prog foo bar baz

2
这很棒,但似乎对以破折号开头的参数无效:prog foo bar --baz - ingydotnet
37
在这种情况下,它也可以工作,但是你需要告诉“make”不将“--baz”解释为命令行选项:“make -- prog foo bar --baz”。 “--”的意思是“这之后的所有内容都是参数,而不是选项”。 - Idelic
2
好观点blueyed!但是有一个解决方案:将“eval”行替换为$(eval $(RUN_ARGS):dummy;@:),没有定义虚拟目标。 - Lucas Cimon
1
@LukaszDynowski 是的,你可以将其替换为类似于 ifneq ($(findstring $(firstword $(MAKECMDGOALS)),$(TARGETS)),) 的内容。 - Idelic
1
我不理解的一件事是@:,我知道$(RUN_ARGS):;只是创建一个空目标,但是@:有什么必要呢? - AntonioCS
显示剩余7条评论

87

对于标准的make,您可以通过定义宏来传递参数,例如:

make run arg1=asdf

然后像这样使用它们

run: ./prog $(arg1)
   etc

关于 make 的参考资料。 Microsoft的 NMake


什么是宏?这不是人们称之为环境变量的东西吗?宏和环境变量之间有什么区别? - KcFnMi
根据谷歌上的“定义宏”(define macro),“宏是一条指令,可以自动扩展为一组指令以执行特定任务。”在搜索“什么是环境变量”的结果中,排名第一的是这个:“环境变量是一个动态命名的值,可以影响计算机上运行进程的行为方式。它们是进程运行的环境的一部分。” - Eddie Knight
每次我听到“宏”这个词,都会让我想起微软Excel中的“您要启用宏吗?”而我仍然没有清楚理解这意味着什么。我尽量避免使用这个词,难道“变量”不够合适吗? - KcFnMi
@KcFnMi,你的直觉是正确的,这不是单词通常意义下的宏。混淆可能会出现,因为它与C语言的某些“宏”有些相似,例如#define ARG1 asdf。虽然我会称之为编译时常量、“井号定义”或类似的东西,因为它实际上并不是一个宏(因为它只扩展到一个常量,而不是一条语句/一组语句)。 - SO_fix_the_vote_sorting_bug

85

TL;DR 不要试着做这个

$ make run arg

创建一个脚本而不是创建一个名为build_and_run_prog.sh的脚本:
#! /bin/sh
# rebuild prog if necessary
make prog
# run prog with some arguments
./prog "$@"

做这个:
$ ./build_and_run_prog.sh arg

阅读以下内容,了解为什么这是最合理的选择,以及为什么其他替代方案最好避免。
回答所提出的问题:如何向make目标传递参数
你可以在配方中使用一个变量。
run: prog
    ./prog $(var)

然后将一个变量赋值作为参数传递给make函数。
$ make run var=arg

这将执行./prog arg

这是“传递参数给配方”的最正确和直接的方法。请参阅GNU Make手册中关于覆盖变量的部分

但是,虽然它可以用于带参数运行程序,但它肯定不是用于这种方式的。

让我详细说明一些问题。

你想要做的是用参数arg运行prog。但是,你不应该写成:

$ ./prog arg

你需要写:
$ make run var=arg

这在尝试传递多个参数或包含空格的参数时变得更加尴尬。
而不是写成
$ ./prog foo "bar baz"

你需要写。
$ make run var="foo bar\ baz"

或者

$ make run var="foo \"bar baz\""

我希望你能看到,除了最简单的论点之外,这种情况会变得非常尴尬。
另外,请注意在makefile中不要将$(var)放在引号中。
run: prog
    ./prog "$(var)"

因为这样,prog 将始终只接收一个参数。
回答你问题背后的意图:你想运行带有一些参数的“prog”,但如果需要的话,在运行之前重新构建它。
创建一个脚本,如果需要的话重新构建,然后带着参数运行“prog”。

build_and_run_prog.sh:

#! /bin/sh
# rebuild prog if necessary
make prog
# run prog with some arguments
./prog "$@"

这个脚本非常清楚地表达了意图。它使用 make 来完成它擅长的工作:构建/编译。它使用一个 shell 脚本来完成它擅长的工作:批处理。
此外,你还可以使用 shell 脚本的完全灵活性和表达能力来完成其他任何你可能需要的任务,而无需考虑 makefile 的所有注意事项。
另外,调用语法现在几乎完全相同:
$ ./build_and_run_prog.sh foo "bar baz"

相比之下:
$ ./prog foo "bar baz"

与之相比
$ make run var="foo bar\ baz"

如何处理参数的背景解释

Make并不是设计用来将参数传递给目标的。命令行上的所有参数都会被解释为目标(也称为目标)、选项或变量赋值。

因此,如果你运行以下命令:

$ make run foo --wat var=arg

make会将runfoo解释为目标,根据它们的配方进行更新。--wat是make的一个选项。而var=arg是make的一个变量赋值。

我希望你能看到,从命令行传递信息到配方中使用的唯一方法(无需使用技巧)是通过变量赋值。

有关更多详细信息,请参阅GNU手册中关于如何运行make的内容


为了完整起见,这里列举了一些“传递参数给make run”的技巧。 方法1:
run: prog
    ./prog $(filter-out $@, $(MAKECMDGOALS))

%:
    @true

超短解释:从目标列表中过滤出当前目标,然后将目标列表作为参数传递给prog。同时创建一个无操作的通配目标(%),以静默忽略所有其他"目标"。
这将使您能够编写类似以下的内容。
$ make run arg1 arg2

方法1的问题

  • 以破折号开头的参数将由make解释,而不会作为目标传递。

      $ make run --foo --bar
    

    解决方法

      $ make run -- --foo --bar
    
  • 带有等号的参数将由make解释,而不会传递

      $ make run foo=bar
    

    无解决方法

  • 带有空格的参数很尴尬

      $ make run foo "bar\ baz"
    

    无解决方法

  • 如果参数恰好是目标run(与目标相等),它也将被删除

      $ make run foo bar run
    

    将运行./prog foo bar而不是./prog foo bar run

    使用方法2可能有解决方法

  • 如果参数是合法的目标,它也将被运行。

      $ make run foo bar clean
    

    将运行./prog foo bar clean,但也会运行目标clean的配方(假设存在)。

    使用方法2可能有解决方法

  • 当您错误输入一个合法的目标时,由于捕获所有目标,它将被静默忽略。

      $ make celan
    

    将只是静默忽略celan

    解决方法是使所有内容都显示详细信息,这样您就可以看到发生的情况。但这会为合法输出产生很多噪音。

方法二:
ifeq (run, $(firstword $(MAKECMDGOALS)))
  runargs := $(wordlist 2, $(words $(MAKECMDGOALS)), $(MAKECMDGOALS))
  $(eval $(runargs):;@true)
endif

run:
    ./prog $(runargs)

超短解释:如果目标是“run”,则处理目标列表并保存在变量中。同时,使用“eval”创建不执行任何操作的目标来处理剩余的“goals”。稍后在运行“prog”时,将准备好的变量作为参数传递。
方法2的问题:
  • 如果一个参数与现有的目标具有相同的名称,那么make将打印一个警告,表示正在被覆盖。

    我不知道任何解决方法

  • 带有等号的参数仍将由make解释,而不是传递

    没有解决方法

  • 带有空格的参数仍然很尴尬

    没有解决方法

  • 带有空格的参数会破坏eval尝试创建无操作目标。

    解决方法:创建一个全局的无操作目标,如上所述。但问题是它会再次默默地忽略错误输入的合法目标。

  • 它使用eval在运行时修改makefile。在可读性、可调试性和最小惊讶原则方面,还能更糟糕吗?

    解决方法:不要这样做!

我只测试过使用GNU make。其他make可能有不同的行为。


GNU Make 手册

https://www.gnu.org/software/make/manual/html_node/index.html


46
您可以像下面这样将变量传递给Makefile:

您可以将变量传递给Makefile,方法如下:

run:
    @echo ./prog $$FOO

使用方法:

$ make run FOO="the dog kicked the cat"
./prog the dog kicked the cat
或者:
$ FOO="the dog kicked the cat" make run
./prog the dog kicked the cat

或者使用由Beta提供的解决方案:


run:
    @echo ./prog $(filter-out $@,$(MAKECMDGOALS))
%:
    @:

%: - 匹配任意任务名称的规则; @: - 空菜谱 = 什么也不做。

用法:

$ make run the dog kicked the cat
./prog the dog kicked the cat

25

这里有另一种解决方案,可以帮助应对一些使用情况:

test-%:
    $(PYTHON) run-tests.py $@
换句话说,选择一些前缀(在这种情况下为test-),然后直接将目标名称传递给程序/运行器。我想如果有某个运行器脚本可以将目标名称解包成对底层程序有用的东西,那么这将非常有用。

13
您还可以使用“$*”仅传递与“%”匹配的目标部分。 - Malvineous

11

不可以。从GNU make的man页面中查看语法:

make [ -f makefile ] [ 选项 ] ... [ 目标 ] ...

你可以指定多个目标,因此“no”(至少不是以你指定的方式)。


9

您可以明确地在命令行中提取每个第n个参数。为此,您可以使用变量MAKECMDGOALS,它保存了给“make”传递的命令行参数列表,解释为目标列表。如果要提取第n个参数,则可以将该变量与“word”函数结合使用。例如,如果要获取第二个参数,您可以将其存储在变量中,如下所示:

second_argument := $(word 2, $(MAKECMDGOALS) )

1
这也会为该参数运行make命令。make: *** 没有生成目标“arg”的规则。停止。 - ThomasReggi
@ThomasReggi 你可以添加一个“catch all”目标来抑制错误:%: @echo 完成 - Larry
如果您想要一系列单词,请参阅“wordlist”函数:https://www.gnu.org/software/make/manual/html_node/Text-Functions.html - cassepipe

7

run: ./prog 看起来有点奇怪,因为右侧应该是一个前提条件,所以 run: prog 更好。

我建议简单地使用:

.PHONY: run

run:
    prog $(arg1)

我想补充一下,参数可以通过以下方式传递:

  1. 作为参数:make arg1="asdf" run
  2. 或者定义为环境变量:arg1="asdf" make run

旁边的问题,我到处看到.PHONY: {command},这是什么意思? - Dylan Pierce
2
@DylanPierce 请查看这个答案。简单来说,左侧是一个目标,通常是一个文件。.PHONY禁用了对该文件的时间戳检查,否则如果您创建一个空的run文件,则目标命令将不会执行。 - dma_k
谢谢@dma_k,我没有意识到Makefile命令是如此基于文件的,我以为它们是隐含的操作。 - Dylan Pierce

5

我不太为此感到自豪,但我不想传递环境变量,所以我反转了运行预定义命令的方式:

run:
    @echo command-you-want

这将打印你想要运行的命令,所以只需在子shell中评估它:

$(make run) args to my command

10
两年后回顾这个答案——我当时为什么要如此固执,不想使用环境变量,为什么认为内联生成另一个命令更好呢?我现在觉得这样做并不明智。 - Conrad.Dean
我对你上面的“回顾”评论如下:在我的使用情况中,我绝对不想考虑环境变量,因为我使用make deploy version=<version>将微服务部署到云中。我不希望任何人因错误而覆盖先前的工作版本,发现他们必须编辑环境变量时已经太晚了... - Fabien Haddadi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接