Comment écrire une boucle dans un Makefile?

218

Je veux exécuter les commandes suivantes:

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

Comment écrire cette chose comme une boucle dans un Makefile?

avd
la source
Similaire, mais légèrement plus générique: commandes bash multilignes dans makefile
nobar

Réponses:

273

Ce qui suit le fera si, comme je suppose par votre utilisation de ./a.out, vous êtes sur une plate-forme de type UNIX.

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

Testez comme suit:

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

produit:

1
2
3
4

Pour des gammes plus importantes, utilisez:

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

Cela produit 1 à 10 inclus, changez simplement la whilecondition de terminaison de 10 à 1000 pour une plage beaucoup plus grande comme indiqué dans votre commentaire.

Les boucles imbriquées peuvent se faire ainsi:

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

produisant:

1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
4 1
4 2
4 3
paxdiablo
la source
1
qwert est juste un nom cible qui n'est probablement pas un vrai fichier. Les règles Makefile ont besoin d'un objectif. Quant à l'erreur de syntaxe, il vous manque des trucs - voir mise à jour.
paxdiablo
2
Puisque vous supposez que son shell reconnaît ((...)), pourquoi ne pas utiliser le plus simple pour ((i = 0; i <WHATEVER; ++ i)); faire ...; terminé ?
Idelic
1
Notez que la seqcommande qui génère une séquence de nombres existe sur la plupart (tous?) Des systèmes Unix, vous pouvez donc écrire for number in ``seq 1 1000``; do echo $$number; done(Mettez un seul backtick de chaque côté de la commande seq, pas deux, je ne sais pas comment formater cela correctement en utilisant la syntaxe de stackoverflow)
Suzanne Dupéron
3
Merci, il me manquait le double $$ pour référencer la variable de boucle for.
Leif Gruenwoldt du
1
@ Jonz: le caractère de continuation de ligne est dû au fait que makechaque ligne est normalement traitée comme une chose à exécuter dans un sous-shell séparé . Sans suite, il essaierait d'exécuter un sous-shell avec juste (par exemple) for number in 1 2 3 4 ; do, sans le reste de la boucle. Avec lui, il devient effectivement une seule ligne du formulaire while something ; do something ; done, qui est une déclaration complète. La $$question est répondue ici: stackoverflow.com/questions/26564825/… , avec le code apparemment extrait de cette même réponse :-)
paxdiablo
267

Si vous utilisez GNU make, vous pouvez essayer

CHIFFRES = 1 2 3 4
fais le:
        $ (foreach var, $ (NUMBERS),. / a.out $ (var);)

qui va générer et exécuter

