266 votes

Comment écrire une boucle dans un Makefile ?

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 en tant que boucle dans une Makefile ?

0 votes

Similaire, mais légèrement plus générique : Commandes bash multilignes dans le makefile

339voto

paxdiablo Points 341644

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

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

Faites le test comme suit :

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

produit :

1
2
3
4

Pour des plages plus importantes, utilisez :

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

Cette sortie va de 1 à 10 inclus, il suffit de changer l'option while de 10 à 1000 pour une gamme beaucoup plus large comme indiqué dans votre commentaire.

Emboîté Les boucles peuvent être faites 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

0 votes

Mais si je veux aller jusqu'à 1000, alors ne pouvons-nous pas spécifier la limite ?

0 votes

Une chose est : à quoi sert l'étiquette qwert ? Une autre question concerne les boucles imbriquées. Une erreur de syntaxe apparaît ici for n1 in 0 1 2 ; do \ for n2 in 1 2 3 ; do \ ./a.out $$n1 $$n2 ; \ done done

1 votes

Qwert est juste un nom de cible qui a peu de chances d'être un vrai fichier. Les règles de Makefile ont besoin d'une cible. Pour ce qui est de l'erreur de syntaxe, il vous manque des éléments - voir la mise à jour.

306voto

Idelic Points 4196

Si vous utilisez GNU make, vous pourriez essayer de

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

qui générera et exécutera

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

36 votes

Cette réponse est à mon avis meilleure, car elle ne nécessite pas l'utilisation d'un shell, c'est un makefile pur (même s'il est spécifique à GNU).

10 votes

Le point-virgule est crucial, sinon seule la première itération sera exécutée.

9 votes

Cette solution permet de masquer le code de sortie de ./a.out 1 . ./a.out 2 sera exécuté quoi qu'il en soit.

130voto

bobbogo Points 4201

LE La principale raison d'utiliser make IMHO est la -j drapeau. make -j5 lancera 5 commandes shell en même temps. C'est une bonne chose si vous avez 4 processeurs, et un bon test de tout makefile.

En gros, 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 -j amical (un bon signe). Pouvez-vous repérer le texte passe-partout ? Nous pourrions é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 chose que la formulation précédente pour ce qui est de l'utilisation de l'option faire mais un peu plus compact).

Un paramétrage supplémentaire permettant de spécifier une limite sur la ligne de commande (aussi fastidieuse que make ne dispose pas de bonnes macros arithmétiques, je vais donc 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 l'exécutez avec make -j5 LAST=550 avec LAST par défaut à 1000.

8 votes

C'est certainement la meilleure réponse, pour la raison que bobbogo a mentionnée ( -j ).

0 votes

Y a-t-il une raison particulière à l'utilisation des suffixes .PHONY ? Sont-ils nécessaires pour quelque chose ?

7 votes

@seb : Sans .PHONY: all make va chercher un fichier appelé all . Si un tel fichier existe, make vérifie alors le last-changed-time de ce fichier, et le fichier makefile ne fera presque certainement pas ce que vous vouliez. Le site .PHONY La déclaration indique que all est une cible symbolique. Make va donc considérer le all cible pour être toujours dépassée. Parfait. Voir le manuel.

24voto

s.straw Points 51

Je me rends compte que la question date de plusieurs années, mais ce message peut encore être utile à quelqu'un, car il présente une approche différente de celle décrite ci-dessus, qui ne repose ni sur les opérations du shell ni sur la nécessité pour le développeur d'élaborer une chaîne de valeurs numériques codées en dur.

la macro intégrée $(eval ....) est votre amie. Ou du moins, elle peut l'être.

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'adaptera pas bien aux grandes valeurs - il fonctionne, mais comme la chaîne ITERATE_COUNT augmentera de 2 caractères (espace et point) pour chaque itération, il faudra progressivement plus de temps pour compter les mots à mesure que vous atteindrez les milliers. Tel qu'il est écrit, il ne gère pas les itérations imbriquées (il faudrait une fonction d'itération et un compteur séparés pour le faire). C'est purement gnu make, aucun shell n'est requis (bien que de toute évidence l'OP cherchait à exécuter un programme à chaque fois -- ici, je ne fais qu'afficher un message). Le if dans ITERATE est destiné à attraper la valeur 0, parce que $(word...) se trompera autrement.

Notez que la chaîne croissante servant de compteur est employée parce que le constructeur $(words...) peut fournir un compte arabe, mais que make ne supporte pas autrement les opérations mathématiques (vous ne pouvez pas assigner 1+1 à quelque chose et obtenir 2, à moins que vous n'invoquiez quelque chose du shell pour l'accomplir pour vous, ou que vous utilisiez une opération macro tout aussi alambiquée). Cela fonctionne très bien pour un compteur INCREMENTAL, mais pas si bien pour un compteur DECREMENTAL.

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 dans un environnement de compilation multi-binaire et multi-bibliothèque où vous devez savoir s'il faut intégrer d'AUTRES bibliothèques lorsque vous incluez une bibliothèque qui a elle-même d'autres dépendances (dont certaines varient en fonction des paramètres de compilation), et j'utilise une méthode $(eval) et compteur similaire à celle décrite ci-dessus (dans mon cas, le compteur est utilisé pour s'assurer que nous n'entrons pas d'une manière ou d'une autre dans une boucle sans fin, et aussi comme un diagnostic pour signaler combien d'itérations ont été nécessaires).

Autre chose qui n'a pas d'importance, mais qui n'a rien à voir avec la question de l'OP : $(eval...) fournit une méthode pour contourner l'horreur interne de make pour les références circulaires, qui est très bien d'appliquer quand une variable est un type macro (initialisée avec =), par opposition à une affectation immédiate (initialisée avec :=). Il arrive que vous souhaitiez 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 qui est 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 qu'il se passe de toute façon.

  SOMESTRING = foo

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

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

default:
  @echo ${SOMESTRING}

Bon travail.

22voto

cherno Points 180

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

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

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

Ceci exécute les commandes concaténées en une seule ligne :

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

Comme mentionné ailleurs, sur une plateforme *nix, utilisez CMDSEP = ; .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X