我想执行以下命令:
./a.out 1
./a.out 2
./a.out 3
./a.out 4
.
.
. and so on
如何将这个东西写成Makefile
中的循环?
答案 0 :(得分:240)
如果你正在使用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;
答案 1 :(得分:238)
如果我假设您使用./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
答案 2 :(得分:98)
使用make IMHO的主要原因是-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
这是-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=550
运行此操作,LAST
默认为1000。
答案 3 :(得分:20)
我意识到这个问题已经有好几年了,但这个帖子可能仍然对某人有用,因为它演示了一种与上述不同的方法,并且不依赖于shell操作,也不需要开发人员使用schpeel输出一个硬编码的数字值字符串。
$(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计数器则不太好。
我自己不使用它,但最近,我需要编写一个递归函数来评估跨多个二进制,多库构建环境中的库依赖关系,在这种情况下,当您包含时需要知道引入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}
快乐的化妆。
答案 4 :(得分:17)
对于跨平台支持,使命令分隔符(用于在同一行上执行多个命令)可配置。
例如,如果您在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 = ;
。
答案 5 :(得分:5)
对于这个问题,这不是一个真正的答案,而是解决这些问题的明智方法:
而不是编写复杂文件,只需将控制委托给例如bash脚本,如: 生成文件
foo : bar.cpp baz.h
bash script.sh
和script.sh看起来像:
for number in 1 2 3 4
do
./a.out $number
done
答案 6 :(得分:3)
您可以使用set -e
作为for循环的前缀。例如:
all:
set -e; for a in 1 2 3; do /bin/false; echo $$a; done
make
将立即退出,退出代码为<> 0
。
答案 7 :(得分:2)
也许你可以使用:
xxx:
for i in `seq 1 4`; do ./a.out $$i; done;
答案 8 :(得分:1)
一个简单的 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})
答案 9 :(得分:0)
尽管GNUmake table toolkit有一个真实的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
答案 10 :(得分:0)
使用for number in 1 2 3 4 ; do \ ...
解决方案的问题是,不能在循环内分配任何变量。 $(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
答案 11 :(得分:0)
这个答案,就像@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
标志进行并行化。
答案 12 :(得分:-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.