./a.out 1; ./a.out 2; ./a.out 3; ./a.out 4;
Idélique
la source
27
Cette réponse est meilleure à mon humble avis, car elle ne nécessite aucun shell, c'est un makefile pur (même s'il est spécifique à GNU).
Jocelyn delalande
7
le point-virgule est crucial sinon seule la première itération s'exécutera
Jeremy Leipzig
7
Cette solution masque le code de sortie de ./a.out 1. ./a.out 2sera exécuté malgré tout.
bobbogo
1
@Alexander: pourriez-vous préciser à quelle limitation vous faites référence?
Idelic
1
@Idelic, oui. Pour le makefile et le shellscript suivants, le shellscript fonctionne (passe l'argument à ls), tandis que le Makefile donne une erreur: make: execvp: / bin / sh: Liste d'arguments trop longue Makefile: ix.io/d5L Shellscript: ix.io / d5M Il s'agit d'un problème dans la fonction foreach que j'ai rencontré au travail, lorsqu'une liste inhabituellement longue de noms de fichiers (avec de longs chemins également) a été transmise à une commande. Nous avons dû trouver une solution de contournement.
Alexander
107

LA principale raison d'utiliser make IMHO est le -jdrapeau. make -j5exécutera 5 commandes shell à la fois. C'est bien si vous avez 4 processeurs, et un bon test de n'importe quel makefile.

Fondamentalement, vous voulez faire voir quelque chose comme:

.PHONY: all
all: job1 job2 job3

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

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

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

C'est -jsympa (bon signe). Pouvez-vous repérer la plaque de chaudière? On pourrait écrire:

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

pour le même effet (oui, c'est la même que la formulation précédente en ce qui concerne make , juste un peu plus compact).

Un peu de paramétrage pour que vous puissiez spécifier une limite sur la ligne de commande (fastidieux car maken'a pas de bonnes macros arithmétiques, donc je vais tricher ici et utiliser $(shell ...))

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

Vous exécutez cela avec make -j5 LAST=550, avec une valeur par LASTdéfaut de 1000.

bobbogo
la source
7
C'est certainement la meilleure réponse, pour la raison que Bobbogo a mentionnée ( -j ).
dbn
Une raison particulière pour laquelle il utilise les suffixes .PHONY? Sont-ils nécessaires pour quelque chose?
Seb
6
@seb: Sans .PHONY: all, make recherchera un fichier appelé all. Si un tel fichier existe, make vérifie ensuite l'heure de la dernière modification de ce fichier, et le makefile ne fera certainement pas ce que vous vouliez. La .PHONYdéclaration indique à make que allc'est une cible symbolique. Make considérera donc leall cible est toujours dépassée. Parfait. Voir le manuel.
bobbogo
4
Comment fonctionne cette ligne: $ {JOBS}: job%: À quoi sert le deuxième point-virgule? Je n'ai rien vu sur gnu.org/software/make/manual/make.htm
Joe
2
@JoeS gnu.org/software/make/manual/make.html#Static-Pattern (je pense que vous vouliez dire à quoi sert le 2e * deux-points * ). Ne les confondez pas avec les règles de modèle non agréables (à mon humble avis) . Règles de modèle statiques sont vraiment utiles à chaque fois que la liste des cibles peut être compensée par l' une des marque « s motifs Noddy (même pour les dépendances). Ici, j'en utilise un juste pour la commodité que tout ce qui correspond %à la règle est disponible comme $*dans la recette.
bobbogo
22

Je me rends compte que la question est vieille de plusieurs années, mais ce message peut toujours être utile à quelqu'un car il démontre une approche qui diffère de ce qui précède, et ne dépend ni des opérations du shell ni de la nécessité pour le développeur de supprimer un code dur codé chaîne de valeurs numériques.

la macro intégrée $ (eval ....) est votre amie. Ou peut être au moins.

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)

C'est un exemple simpliste. Il ne s'agrandira pas assez pour les grandes valeurs - cela fonctionne, mais comme la chaîne ITERATE_COUNT augmentera de 2 caractères (espace et point) pour chaque itération, à mesure que vous montez par milliers, il faut progressivement plus de temps pour compter les mots. Comme écrit, il ne gère pas l'itération imbriquée (vous auriez besoin d'une fonction d'itération et d'un compteur séparés pour le faire). Ceci est purement gnu make, aucune exigence de shell (bien que évidemment l'OP cherchait à exécuter un programme à chaque fois - ici, j'affiche simplement un message). Le if dans ITERATE est destiné à attraper la valeur 0, car $ (mot ...) se trompera autrement.

Notez que la chaîne croissante pour servir de compteur est utilisée parce que le $ (mots ...) intégré peut fournir un compte arabe, mais cette marque ne prend pas en charge les opérations mathématiques (vous ne pouvez pas affecter 1 + 1 à quelque chose et obtenir 2, sauf si vous invoquez quelque chose à partir du shell pour l'accomplir pour vous, ou si vous utilisez une opération de macro tout aussi compliquée). Cela fonctionne très bien pour un compteur INCRÉMENTAL, mais pas aussi bien pour un compteur DÉCRÉMENTAL.

Je ne l'utilise pas moi-même, mais récemment, j'ai eu besoin d'écrire une fonction récursive pour évaluer les dépendances des bibliothèques à travers un environnement de construction multi-binaire et multi-bibliothèque où vous devez savoir pour apporter D'AUTRES bibliothèques lorsque vous incluez une bibliothèque qui lui-même a d'autres dépendances (dont certaines varient en fonction des paramètres de construction), et j'utilise une méthode $ (eval) et counter similaire à la précédente (dans mon cas, le compteur est utilisé pour nous assurer que nous n'entrons pas dans une infinité boucle, et aussi comme diagnostic pour signaler combien d'itération était nécessaire).

Quelque chose d'autre ne vaut rien, bien que non significatif pour l'OP Q: $ (eval ...) fournit une méthode pour contourner l'horreur interne de make aux références circulaires, ce qui est tout à fait correct à appliquer lorsqu'une variable est un type macro (initialisé avec =), par rapport à une affectation immédiate (initialisée avec: =). Il y a des moments où vous voulez pouvoir utiliser une variable dans sa propre affectation, et $ (eval ...) vous permettra de le faire. La chose importante à considérer ici est qu'au moment où vous exécutez l'eval, la variable est résolue et la partie résolue n'est plus traitée comme une macro. Si vous savez ce que vous faites et que vous essayez d'utiliser une variable sur le RHS d'une affectation à elle-même, c'est généralement ce que vous voulez de toute façon.

  SOMESTRING = foo

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

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

default:
  @echo ${SOMESTRING}

Make'ing heureux.

paille
la source
20

Pour la prise en charge multiplateforme, rendez le séparateur de commandes (pour exécuter plusieurs commandes sur la même ligne) configurable.

Si vous utilisez MinGW sur une plateforme Windows par exemple, le séparateur de commandes est &:

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

Cela exécute les commandes concaténées sur une seule ligne:

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

Comme mentionné ailleurs, sur une plate-forme * nix, utilisez CMDSEP = ;.

cherno
la source
7

Ce n'est pas vraiment une pure réponse à la question, mais une façon intelligente de contourner ces problèmes:

au lieu d'écrire un fichier complexe, déléguez simplement le contrôle par exemple à un script bash comme: makefile

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

et script.sh ressemble à:

for number in 1 2 3 4
do
    ./a.out $number
done
Willem Van Onsem
la source
Je suis coincé à une étape lors de l'écriture d'un Makefile. J'ai le code suivant: set_var: @ NUM = 0; tandis que [[$$ NUM <1]]; do \ echo "je suis ici"; \ echo $$ NUM vidage $$ {NUM} .txt; \ var = "SSA_CORE $$ {NUM} _MAINEXEC"; \ echo $$ var; \ var1 = eval echo \$${$(var)}; \ echo $$ var1; \ ((NUM = NUM ​​+ 1)); \ done all: set_var ici SSA_CORE0_MAINEXEC est une variable d'environnement qui est déjà définie. Je souhaite donc que cette valeur soit évaluée ou imprimée à l'aide de la variable var1. Je l'ai essayé comme indiqué ci-dessus mais ne fonctionne pas. pLease aide.
XYZ_Linux
2
C'est en effet une solution de contournement facile, mais cela vous empêche d'utiliser la belle option "make -j 4" pour que les processus s'exécutent en parallèle.
TabeaKischka
1
@TabeaKischka: en effet. Ce n'était pas la méthode " recommandée ", mais cela signifie davantage si l'on a besoin de certaines fonctionnalités qui ne sont pas proposées par un Makefile, alors on peut recourir à une implémentation dans bashet donc utiliser les fonctionnalités. La boucle est l'une des caractéristiques pour lesquelles cela peut être démontré.
Willem Van Onsem
4

Vous pouvez peut-être utiliser:

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

Vous pouvez utiliser set -ecomme préfixe pour la boucle for. Exemple:

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

make quittera immédiatement avec un code de sortie <> 0 .

Frederik Teichert
la source
0

Bien que la boîte à outils de la table GNUmake ait une vraie whileboucle (quoi que cela signifie dans la programmation GNUmake avec ses deux ou trois phases d'exécution), si la chose dont vous avez besoin est une liste itérative, il existe une solution simple avec interval. Pour le plaisir, nous convertissons également les nombres en hexadécimal:

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

Production:

./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
Vroomfondel
la source
0

Une solution macro simple, indépendante du shell / de la plateforme, est ...

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

$(foreach i,$(call %sequence,10),$(info ./a.out ${i}))
rivy
la source
-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. 
Leslie Satenstein
la source