如何在Makefile中编写循环?

我想执行以下命令:

./a.out 1
./a.out 2
./a.out 3
./a.out 4
.
.
. and so on

如何将这个东西写为Makefile中的循环?

328451 次浏览

如果你是在unix类型的平台上(我假设你使用了./a.out),下面的代码就可以做到这一点。

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(包含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

如果您正在使用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;

使用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没有任何好的算术宏,所以我在这里作弊,使用$(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。

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

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

我意识到这个问题已经是几年前的问题了,但是这篇文章可能仍然对某些人有用,因为它演示了一种不同于上面的方法,它既不依赖于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中调用某物来为你完成它,或者使用同样复杂的宏操作)。这适用于INCREMENTAL计数器,但不适用于DECREMENT计数器。

我自己不使用这个,但是最近,我需要编写一个递归函数来评估multi-binary库依赖关系,multi-library构建环境,你需要知道引进其他库时包括一些图书馆本身有其他依赖项(其中有些取决于建立参数),我使用一个美元(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}

快乐使等等。

这并不是对这个问题的纯粹回答,而是一种解决这类问题的聪明方法:

而不是写一个复杂的文件,简单地委托控制,例如一个bash脚本: makefile < / p >

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;
#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.

你可以使用set -e作为for循环的前缀。例子:

all:
set -e; for a in 1 2 3; do /bin/false; echo $$a; done

make将立即退出,退出代码为<> 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

一个简单的,shell /独立于平台的,纯宏解决方案是…

# GNU make (`gmake`) compatible; ref: <https://www.gnu.org/software/make/manual>
define EOL


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


.PHONY: target
target:
$(foreach i,$(call %sequence,10),./a.out ${i}${EOL})

在循环中动态地分配变量

for number in 1 2 3 4 ; do \ ...-solution的问题是,在循环中不能赋值变量. solution。$(eval VAR=...)只能在目标执行开始时已知赋值的内容时使用。如果赋值依赖于循环变量,VAR将为

为了避免这个问题,可以使用目标功能对循环建模。下面的例子从SRC / OBJ获取第n个文件,并将它们一起处理。使用此结构,您甚至可以使用$(eval ...)来处理循环变量,如VAR3所示。

makefile

SRC = f1.c f2.cpp f3.cpp
OBJ = f1.o f2.o f3.o


SRC2 = $(addsuffix _,$(SRC))
JOIN = $(join $(SRC2),$(OBJ))


PHONY: all
all : info loop


loop : $(JOIN)


$(JOIN) :
@# LOOP - CONTENT
@echo "TARGET: $@"
$(eval VAR1=$(word 1,$(subst _, ,$@)))
@echo "VAR1: "$(VAR1)
$(eval VAR2=$(word 2,$(subst _, ,$@)))
@echo "VAR2: "$(VAR2)
$(eval VAR3=$(subst .o,.x,$(VAR2)))
@echo "You can even substitute you loop variable VAR3: "$(VAR3)
#g++ -o $(VAR2) $(VAR1)
@echo


PHONY: info
info:
@printf "\n"
@echo "JOIN: "$(JOIN)
@printf "\n"

输出

$ make


JOIN: f1.c_f1.o f2.cpp_f2.o f3.cpp_f3.o


TARGET: f1.c_f1.o
VAR1: f1.c
VAR2: f1.o
You can even substitute you loop variable VAR3: f1.x
#g++ -o f1.o f1.c


TARGET: f2.cpp_f2.o
VAR1: f2.cpp
VAR2: f2.o
You can even substitute you loop variable VAR3: f2.x
#g++ -o f2.o f2.cpp


TARGET: f3.cpp_f3.o
VAR1: f3.cpp
VAR2: f3.o
You can even substitute you loop variable VAR3: f3.x
#g++ -o f3.o f3.cpp

这个答案,就像@Vroomfondel的答案一样,旨在以优雅的方式规避循环问题。

我的想法是让make将循环本身生成为导入的makefile,如下所示:

include Loop.mk
Loop.mk:Loop.sh
Loop.sh > $@

shell脚本可以像你喜欢的那样高级,但可以是一个最小的工作示例

#!/bin/bash
LoopTargets=""
NoTargest=5
for Target in `seq $NoTargest` ; do
File="target_${Target}.dat"
echo $File:data_script.sh
echo $'\t'./data_script.ss $Target
LoopTargets="$LoopTargets $File"
done
echo;echo;echo LoopTargets:=$LoopTargets

生成文件

target_1.dat:data_script.sh
./data_script.ss 1
target_2.dat:data_script.sh
./data_script.ss 2
target_3.dat:data_script.sh
./data_script.ss 3
target_4.dat:data_script.sh
./data_script.ss 4
target_5.dat:data_script.sh
./data_script.ss 5




LoopTargets:= target_1.dat target_2.dat target_3.dat target_4.dat target_5.dat

这样做的好处是make本身可以跟踪哪些文件已经生成,哪些文件需要(重新)生成。同样,这也使make能够使用-j标志进行并行化。

这招对我很管用:

NUM=4


a-out:
for (( i=1; i<=${NUM}; i++ )) \
do \
./a.out $$i ; \
done

版本

代码

files := $(wildcard ./*.txt ./**/*.go */**/*.js )


showFileFunc = echo "$(abspath ${1})\${2}"
delFileFunc = del "$(abspath ${1})\${2}"
cmdSplit = &
targetDisplay:
$(foreach curFile, ${files}, ${call showFileFunc,${dir $(curFile)},${notdir $(curFile)}} ${cmdSplit})
targetDelete:
$(foreach curFile, ${files}, ${call delFileFunc,${dir $(curFile)},${notdir $(curFile)}} ${cmdSplit})

测试目录

Makefile
📝1.txt
📂 myDir
- 📝foo.go
- 📝bar.go
- 📂subDir
- 📝qoo.js

测试CMD &输出

make showFile -s


output:
"C:/...\1.txt"
"C:/.../myDir\bar.go"
"C:/.../myDir\foo.go"          // since `.//**.js`
"C:/.../myDir/subDir\qoo.js"   // `.//**.js` can't but `*/**/*.js` is OK

参考