如何在Makefile中编写循环?

avd*_*avd 201 loops makefile

我想执行以下命令:

./a.out 1
./a.out 2
./a.out 3
./a.out 4
.
.
. and so on
Run Code Online (Sandbox Code Playgroud)

如何把这个东西写成一个循环Makefile

Ide*_*lic 255

如果你正在使用GNU make,你可以试试

NUMBERS = 1 2 3 4
doit:
        $(foreach var,$(NUMBERS),./a.out $(var);)

这将生成并执行

./a.out 1; ./a.out 2; ./a.out 3; ./a.out 4;

  • 这个答案是恕我直言,因为它不需要使用任何shell,它是纯makefile(即使它是GNU特定的). (26认同)
  • 分号是至关重要的,否则只有第一次迭代才会执行 (7认同)
  • 该解决方案隐藏了退出代码`./a.out 1`.`.a.out 2`将被执行. (7认同)
  • 这不是一个更好的答案,因为与在 shell 中使用 while 函数相比,内置的 foreach 函数对可以循环的元素数量有限制。 (2认同)
  • @Alexander:正如消息所说,这不是 GNU make 的 `foreach` 中的限制,而是 shell 用来调用 `ls` 的 `exec` 系统调用中的限制。除了内存之外,foreach 的参数数量没有限制。您指向的脚本和制作文件显然不做同样的事情,很明显为什么您会看到您提到的行为。同样,它与 `foreach` 函数中的限制无关。 (2认同)
  • 建议改为“$(foreach var,$(NUMBERS),./a.out $(var) &&) true”,这样如果任何命令失败,它就会中止。这解决了@bobbogo 指出的问题。 (2认同)

pax*_*blo 253

如果我假设您使用的./a.out是UNIX类型的平台,那么下面将会这样做.

for number in 1 2 3 4 ; do \
    ./a.out $$number ; \
done
Run Code Online (Sandbox Code Playgroud)

测试如下:

target:
    for number in 1 2 3 4 ; do \
        echo $$number ; \
    done
Run Code Online (Sandbox Code Playgroud)

生产:

1
2
3
4
Run Code Online (Sandbox Code Playgroud)

对于更大的范围,请使用:

target:
    number=1 ; while [[ $$number -le 10 ]] ; do \
        echo $$number ; \
        ((number = number + 1)) ; \
    done
Run Code Online (Sandbox Code Playgroud)

输出1到10(包括1和10),只需将while终止条件从10 更改为1000即可,如评论中所示,范围更大.

因此可以完成嵌套循环:

target:
    num1=1 ; while [[ $$num1 -le 4 ]] ; do \
        num2=1 ; while [[ $$num2 -le 3 ]] ; do \
            echo $$num1 $$num2 ; \
            ((num2 = num2 + 1)) ; \
        done ; \
        ((num1 = num1 + 1)) ; \
    done
Run Code Online (Sandbox Code Playgroud)

生产:

