Come scrivere loop in un Makefile?


Risposte:


273

Quanto segue lo farà se, come suppongo dal tuo uso ./a.out, sei su una piattaforma di tipo UNIX.

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

Prova come segue:

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

produce:

1
2
3
4

Per gamme più grandi, utilizzare:

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

Questo produce da 1 a 10 inclusi, basta cambiare la whilecondizione di terminazione da 10 a 1000 per un intervallo molto più ampio come indicato nel tuo commento.

I loop nidificati possono essere eseguiti in questo modo:

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

produzione:

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 è solo un nome di destinazione che è improbabile che sia un file reale. Le regole del makefile richiedono un obiettivo. Per quanto riguarda l'errore di sintassi, ti mancano alcune cose - vedi aggiornamento.
paxdiablo,

2
Dato che stai supponendo che la sua shell riconosca ((...)), perché non usare il molto più semplice per ((i = 0; i <WHATEVER; ++ i)); fare ...; fatto ?
Idelic,

1
Nota che il seqcomando che genera una sequenza di numeri esiste sulla maggior parte (tutti?) Dei sistemi unix, quindi puoi scrivere for number in ``seq 1 1000``; do echo $$number; done(Metti un singolo backtick su ciascun lato del comando seq, non due, non so come formattarlo correttamente usando la sintassi di StackOverflow)
Suzanne Dupéron,

3
Grazie, mi mancava il doppio $$ per fare riferimento alla variabile for loop.
Leif Gruenwoldt,

1
@Jonz: il carattere di continuazione della linea è perché makenormalmente tratta ogni linea come un oggetto da eseguire in una sotto-shell separata . Senza continuazione, proverebbe a eseguire una subshell con solo (ad esempio) for number in 1 2 3 4 ; do, senza il resto del ciclo. Con esso, diventa effettivamente una singola riga del modulo while something ; do something ; done, che è una dichiarazione completa. La $$risposta è qui: stackoverflow.com/questions/26564825/… , con il codice apparentemente estratto da questa risposta :-)
paxdiablo,

266

Se stai usando GNU make, puoi provare

NUMERI = 1 2 3 4
fallo:
        $ (foreach var, $ (NUMBERS),. / a.out $ (var);)

che genererà ed eseguirà

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

27
Questa risposta è migliore di IMHO, perché non richiede l'uso di alcuna shell, è puro makefile (anche se è specifico per GNU).
Jocelyn delalande,

7
il punto e virgola è cruciale, altrimenti verrà eseguita solo la prima iterazione
Jeremy Leipzig,

7
Questa soluzione nasconde il codice di uscita di ./a.out 1. ./a.out 2sarà eseguito indipendentemente.
bobbogo,

1
@Alexander: potresti approfondire a quale limitazione ti riferisci?
Idelico il

1
@Idelic, si. Per il seguente makefile e shellscript, lo shellscript funziona (passa l'argomento a ls), mentre il Makefile fornisce un errore: make: execvp: / bin / sh: lista degli argomenti troppo lunga Makefile: ix.io/d5L Shellscript: ix.io / d5M Questo è un problema nella funzione foreach che ho riscontrato sul lavoro, quando un elenco insolitamente lungo di nomi di file (anche con percorsi lunghi) è stato passato a un comando. Abbiamo dovuto trovare una soluzione alternativa.
Alexander,

107

Il motivo principale per utilizzare make IMHO è il-j bandiera. make -j5eseguirà 5 comandi shell contemporaneamente. Questo è buono se hai 4 CPU, e un buon test di qualsiasi makefile.

Fondamentalmente, vuoi far vedere qualcosa del tipo:

.PHONY: all
all: job1 job2 job3

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

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

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

Questo è -jamichevole (un buon segno). Riesci a individuare la piastra della caldaia? Potremmo scrivere:

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

per lo stesso effetto (sì, questo è lo stesso della formulazione precedente per quanto riguarda make , solo un po 'più compatto).

Un ulteriore bit di parametrizzazione in modo da poter specificare un limite sulla riga di comando (noioso in quanto makenon ha buone macro aritmetiche, quindi trufferò qui e userò $(shell ...))

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

Si esegue con make -j5 LAST=550, con LASTimpostazione predefinita a 1000.


7
Questa è sicuramente la risposta migliore, per il motivo che ha menzionato bobbogo ( -j ).
dbn

Qualche motivo particolare che utilizza i suffissi .PHONY? Sono richiesti per qualcosa?
Seb

6
@seb: senza .PHONY: all, make cercherà un file chiamato all. Se esiste un file di questo tipo, controlla quindi l'ultima volta che il file è stato modificato e il makefile quasi certamente non farà ciò che intendevi. La .PHONYdichiarazione dice a make che allè un obiettivo simbolico. Make considererà quindi l' allobiettivo di essere sempre obsoleto. Perfetto. Vedi il manuale
bobbogo,

4
Come funziona questa riga: $ {JOBS}: job%: A cosa serve il secondo punto e virgola? Non ho visto nulla in gnu.org/software/make/manual/make.htm
Joe

2
@JoeS gnu.org/software/make/manual/make.html#Static-Pattern (penso che volevi dire qual è il secondo * colon * per ). Non confonderli con le Regole del modello non gradevoli (IMHO) . Le regole dei pattern statici sono davvero utili ogni volta che l'elenco degli obiettivi può essere abbinato a uno dei pattern noddy di make (lo stesso vale per le dipendenze). Qui ne uso uno solo per la comodità che tutto ciò che corrisponde %alla regola è disponibile come $*nella ricetta.
bobbogo,

22

Mi rendo conto che la domanda ha diversi anni, ma questo post potrebbe ancora essere utile a qualcuno in quanto dimostra un approccio che differisce da quanto sopra, e non fa affidamento né sulle operazioni della shell né sulla necessità per lo sviluppatore di scrivere un hardcoded stringa di valori numerici.

la macro incorporata $ (eval ....) è tua amica. O può essere almeno.

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)

Questo è un esempio semplicistico. Non si ridimensionerà abbastanza per valori grandi: funziona, ma poiché la stringa ITERATE_COUNT aumenterà di 2 caratteri (spazio e punto) per ogni iterazione, man mano che salirai in migliaia, ci vorrà progressivamente più tempo per contare le parole. Come scritto, non gestisce l'iterazione nidificata (per farlo è necessario disporre di una funzione di iterazione e di un contatore separati). Questo è puramente gnu, nessun requisito di shell (anche se ovviamente l'OP stava cercando di eseguire un programma ogni volta - qui, sto semplicemente visualizzando un messaggio). L'if all'interno di ITERATE ha lo scopo di catturare il valore 0, altrimenti $ (parola ...) si sbaglia.

