Como escrever um loop em um Makefile?


Respostas:


273

O seguinte será feito se, como eu suponho pelo seu uso ./a.out, você estiver em uma plataforma do tipo UNIX.

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

Teste da seguinte maneira:

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

produz:

1
2
3
4

Para intervalos maiores, use:

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

Isso gera de 1 a 10 inclusive, basta alterar a whilecondição de término de 10 para 1000 para um intervalo muito maior, conforme indicado no seu comentário.

Loops aninhados podem ser feitos assim:

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

produzindo:

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 é apenas um nome de destino que dificilmente será um arquivo real. Regras de makefile precisam de um destino. Quanto ao erro de sintaxe, você está perdendo algumas coisas - consulte a atualização.
paxdiablo

2
Como você está assumindo que seu shell reconhece ((...)), por que não usar o mais simples para ((i = 0; i <WHATEVER; ++ i)); Faz ...; feito ?
Idelic

1
Observe que o seqcomando que gera uma sequência de números existe na maioria dos sistemas unix (todos?), Para que você possa escrever for number in ``seq 1 1000``; do echo $$number; done(Coloque um único bastão em cada lado do comando seq, e não dois, não sei como formatar isso corretamente usando a sintaxe do stackoverflow)
Suzanne Dupéron 23/09

3
Obrigado, estava faltando o $$ duplo para fazer referência à variável de loop for.
Leif Gruenwoldt 4/10/2013

1
@ Jonz: o caractere de continuação de linha é porque makenormalmente trata cada linha como uma coisa a ser executada em um sub-shell separado . Sem continuação, ele tentaria executar um subshell com just (por exemplo) for number in 1 2 3 4 ; do, sem o restante do loop. Com isso, ele se torna efetivamente uma única linha do formulário while something ; do something ; done, que é uma declaração completa. A $$pergunta é respondida aqui: stackoverflow.com/questions/26564825/... , com o código aparentemente extraído dessa resposta muito :-)
paxdiablo

265

Se você estiver usando o GNU make, você pode tentar

NÚMEROS = 1 2 3 4
faça:
        $ (foreach var, $ (NÚMEROS),. / a.out $ (var);)

que irá gerar e executar

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

27
Esta resposta é IMHO melhor, porque não requer o uso de nenhum shell, é puro makefile (mesmo que seja específico do GNU).
Jocelyn delalande

7
o ponto e vírgula é crucial caso contrário, apenas a primeira iteração irá executar
Jeremy Leipzig

7
Esta solução oculta o código de saída de ./a.out 1. ./a.out 2será executado independentemente.
precisa saber é o seguinte

1
@ Alexander: você poderia elaborar a que limitação você está se referindo?
Idelic

1
@ Idelic, sim. Para o seguinte makefile e shellscript, o shellscript funciona (passa o argumento para ls), enquanto o Makefile fornece um erro: make: execvp: / bin / sh: Lista de argumentos muito longa Makefile: ix.io/d5L Shellscript: ix.io / d5M Esse é um problema na função foreach que encontrei no trabalho, quando uma lista incomumente longa de nomes de arquivos (com caminhos longos também) era passada para um comando. Tivemos que encontrar uma solução alternativa.
22414 Alexander

107

A principal razão para usar make IMHO é a-j bandeira. make -j5irá executar 5 comandos de shell de uma só vez. Isso é bom se você tiver 4 CPUs, e um bom teste de qualquer makefile.

Basicamente, você quer ver algo como:

.PHONY: all
all: job1 job2 job3

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

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

.PHONY: job3
job3: ; ./a.out 3

Isso é -jamigável (um bom sinal). Você consegue identificar a placa da caldeira? Poderíamos escrever:

.PHONY: all job1 job2 job3
all: job1 job2 job3
job1 job2 job3: job%:
    ./a.out $*

para o mesmo efeito (sim, é o mesmo que a formulação anterior no que diz respeito ao make , apenas um pouco mais compacto).

Um pouco mais de parametrização para que você possa especificar um limite na linha de comando (tedioso, pois makenão possui boas macros aritméticas, por isso vou trapacear aqui e usar $(shell ...))

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

Você executa isso com make -j5 LAST=550, com o LASTpadrão 1000.


7
Esta é definitivamente a melhor resposta, pelo motivo que o bobbogo mencionou ( -j ).
dbn

Algum motivo específico para usar sufixos .PHONY? Eles são necessários para alguma coisa?
Seb

6
@seb: Sem .PHONY: all, o make procurará um arquivo chamado all. Se esse arquivo existir, verifique se o arquivo foi modificado pela última vez, e o makefile quase certamente não fará o que você pretendia. A .PHONYdeclaração diz ao make que allé um alvo simbólico. A Make considerará, portanto, a allmeta sempre desatualizada. Perfeito. Veja o manual.
bobbogo 12/12/12

4
Como esta linha funciona: $ {JOBS}: job%: Para que serve o segundo ponto e vírgula? Eu não vi nada em gnu.org/software/make/manual/make.htm
Joe

2
@JoeS gnu.org/software/make/manual/make.html#Static-Pattern (acho que você quis dizer o que é o segundo * dois pontos * ). Não os confunda com as Regras de Padrão Desagradáveis ​​(IMHO) . As regras de padrões estáticos são realmente úteis sempre que a lista de destinos pode ser correspondida por um dos padrões nodosos de make (o mesmo se aplica às dependências). Aqui eu uso um apenas para a comodidade de que qualquer que seja o que corresponde %à regra está disponível como $*na receita.
22414 bobbogo