1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
4 1
4 2
4 3
Run Code Online (Sandbox Code Playgroud)

  • 请注意,在大多数(所有?)unix 系统上都存在生成数字序列的 `seq` 命令,因此您可以在 ``seq 1 1000`` 中编写 `for number ;做回声 $$number; done`(在 seq 命令的每一侧放一个反引号,而不是两个,我不知道如何使用 stackoverflow 的语法正确格式化它) (3认同)
  • 谢谢,我错过了双引号来引用for循环变量. (3认同)
  • 因为你假设他的shell识别((...)),为什么不使用更简单的((i = 0; i <WHATEVER; ++ i)); 做......; 做完了? (2认同)
  • 有人可以解释双 $$ 和行继续符 / 的需要吗?我是 makefile 的新手,但我在其他地方没有看到这种编码 (2认同)
  • @Jonz:行继续字符是因为“make”通常将每一行视为在*单独的*子shell中运行的东西。如果没有继续,它将尝试仅使用(例如)`for number in 1 2 3 4 ; 运行子shell。do`,没有循环的其余部分。有了它,它就有效地变成了“while some ;”形式的单行。做一点事 ; done`,这是一个完整的语句。“$$”问题在这里得到解答:/sf/ask/1859537781/,代码似乎摘自这个答案:-) (2认同)

bob*_*ogo 102

使用make恕我直言,主要原因是-j标志.make -j5将一次运行5个shell命令.如果您有4个CPU,并且对任何makefile进行了很好的测试,那么这很好.

基本上,你想让make看到类似的东西:

.PHONY: all
all: job1 job2 job3

.PHONY: job1
job1: ; ./a.out 1

.PHONY: job2
job2: ; ./a.out 2

.PHONY: job3
job3: ; ./a.out 3
Run Code Online (Sandbox Code Playgroud)

这很-j友好(一个好兆头).你能发现锅炉板吗?我们可以写:

.PHONY: all job1 job2 job3
all: job1 job2 job3
job1 job2 job3: job%:
    ./a.out $*
Run Code Online (Sandbox Code Playgroud)

为了相同的效果(是的,就制造而言,这与先前的配方相同,只是更紧凑).

进一步参数化,以便您可以在命令行上指定限制(繁琐,因为make没有任何好的算术宏,所以我会在这里作弊并使用$(shell ...))

LAST := 1000
NUMBERS := $(shell seq 1 ${LAST})
JOBS := $(addprefix job,${NUMBERS})
.PHONY: all ${JOBS}
all: ${JOBS} ; echo "$@ success"
${JOBS}: job%: ; ./a.out $*
Run Code Online (Sandbox Code Playgroud)

你运行它make -j5 LAST=550,LAST默认为1000.

  • 这绝对是最好的答案,因为bobbogo提到(_-j_). (7认同)
  • @seb:没有`.PHONY:all`,make会查找名为`all`的文件.如果存在这样的文件,那么make会检查该文件的最后更改时间,并且makefile几乎肯定不会按照您的意图执行.`.PHONY`声明告诉make`all`是一个符号目标.因此,Make会认为`all`目标永远是过时的.完善.请参阅手册. (6认同)
  • 这条线是如何工作的:$ {JOBS}:job%:第二个分号是什么?我在http://www.gnu.org/software/make/manual/make.htm中没有看到任何内容 (4认同)
  • @JoeS http://www.gnu.org/software/make/manual/make.html#Static-Pattern(我认为你的意思是_What是第二个*冒号*for_)._不要将它们与不太好的(恕我直言)模式规则_混淆.每当目标列表可以通过_make_的节点模式之一匹配时,静态模式规则就非常有用(同样适用于依赖项).在这里我使用一个只是为了方便,在规则中与`%`匹配的任何东西在配方中都可以作为`$*`. (2认同)

小智 21

我意识到这个问题已有几年了,但这个帖子可能仍然对某人有用,因为它演示了一种与上述不同的方法,并且不依赖于shell操作,也不需要开发人员进行硬编码数值字符串.

$(eval ....)内置宏是你的朋友.或者至少可以.

define ITERATE
$(eval ITERATE_COUNT :=)\
$(if $(filter ${1},0),,\
  $(call ITERATE_DO,${1},${2})\
)
endef

define ITERATE_DO
$(if $(word ${1}, ${ITERATE_COUNT}),,\
  $(eval ITERATE_COUNT+=.)\
  $(info ${2} $(words ${ITERATE_COUNT}))\
  $(call ITERATE_DO,${1},${2})\
)
endef

default:
  $(call ITERATE,5,somecmd)
  $(call ITERATE,0,nocmd)
  $(info $(call ITERATE,8,someothercmd)
Run Code Online (Sandbox Code Playgroud)

这是一个简单的例子.对于大值,它不会很好地扩展 - 它可以工作,但是每次迭代时ITERATE_COUNT字符串将增加2个字符(空格和点),当你进入数千时,计算单词的时间会越来越长.如上所述,它不处理嵌套迭代(您需要一个单独的迭代函数和计数器来执行此操作).这纯粹是gnu make,没有shell要求(虽然显然OP每次都想要运行一个程序 - 在这里,我只是在显示一条消息).ITERATE中的if用于捕获值0,因为$(word ...)否则会出错.

请注意,使用增长的字符串作为计数器是因为$(words ...)内置可以提供阿拉伯计数,但是make不支持数学运算(你不能将1 + 1分配给某个东西并获得2,除非你从shell调用一些东西来为你完成它,或者使用一个同样错综复杂的宏操作).这对于INCREMENTAL计数器非常有用,但对于DECREMENT计数器则不太好.

我自己不使用它,但是最近,我需要编写一个递归函数来评估多二进制,多库构建环境中的库依赖关系,当你包含一些库时你需要知道引入OTHER库本身有其他依赖项(其中一些依赖于构建参数而有所不同),我使用类似于上面的$(eval)和计数器方法(在我的情况下,计数器用于确保我们不会以某种方式进入无穷无尽的循环,并作为诊断报告需要多少迭代).

其他一些东西没有价值,虽然对OP的Q没有意义:$(eval ...)提供了一种方法来规避make对循环引用的内部憎恶,当变量是一个宏类型时,这是很好的和强制执行的(初始化为=),而不是立即赋值(用:=初始化).有时你希望能够在自己的赋值中使用变量,而$(eval ...)将使你能够做到这一点.这里要考虑的重要一点是,在运行eval时,变量会被解析,并且已解析的部分不再被视为宏.如果你知道自己在做什么,并且你试图在自己的任务的RHS上使用变量,那么这通常是你想要发生的事情.

  SOMESTRING = foo

  # will error.  Comment out and re-run
  SOMESTRING = pre-${SOMESTRING}

  # works
  $(eval SOMESTRING = pre${SOMESTRING}

default:
  @echo ${SOMESTRING}
Run Code Online (Sandbox Code Playgroud)

快乐的化妆.


che*_*rno 19

对于跨平台支持,使命令分隔符(用于在同一行上执行多个命令)可配置.

例如,如果您在Windows平台上使用MinGW,则命令分隔符为&:

NUMBERS = 1 2 3 4
CMDSEP = &
doit:
    $(foreach number,$(NUMBERS),./a.out $(number) $(CMDSEP))
Run Code Online (Sandbox Code Playgroud)

这将在一行中执行连接命令:

./a.out 1 & ./a.out 2 & ./a.out 3 & ./a.out 4 &
Run Code Online (Sandbox Code Playgroud)

如其他地方所述,在*nix平台上使用CMDSEP = ;.


Wil*_*sem 6

这实际上并不是对问题的纯粹答案,而是解决此类问题的明智方法:

无需编写复杂的文件,只需将控制委托给一个bash脚本,例如:makefile

foo : bar.cpp baz.h
    bash script.sh
Run Code Online (Sandbox Code Playgroud)

和script.sh看起来像:

for number in 1 2 3 4
do
    ./a.out $number
done
Run Code Online (Sandbox Code Playgroud)

  • 这确实是一个简单的解决方法,但是它使您无法使用漂亮的“ make -j 4”选项来使进程并行运行。 (2认同)

小智 5

也许你可以使用:

xxx:
    for i in `seq 1 4`; do ./a.out $$i; done;
Run Code Online (Sandbox Code Playgroud)