Wie schreibe ich eine Schleife in ein Makefile?


217

Ich möchte die folgenden Befehle ausführen:

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

Wie schreibe ich dieses Ding als Schleife in eine Makefile?


Ähnlich, aber etwas allgemeiner:
Mehrzeilige

Antworten:


273

Das Folgende wird es tun, wenn Sie sich, wie ich durch Ihre Verwendung von ./a.outannehme, auf einer UNIX-Plattform befinden.

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

Test wie folgt:

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

produziert:

1
2
3
4

Verwenden Sie für größere Reichweiten:

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

Dies gibt 1 bis einschließlich 10 aus. Ändern Sie einfach die whileAbschlussbedingung von 10 auf 1000 für einen viel größeren Bereich, wie in Ihrem Kommentar angegeben.

Verschachtelte Schleifen können folgendermaßen ausgeführt werden:

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

Herstellung:

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 ist nur ein Zielname, bei dem es sich wahrscheinlich nicht um eine echte Datei handelt. Makefile-Regeln benötigen ein Ziel. Was den Syntaxfehler betrifft, fehlen Ihnen einige Dinge - siehe Update.
Paxdiablo

2
Da Sie davon ausgehen, dass seine Shell ((...)) erkennt, können Sie das viel einfachere für ((i = 0; i <WHATEVER; ++ i)) verwenden. machen ...; getan ?
Idelic

1
Beachten Sie, dass der seqBefehl, der eine Folge von Zahlen generiert, auf den meisten (allen?) Unix-Systemen vorhanden ist, sodass Sie schreiben können for number in ``seq 1 1000``; do echo $$number; done(Setzen Sie einen einzelnen Backtick auf jede Seite des seq-Befehls, nicht auf zwei, ich weiß nicht, wie ich das richtig formatieren soll unter Verwendung der Syntax von stackoverflow)
Suzanne Dupéron

3
Danke, mir fehlte das doppelte $$, um auf die for-Schleifenvariable zu verweisen.
Leif Gruenwoldt

1
@Jonz: Das Zeilenfortsetzungszeichen ist, weil makenormalerweise jede Zeile als eine Sache behandelt wird, die in einer separaten Unter-Shell ausgeführt werden soll. Ohne Fortsetzung würde es versuchen, eine Subshell mit nur (zum Beispiel) for number in 1 2 3 4 ; doohne den Rest der Schleife auszuführen . Damit wird es effektiv zu einer einzelnen Zeile des Formulars while something ; do something ; done, was eine vollständige Aussage ist. Die $$Frage wird hier beantwortet: stackoverflow.com/questions/26564825/… , wobei der Code scheinbar aus dieser Antwort extrahiert wurde :-)
paxdiablo

265

Wenn Sie GNU make verwenden, können Sie es versuchen

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

die generieren und ausführen

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

27
Diese Antwort ist meiner Meinung nach besser, da keine Shell verwendet werden muss, sondern ein reines Makefile (auch wenn es GNU-spezifisch ist).
Jocelyn delalande

7
Das Semikolon ist entscheidend, sonst wird nur die erste Iteration ausgeführt
Jeremy Leipzig

7
Diese Lösung verbirgt den Exit-Code von ./a.out 1. ./a.out 2wird trotzdem ausgeführt.
Bobbogo

1
@Alexander: Könnten Sie näher erläutern, auf welche Einschränkung Sie sich beziehen?
Idelic

1
@ Idelic, ja. Für das folgende Makefile und Shellscript funktioniert das Shellscript (übergibt das Argument an ls), während das Makefile einen Fehler ausgibt: make: execvp: / bin / sh: Argumentliste zu lang Makefile: ix.io/d5L Shellscript: ix.io. / d5M Dies ist ein Problem in der foreach-Funktion, auf das ich bei der Arbeit gestoßen bin, als eine ungewöhnlich lange Liste von Dateinamen (auch mit langen Pfaden) an einen Befehl übergeben wurde. Wir mussten eine Problemumgehung finden.
Alexander

107

Der Hauptgrund für make IMHO ist die -jFlagge. make -j5führt 5 Shell-Befehle gleichzeitig aus. Dies ist gut, wenn Sie 4 CPUs haben und einen guten Test für jedes Makefile.

Grundsätzlich möchten Sie etwas sehen lassen wie:

.PHONY: all
all: job1 job2 job3

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

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

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