22

Sei que a pergunta tem vários anos, mas este post ainda pode ser útil para alguém, pois demonstra uma abordagem diferente da anterior, e não depende de operações do shell nem da necessidade de o desenvolvedor extrair um código codificado sequência de valores numéricos.

a macro embutida $ (eval ....) é sua amiga. Ou pode ser pelo menos.

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)

Esse é um exemplo simplista. Ele não será escalável para valores grandes - funciona, mas como a sequência ITERATE_COUNT aumenta em 2 caracteres (espaço e ponto) para cada iteração, à medida que você aumenta os milhares, leva progressivamente mais tempo para contar as palavras. Como está escrito, ele não trata de iteração aninhada (você precisaria de uma função de iteração separada e de um contador para fazer isso). Isso é puramente gnu make, sem necessidade de shell (embora obviamente o OP esteja procurando executar um programa a cada vez - aqui, estou apenas exibindo uma mensagem). O if dentro de ITERATE tem como objetivo capturar o valor 0, porque $ (word ...) ocorrerá um erro caso contrário.

Observe que a cadeia crescente para servir como contador é empregada porque o $ (words ...) builtin pode fornecer uma contagem arábica, mas essa marca não suporta operações matemáticas (você não pode atribuir 1 + 1 a algo e obter 2, a menos que você esteja invocando algo do shell para fazer isso por você ou usando uma operação macro igualmente complicada). Isso funciona muito bem para um contador INCREMENTAL, no entanto, não tão bem para um DECREMENT.

Eu não uso isso sozinho, mas recentemente precisei escrever uma função recursiva para avaliar as dependências da biblioteca em um ambiente de compilação multi-binária e com várias bibliotecas, onde você precisa saber para trazer OUTRAS bibliotecas quando incluir alguma biblioteca que em si tem outras dependências (algumas das quais variam dependendo dos parâmetros de compilação), e eu uso um método $ (eval) e counter semelhante ao descrito acima (no meu caso, o contador é usado para garantir que de alguma forma não entremos em um interminável loop e também como um diagnóstico para relatar a quantidade de iteração necessária).

Outra coisa que não vale nada, embora não seja significativa para o Q: $ do OP (eval ...), fornece um método para contornar a aversão interna do make a referências circulares, o que é bom e bom de aplicar quando uma variável é do tipo macro (inicializada com =) versus uma atribuição imediata (inicializada com: =). Há momentos em que você deseja usar uma variável dentro de sua própria atribuição, e $ (eval ...) permitirá que você faça isso. O importante a considerar aqui é que, no momento em que você executa a avaliação, a variável é resolvida e a parte resolvida não é mais tratada como uma macro. Se você sabe o que está fazendo e está tentando usar uma variável no RHS de uma tarefa para si mesma, geralmente é isso que você deseja que aconteça.

  SOMESTRING = foo

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

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

default:
  @echo ${SOMESTRING}

Feliz fazendo.


20

Para suporte a várias plataformas, configure o separador de comandos (para executar vários comandos na mesma linha).

Se você estiver usando o MinGW em uma plataforma Windows, por exemplo, o separador de comandos é &:

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

Isso executa os comandos concatenados em uma linha:

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

Como mencionado em outro lugar, em uma plataforma * nix, use CMDSEP = ;.


7

Esta não é realmente uma resposta pura para a pergunta, mas uma maneira inteligente de solucionar esses problemas:

em vez de escrever um arquivo complexo, simplesmente delegue o controle para, por exemplo, um script bash como: makefile

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

e script.sh se parece com:

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

Fiquei paralisado enquanto escrevia um Makefile. Eu tenho o seguinte código: set_var: @ NUM = 0; while [[$$ NUM <1]]; faça eco "estou aqui"; \ echo $$ NUM despejo $$ {NUM} .txt; \ var = "SSA_CORE $$ {NUM} _MAINEXEC"; \ echo $$ var; \ var1 = eval echo \$${$(var)}; \ echo $$ var1; \ ((NUM = NUM ​​+ 1)); \ done all: set_var here SSA_CORE0_MAINEXEC é uma variável de ambiente que já está configurada. Portanto, desejo que esse valor seja avaliado ou impresso usando a variável var1. Eu tentei como mostrado acima, mas não está funcionando. por favor ajude.
XYZ_Linux 17/01

2
Essa é realmente uma solução fácil, mas impede que você use a boa opção "make -j 4" para que os processos sejam executados em paralelo.
TabeaKischka

1
@TabeaKischka: de fato. Não era o caminho " recomendado ", mas é mais importante se você precisar de alguns recursos que não são oferecidos por um Makefile, e então poderá recorrer a uma implementação bashe, assim, usá-los. O loop é um dos recursos pelos quais isso pode ser demonstrado.
Willem Van Onsem 19/07/2018

4

Talvez você possa usar:

xxx:
    for i in `seq 1 4`; do ./a.out $$i; done;

3

Você pode usar set -ecomo um prefixo para o loop for. Exemplo:

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

makesairá imediatamente com um código de saída <> 0.


0

Embora o kit de ferramentas da tabela GNUmake possua um whileloop verdadeiro (o que quer que isso signifique na programação do GNUmake com suas duas ou três fases de execução), se o necessário é uma lista iterativa, existe uma solução simples interval. Por diversão, também convertemos os números em hexadecimal:

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))

Resultado:

./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

Uma solução macro pura, simples e independente de shell / plataforma é ...

%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. 
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.