如何在Makefile中编写循环?
我想要执行以下命令:
./a.out 1 ./a.out 2 ./a.out 3 ./a.out 4 . . . and so on
如何把这个东西写成一个Makefile
的循环?
如果我使用./a.out
假设你是在一个UNIXtypes的平台上,那么下面将会这样做。
for number in 1 2 3 4 ; do \ ./a.out $$number ; \ done
testing如下:
qwert: for number in 1 2 3 4 ; do \ echo $$number ; \ done
生产:
1 2 3 4
对于更大的范围,使用:
qwert: number=1 ; while [[ $$number -le 10 ]] ; do \ echo $$number ; \ ((number = number + 1)) ; \ done
这将输出1到10(包括1和10),只需将您在注释中指示的更大的范围从10到1000更改为10到1000。
嵌套循环可以这样做:
qwert: 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
生产:
1 1 1 2 1 3 2 1 2 2 2 3 3 1 3 2 3 3 4 1 4 2 4 3
如果你使用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;
使用恕我直言的主要原因是-j
标志。 make -j5
会一次运行5个shell命令。 如果你有4个CPU,这是很好的,并且对任何makefile都有很好的testing。
基本上,你想让看到像这样的东西:
.PHONY: all all: job1 job2 job3 .PHONY: job1 job1: ; ./a.out 1 .PHONY: job2 job2: ; ./a.out 2 .PHONY: job3 job3: ; ./a.out 3
这是-j
友好(一个好兆头)。 你能发现锅炉板吗? 我们可以写:
.PHONY: all job1 job2 job3 all: job1 job2 job3 job1 job2 job3: job%: ./a.out $*
同样的效果(是的,就制作而言,这和以前的表述是一样的,只是更简洁一些)。
进一步的参数化位,以便你可以在命令行上指定一个限制(繁琐,因为make
没有任何好的算术macros,所以我会在这里作弊并使用$(shell ...)
)
LAST := 1000 NUMBERS := $(shell seq 1 ${LAST}) JOBS := $(addprefix job,${NUMBERS}) .PHONY: all ${JOBS} all: ${JOBS} ; echo "$@ success" ${JOBS}: job%: ; ./a.out $*
你用make -j5 LAST=550
运行这个, LAST
默认为1000。
我意识到这个问题已经有好几年了,但是这个post对于某个人来说可能还是有用的,因为它展示了一种与上面不同的方法,它不依赖于shell操作,也不需要开发人员拼出硬编码数字值的string。
$(eval ….)内置macros是你的朋友。 或者至less可以。
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)
这是一个简单的例子。 对于较大的值,它不会很好地扩展 – 它可以工作,但是随着ITERATE_COUNTstring每次迭代会增加2个字符(空格和点),当您进入数千个字符时,需要花费更长的时间来计算字数。 正如所写,它不处理嵌套的迭代(你需要一个单独的迭代函数和计数器来这样做)。 这纯粹是GNU make,没有shell要求(尽pipeOP显然每次都想运行一个程序 – 在这里,我只是在显示一条消息)。 ITERATE中的if旨在捕获值0,否则$(word …)将会出错。
请注意,由于$(words …)builtin可以提供一个阿拉伯数,但是这个数据操作不支持math运算,所以我们使用了不断增长的string作为计数器。(您不能将1 + 1赋值给某个值,除非你从shell中调用某些东西来完成它,或者使用同样复杂的macros操作)。 这对INCREMENTAL计数器非常有用,但是对于DECREMENT计数器来说不太好。
我自己并没有使用它,但是最近我需要编写一个recursion函数来评估跨多个二进制,多库构build环境的库依赖关系,在这个环境中你需要知道如果包含一些库本身有其他的依赖关系(其中一些取决于构build参数),我使用类似于上面的$(eval)和计数器方法(在我的情况下,计数器用来确保我们不会无休止地进入一个无尽的循环,也作为诊断来报告需要多less迭代)。
其他东西什么都不值钱,虽然对于OP的Q来说并不重要:$(eval …)提供了一种方法来规避make对内部循环引用的厌恶,当variables是一个macrostypes时,这是非常好的, =),而不是立即赋值(用=来初始化)。 有时候你希望能够在自己的任务中使用一个variables,$(eval …)将使你能够做到这一点。 这里要考虑的重要一点是,在你运行eval的时候,variables被parsing了,被parsing的那部分不再被视为macros。 如果你知道自己在做什么,而且你正在尝试在一个任务的RHS上使用一个variables,那么通常这就是你想要发生的事情。
SOMESTRING = foo # will error. Comment out and re-run SOMESTRING = pre-${SOMESTRING} # works $(eval SOMESTRING = pre${SOMESTRING} default: @echo ${SOMESTRING}
快乐的make'ing。
对于跨平台支持,可以configuration命令分隔符(用于在同一行上执行多个命令)。
例如,如果您在Windows平台上使用MinGW,则命令分隔符为&
:
NUMBERS = 1 2 3 4 CMDSEP = & doit: $(foreach number,$(NUMBERS),./a.out $(number) $(CMDSEP))
这将在一行中执行连接的命令:
./a.out 1 & ./a.out 2 & ./a.out 3 & ./a.out 4 &
如别处所述,在* nix平台上使用CMDSEP = ;
。
这个问题并不是纯粹的答案,而是解决这些问题的一个聪明的方法:
而不是编写一个复杂的文件,只需将控制委托给一个bash脚本,比如:makefile
foo : bar.cpp baz.h bash script.sh
和script.sh看起来像:
for number in 1 2 3 4 do ./a.out $number done
也许你可以使用:
xxx: for i in `seq 1 4`; do ./a.out $$i; done;
您可以使用set -e
作为for循环的前缀。 例:
all: set -e; for a in 1 2 3; do /bin/false; echo $$a; done
make
会立即退出,退出代码<> 0
。
#I have a bunch of files that follow the naming convention #soxfile1 soxfile1.o soxfile1.sh soxfile1.ini soxfile1.txt soxfile1.err #soxfile2 soxfile2.o soxfile2.sh soxfile2.ini soxfile2.txt soxfile2.err #sox... .... ..... .... .... .... #in the makefile, only select the soxfile1.. soxfile2... to install dir #My GNU makefile solution follows: tgt=/usr/local/bin/ #need to use sudo tgt2=/backup/myapplication/ #regular backup install: for var in $$(ls -f sox* | grep -v '\.' ) ; \ do \ sudo cp -f $$var ${TGT} ; \ cp -f $$var ${TGT2} ; \ done #The ls command selects all the soxfile* including the *.something #The grep command rejects names with a dot in it, leaving #My desired executable files in a list.