Das ist -jfreundlich (ein gutes Zeichen). Kannst du die Kesselplatte erkennen? Wir könnten schreiben:

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

für den gleichen Effekt (ja, dies ist das gleiche wie die vorherige Formulierung, was die Marke betrifft, nur ein bisschen kompakter).

Eine weitere Parametrisierung, damit Sie ein Limit in der Befehlszeile festlegen können (mühsam, da makees keine guten arithmetischen Makros gibt, also werde ich hier schummeln und verwenden $(shell ...))

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

Sie führen dies mit aus make -j5 LAST=550, wobei LASTstandardmäßig 1000 verwendet wird.


7
Dies ist definitiv die beste Antwort aus dem Grund, den Bobbogo erwähnte ( -j ).
dbn

Gibt es einen bestimmten Grund, warum .PHONY-Suffixe verwendet werden? Werden sie für irgendetwas benötigt?
Seb

6
@seb: Ohne .PHONY: allsucht make nach einer Datei namens all. Wenn eine solche Datei vorhanden ist, überprüft make die zuletzt geänderte Zeit dieser Datei, und das Makefile wird mit ziemlicher Sicherheit nicht das tun, was Sie beabsichtigt haben. Die .PHONYDeklaration sagt make, dass dies allein symbolisches Ziel ist. Make wird daher das allZiel als immer veraltet betrachten. Perfekt. Siehe das Handbuch.
Bobbogo

4
Wie funktioniert diese Zeile: $ {JOBS}: job%: Wofür ist das zweite Semikolon? Ich habe nichts in gnu.org/software/make/manual/make.htm
Joe

2
@JoeS gnu.org/software/make/manual/make.html#Static-Pattern (Ich denke, Sie meinten, wofür ist der 2. * Doppelpunkt * ). Verwechseln Sie sie nicht mit den unschönen (IMHO) Musterregeln . Statische Musterregeln sind immer dann nützlich, wenn die Liste der Ziele mit einem der Knotenmuster von make abgeglichen werden kann (dies gilt auch für die Abhängigkeiten). Hier verwende ich eine nur, um die Bequemlichkeit zu gewährleisten, dass alles, was mit %der Regel übereinstimmt, wie $*im Rezept verfügbar ist .
Bobbogo

22

Mir ist klar, dass die Frage mehrere Jahre alt ist, aber dieser Beitrag kann für jemanden von Nutzen sein, da er einen Ansatz zeigt, der sich von den oben genannten unterscheidet und weder von Shell-Operationen noch von der Notwendigkeit abhängt, dass der Entwickler einen fest codierten Ansatz erstellt Zeichenfolge numerischer Werte.

Das in $ (eval ....) eingebaute Makro ist dein Freund. Oder kann es zumindest sein.

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)

Das ist ein vereinfachtes Beispiel. Es wird für große Werte nicht schön skaliert - es funktioniert, aber da die ITERATE_COUNT-Zeichenfolge für jede Iteration um 2 Zeichen (Leerzeichen und Punkt) erhöht wird, dauert das Zählen der Wörter zunehmend länger, wenn Sie zu Tausenden aufsteigen. Wie geschrieben, werden verschachtelte Iterationen nicht verarbeitet (dazu benötigen Sie eine separate Iterationsfunktion und einen Zähler). Dies ist rein gnu make, keine Shell-Anforderung (obwohl das OP offensichtlich jedes Mal ein Programm ausführen wollte - hier zeige ich lediglich eine Nachricht an). Das if in ITERATE soll den Wert 0 abfangen, da sonst $ (word ...) fehlerhaft wird.