Tieni presente che la stringa crescente da utilizzare come contatore viene utilizzata perché l'integrato $ (parole ...) può fornire un conteggio arabo, ma ciò rende altrimenti non supporta le operazioni matematiche (Non puoi assegnare 1 + 1 a qualcosa e ottenere 2, a meno che tu non stia invocando qualcosa dalla shell per realizzarla per te, o usando un'operazione macro ugualmente contorta). Questo funziona alla grande per un contatore INCREMENTALE, ma non altrettanto per uno DECREMENTO.

Non lo uso da solo, ma di recente ho dovuto scrivere una funzione ricorsiva per valutare le dipendenze delle librerie in un ambiente di compilazione multi-binario e multi-biblioteca in cui è necessario conoscere per portare ALTRE librerie quando si include qualche libreria che stesso ha altre dipendenze (alcune delle quali variano a seconda dei parametri di compilazione), e io uso un metodo $ (eval) e un contatore simile al precedente (nel mio caso, il contatore viene usato per assicurarci che in qualche modo non andiamo in un infinito loop e anche come diagnostica per segnalare quanta iterazione era necessaria).

Qualcos'altro che non vale nulla, anche se non significativo per Q: $ (eval ...) del PO fornisce un metodo per aggirare l'orrore interna di make ai riferimenti circolari, il che va bene e va fatto valere quando una variabile è di tipo macro (intializzata con =), rispetto a un compito immediato (inizializzato con: =). Ci sono volte in cui vuoi essere in grado di utilizzare una variabile all'interno del proprio compito e $ (eval ...) ti consentirà di farlo. La cosa importante da considerare qui è che nel momento in cui si esegue eval, la variabile viene risolta e quella parte che viene risolta non viene più trattata come una macro. Se sai cosa stai facendo e stai cercando di utilizzare una variabile sull'RHS di un compito a se stesso, questo è generalmente ciò che vuoi che accada comunque.

  SOMESTRING = foo

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

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

default:
  @echo ${SOMESTRING}

Lieto successo.


20

Per il supporto multipiattaforma, rendere configurabile il separatore dei comandi (per l'esecuzione di più comandi sulla stessa riga).

Se ad esempio stai usando MinGW su una piattaforma Windows, il comando di separazione è &:

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

Questo esegue i comandi concatenati in una riga:

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

Come menzionato altrove, su una piattaforma * nix utilizzare CMDSEP = ;.


7

Questa non è in realtà una risposta pura alla domanda, ma un modo intelligente per aggirare tali problemi:

invece di scrivere un file complesso, delegare semplicemente il controllo ad esempio a uno script bash come: makefile

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

e script.sh si presenta come:

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

Mi sono bloccato ad un passo mentre scrivevo un Makefile. Ho il seguente codice: set_var: @ NUM = 0; while [[$$ NUM <1]]; fai \ echo "Sono qui"; \ echo $$ NUM dump $$ {NUM} .txt; \ var = "SSA_CORE $$ {NUM} _MAINEXEC"; \ echo $$ var; \ var1 = eval echo \$${$(var)}; \ echo $$ var1; \ ((NUM = NUM ​​+ 1)); \ done all: set_var qui SSA_CORE0_MAINEXEC è una variabile d'ambiente che è già impostata. Quindi voglio che quel valore venga valutato o stampato usando la variabile var1. L'ho provato come mostrato sopra ma non funziona. per favore aiuto.
XYZ_Linux il

2
Questa è davvero una soluzione semplice, tuttavia ti impedisce di usare la bella opzione "make -j 4" per far funzionare i processi in parallelo.
TabeaKischka,

1
@TabeaKischka: davvero. Non era il modo " raccomandato ", ma è più indicato se si hanno bisogno di alcune funzionalità che non sono offerte da un Makefile, quindi si può ricorrere a un'implementazione bashe quindi utilizzare le funzionalità. Il loop è una delle funzionalità per le quali ciò può essere dimostrato.
Willem Van Onsem,

4

Forse puoi usare:

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

3

È possibile utilizzare set -ecome prefisso per il ciclo for. Esempio:

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

makeuscirà immediatamente con un codice di uscita <> 0.


0

Sebbene il toolkit della tabella GNUmake abbia un vero whileciclo (qualunque cosa ciò significhi nella programmazione GNUmake con le sue due o tre fasi di esecuzione), se la cosa necessaria è un elenco iterativo, esiste una soluzione semplice interval. Per divertirci, convertiamo anche i numeri in esadecimali:

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

Produzione:

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

Una semplice soluzione macro pura indipendente dalla shell / piattaforma è ...

%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. 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.