如何在Makefile中编写循环?


Answers:


273

如果按照我的假设,如果您使用的./a.out是UNIX类型的平台,则将执行以下操作。

for number in 1 2 3 4 ; do \
    ./a.out $$number ; \
done

测试如下:

target:
    for number in 1 2 3 4 ; do \
        echo $$number ; \
    done

产生:

1
2
3
4

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

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

这将输出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

生产:

1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
4 1
4 2
4 3

1
qwert只是一个目标名称,不太可能是真实文件。Makefile规则需要一个目标。至于语法错误,您缺少一些东西-请参阅更新。
paxdiablo

2
由于您假设他的外壳可以识别((...)),因此为什么不使用简单得多的((i = 0; i <WHATEVER; ++ i)); 做...; 完成了吗?
Idelic

1
请注意,seq在大多数(所有?)Unix系统上都存在生成数字序列的命令,因此您可以编写for number in ``seq 1 1000``; do echo $$number; done(在seq命令的每一侧都放一个反引号,而不是两个,我不知道如何正确格式化它)使用stackoverflow的语法)
SuzanneDupéron2013年

3
谢谢,我错过了引用$ for循环变量的双重$$。
Leif Gruenwoldt

1
@Jonz:行继续符是因为make通常将每一行视为在单独的子shell中运行的事物。如果没有继续,它将尝试仅使用(例如)运行子shell for number in 1 2 3 4 ; do,而没有循环的其余部分。有了它,它实际上变成了form的一行while something ; do something ; done,这是一个完整的语句。在$$这里回答问题:stackoverflow.com/questions/26564825/…,似乎是从这个答案中提取的代码:-)
paxdiablo

265

如果您使用的是GNU make,则可以尝试

号码= 1 2 3 4
做:
        $(foreach var,$(NUMBERS),。/ a.out $(var);)

这将生成并执行

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

27
恕我直言,这个答案更好,因为它不需要使用任何shell,它是纯makefile(即使它是GNU特定的)。
Jocelyn delalande 2011年

7
分号至关重要,否则仅执行第一次迭代
杰里米·莱比锡

7
此解决方案隐藏的退出代码./a.out 1./a.out 2不管执行。
bobbogo

1
@亚历山大:你能详细说明你指的是什么限制吗?
Idelic

1
@Idelic,是的。对于以下makefile和shellscript,shellscript起作用(将参数传递给ls),而Makefile给出错误:make:execvp:/ bin / sh:参数列表太长 Makefile:ix.io/d5L Shellscript:ix.io / d5M 这是我在工作中遇到的foreach函数中的一个问题,当一个不寻常的长文件名列表(也包含长路径)传递给命令时。我们必须找到一种解决方法。
亚历山大

107

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

基本上,您希望使之类似于:

.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而言,这与以前的配方相同,只是更加紧凑)。