Beachten Sie, dass die wachsende Zeichenfolge, die als Zähler dient, verwendet wird, da das eingebaute $ (Wörter ...) eine arabische Zählung liefern kann, aber make ansonsten keine mathematischen Operationen unterstützt (Sie können etwas nicht 1 + 1 zuweisen und 2 erhalten, es sei denn, Sie rufen etwas aus der Shell auf, um es für Sie zu erledigen, oder verwenden eine ebenso komplizierte Makrooperation. Dies funktioniert hervorragend für einen INCREMENTAL-Zähler, jedoch nicht so gut für einen DECREMENT-Zähler.

Ich benutze dies nicht selbst, aber kürzlich musste ich eine rekursive Funktion schreiben, um Bibliotheksabhängigkeiten in einer Umgebung mit mehreren Binärdateien und mehreren Bibliotheken zu bewerten, in der Sie wissen müssen, wie Sie ANDERE Bibliotheken einbinden können, wenn Sie eine Bibliothek hinzufügen, die selbst hat andere Abhängigkeiten (von denen einige abhängig von den Build-Parametern variieren), und ich verwende eine $ (eval) - und Zählermethode ähnlich der oben genannten (in meinem Fall wird der Zähler verwendet, um sicherzustellen, dass wir nicht irgendwie in ein Endlos geraten Schleife und auch als Diagnose, um zu melden, wie viel Iteration erforderlich war).

Etwas anderes, das nichts wert ist, obwohl es für das Q: $ (eval ...) des OP nicht von Bedeutung ist, bietet eine Methode, um die interne Abscheu von make gegenüber Zirkelverweisen zu umgehen. Dies ist alles gut und in Ordnung, wenn eine Variable ein Makrotyp ist (initialisiert mit =) gegen eine sofortige Zuordnung (initialisiert mit: =). Es gibt Zeiten, in denen Sie eine Variable innerhalb ihrer eigenen Zuweisung verwenden möchten, und mit $ (eval ...) können Sie dies tun. Hierbei ist zu beachten, dass zum Zeitpunkt der Ausführung der Auswertung die Variable aufgelöst wird und der aufgelöste Teil nicht mehr als Makro behandelt wird. Wenn Sie wissen, was Sie tun, und Sie versuchen, eine Variable auf der rechten Seite einer Zuweisung für sich selbst zu verwenden, möchten Sie dies im Allgemeinen trotzdem tun.

  SOMESTRING = foo

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

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

default:
  @echo ${SOMESTRING}

Happy make'ing.


20

Konfigurieren Sie für die plattformübergreifende Unterstützung das Befehlstrennzeichen (zum Ausführen mehrerer Befehle in derselben Zeile).

Wenn Sie MinGW beispielsweise auf einer Windows-Plattform verwenden, lautet das Befehlstrennzeichen &:

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

Dadurch werden die verketteten Befehle in einer Zeile ausgeführt:

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

Wie an anderer Stelle erwähnt, auf einer * nix-Plattform verwenden CMDSEP = ;.


7

Dies ist keine reine Antwort auf die Frage, sondern eine intelligente Methode, um solche Probleme zu umgehen:

Anstatt eine komplexe Datei zu schreiben, delegieren Sie die Steuerung einfach an beispielsweise ein Bash-Skript wie: makefile

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

und script.sh sieht aus wie:

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

Ich bin beim Schreiben eines Makefiles bei einem Schritt hängen geblieben. Ich habe folgenden Code: set_var: @ NUM = 0; während [[$$ NUM <1]]; do \ echo "Ich bin hier"; \ 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 here SSA_CORE0_MAINEXEC ist eine Umgebungsvariable, die bereits festgelegt ist. Daher möchte ich, dass dieser Wert mit der Variablen var1 ausgewertet oder gedruckt wird. Ich habe es wie oben gezeigt versucht, aber es funktioniert nicht. Bitte helfen Sie.
XYZ_Linux

2
Dies ist in der Tat eine einfache Problemumgehung, verhindert jedoch, dass Sie die nette Option "make -j 4" verwenden, um Prozesse parallel auszuführen.
TabeaKischka

1
@ TabeaKischka: in der Tat. Es war nicht der " empfohlene " Weg, aber es ist eher gemeint, wenn man einige Funktionen benötigt, die nicht von einem Makefile angeboten werden, kann man auf eine Implementierung in zurückgreifen bashund somit die Funktionen verwenden. Die Schleife ist eines der Merkmale, für die dies demonstriert werden kann.
Willem Van Onsem

4

Vielleicht können Sie verwenden:

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

3

Sie können set -eals Präfix für die for-Schleife verwenden. Beispiel:

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

makewird sofort mit einem Exit-Code beendet <> 0.


0

Obwohl das GNUmake-Tabellen-Toolkit eine echte whileSchleife hat (was auch immer dies in der GNUmake-Programmierung mit zwei oder drei Ausführungsphasen bedeutet), gibt es eine einfache Lösung mit, wenn eine iterative Liste benötigt wird interval. Zum Spaß konvertieren wir die Zahlen auch in Hex:

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

Ausgabe:

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

Eine einfache, Shell / Plattform-unabhängige, reine Makrolösung ist ...

%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. 
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.