进一步的参数设置,以便您可以在命令行上指定一个限制(这很麻烦,因为make它没有任何好的算术宏,因此我将在此处作弊并使用$(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=550LAST默认为1000。


7
绝对是最好的答案,因为bobbogo提到了(-j)。
dbn 2012年

使用.PHONY后缀的任何特殊原因?他们需要什么吗?
2012年

6
@seb:如果没有.PHONY: all,make会查找名为的文件all。如果存在这样的文件,那么make会检查该文件的上次更改时间,并且makefile几乎可以肯定不会执行您想要的操作。该.PHONY声明告诉make all是一个象征性的目标。因此,Make会认为all目标始终过时。完善。请参阅手册。
bobbogo 2012年

4
该行如何工作:$ {JOBS}:job%:第二个分号是做什么用的?我在gnu.org/software/make/manual/make.htm中

2
@JoeS gnu.org/software/make/manual/make.html#Static-Pattern(我想您的意思是“第二个冒号”)。不要将它们与unnice(IMHO)模式规则混淆。只要目标列表可以由make的 “ noddy”模式之一匹配(相同于依赖项),静态模式规则就非常有用。在这里,我只是为了方便起见,使用与%该规则中的规则相匹配的内容$*
bobbogo 2014年

22

我意识到这个问题已有几年历史了,但是由于该帖子演示了一种与上述方法不同的方法,因此它可能仍对某人有用,并且它既不依赖于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)

这是一个简单的例子。对于较大的值,它不会很好地缩放-它可以工作,但是随着ITERATE_COUNT字符串每次迭代将增加2个字符(空格和点),当您达到数千时,对单词的计数将逐渐变长。如所写,它不处理嵌套迭代(您需要一个单独的迭代函数和计数器来执行此操作)。这纯粹是gnu make,没有shell要求(尽管显然OP每次都希望运行一个程序-在这里,我只是显示一条消息)。ITERATE中的if旨在捕获值0,因为$(word ...)否则会出错。

请注意,由于内置的​​$(words ...)可以提供阿拉伯数字,因此使用了用作计数器的不断增长的字符串,但是make否则不支持数学运算(您不能将1 + 1赋给某对象并得到2,除非您是从shell调用某些东西来为您完成此操作,或者使用等价的宏操作)。这对于递增计数器非常有用,但是对于递减计数器却不太好。

我本人并没有使用它,但是最近,我需要编写一个递归函数来评估跨多二进制,多库构建环境的库依赖关系,在其中需要包括其他库时引入其他库。本身还有其他依赖项(其中一些依赖于构建参数而有所不同),我使用类似于上面的$(eval)和counter方法(在我的情况下,counter用于确保我们不会以某种方式陷入困境循环,并作为诊断报告需要多少迭代)。

其他东西一无是处,尽管对OP的意义不大:$(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}

快乐的化妆。


20

为了实现跨平台支持,请配置命令分隔符(用于在同一行上执行多个命令)。

例如,如果您在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 = ;


7

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

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

foo : bar.cpp baz.h
    bash script.sh

和script.sh看起来像:

for number in 1 2 3 4
do
    ./a.out $number
done

我在编写Makefile时遇到了困难。我有以下代码:set_var:@ NUM = 0; 而[[$$ NUM <1]]; 回显“我在这里”;\ echo $$ NUM dump $$ {NUM} .txt; \ var =“ SSA_CORE $$ {NUM} _MAINEXEC”;\ echo $$ var; \ var1 = eval echo \$${$(var)}; \ echo $$ var1; \((NUM = NUM​​ + 1)); \完成所有操作:set_var这里SSA_CORE0_MAINEXEC是一个已经设置的环境变量。所以我希望使用变量var1对该值进行评估或打印。我尝试了如上所示,但无法正常工作。请帮忙。
XYZ_Linux 2014年

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

1
@TabeaKischka:的确如此。这不是“ 推荐 ”的方式,而是意味着如果某个人需要一些Makefile未提供的功能,则可以回退到其中的实现bash并使用这些功能。循环是可以证明这一点的功能之一。
Willem Van Onsem '18



0

尽管GNUmake表工具包具有一个真实的while循环(无论在GNUmake编程中具有两个或三个执行阶段),但如果需要的是迭代列表,则可以使用提供一个简单的解决方案interval。有趣的是,我们也将数字转换为十六进制:

include gmtt/gmtt.mk

# generate a list of 20 numbers, starting at 3 with an increment of 5
NUMBER_LIST := $(call interval,3,20,5)

# convert the numbers in hexadecimal (0x0 as first operand forces arithmetic result to hex) and strip '0x'
NUMBER_LIST_IN_HEX := $(foreach n,$(NUMBER_LIST),$(call lstrip,$(call add,0x0,$(n)),0x))

# finally create the filenames with a simple patsubst
FILE_LIST := $(patsubst %,./a%.out,$(NUMBER_LIST_IN_HEX))

$(info $(FILE_LIST))

输出:

./a3.out ./a8.out ./ad.out ./a12.out ./a17.out ./a1c.out ./a21.out ./a26.out ./a2b.out ./a30.out ./a35.out ./a3a.out ./a3f.out ./a44.out ./a49.out ./a4e.out ./a53.out ./a58.out ./a5d.out ./a62.out

0

一个简单的,与shell /平台无关的纯宏解决方案是...

%sequence = $(if $(word ${1},${2}),$(wordlist 1,${1},${2}),$(call %sequence,${1},${2} $(words _ ${2})))

$(foreach i,$(call %sequence,10),$(info ./a.out ${i}))

-1
#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. 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.