Verwendung von Shell-Befehlen in Makefile

99

Ich versuche, das Ergebnis von lsin anderen Befehlen (z. B. echo, rsync) zu verwenden:

all:
    <Building, creating some .tgz files - removed for clarity>
    FILES = $(shell ls)
    echo $(FILES)

Aber ich verstehe:

make
FILES = Makefile file1.tgz file2.tgz file3.tgz
make: FILES: No such file or directory
make: *** [all] Error 1

Ich habe versucht echo $$FILES, echo ${FILES}und echo $(FILES)ohne Glück.

Adam Matan
quelle

Antworten:

149

Mit:

FILES = $(shell ls)

Darunter allso eingerückt , ist es ein Build-Befehl. Dies wird also erweitert $(shell ls)und versucht dann, den Befehl auszuführen FILES ....

Wenn FILESes sich um eine makeVariable handeln soll, müssen diese Variablen außerhalb des Rezeptteils zugewiesen werden, z.

FILES = $(shell ls)
all:
        echo $(FILES)

Dies bedeutet natürlich, dass vor dem Ausführen eines der Befehle, mit denen die .tgz-Dateien erstellt werden, die Option FILES"Ausgabe von ls" festgelegt wird . (Wie Kaz bemerkt, wird die Variable jedes Mal neu erweitert, so dass sie schließlich die .tgz-Dateien enthält. Einige Markenvarianten müssen dies aus Gründen der Effizienz und / oder Korrektheit vermeiden. 1 )FILES := ...

Wenn FILESes sich um eine Shell-Variable handeln soll, können Sie sie festlegen, müssen sie jedoch in Shell-ese ohne Leerzeichen ausführen und in Anführungszeichen setzen:

all:
        FILES="$(shell ls)"

Jede Zeile wird jedoch von einer separaten Shell ausgeführt, sodass diese Variable nicht bis zur nächsten Zeile überlebt. Sie müssen sie daher sofort verwenden:

        FILES="$(shell ls)"; echo $$FILES

Dies ist alles ein bisschen albern, da die Shell *(und andere Shell-Glob-Ausdrücke) in erster Linie für Sie erweitert wird, sodass Sie einfach:

        echo *

als Ihr Shell-Befehl.

Schließlich als allgemeine Regel (für dieses Beispiel nicht wirklich anwendbar): Wie Esperanto in Kommentaren feststellt, ist die Verwendung der Ausgabe von lsnicht vollständig zuverlässig (einige Details hängen von den Dateinamen und manchmal sogar von der Version ab ls; einige Versionen lsversuchen, die Ausgabe zu bereinigen in manchen Fällen). So wie l0b0 und idelic Note, wenn Sie GNU verwenden machen Sie verwenden können , $(wildcard)und $(subst ...)alles , was im Inneren zu erreichen makeselbst (alle „weird Zeichen in Dateinamen“ Probleme zu vermeiden). (In shSkripten, einschließlich des Rezeptteils von Makefiles, besteht eine andere Methode darin find ... -print0 | xargs -0, das Stolpern über Leerzeichen, Zeilenumbrüche, Steuerzeichen usw. zu vermeiden.)


1 In der Dokumentation zu GNU Make wird weiter darauf hingewiesen, dass POSIX 2012 eine zusätzliche ::=Zuweisung vorgenommen hat . Ich habe dafür weder einen Schnellreferenzlink zu einem POSIX-Dokument gefunden, noch weiß ich sofort, welche makeVarianten die ::=Zuweisung unterstützen, obwohl GNU make dies heute mit der gleichen Bedeutung tut :=, dh die Zuweisung jetzt mit Erweiterung.

Beachten Sie, dass VAR := $(shell command args...)dies auch VAR != command args...in mehreren makeVarianten geschrieben werden kann, einschließlich aller modernen GNU- und BSD-Varianten, soweit ich weiß. Diese anderen Varianten haben keine $(shell)Verwendung, daher VAR != command args...ist die Verwendung sowohl kürzer als auch in mehr Varianten überlegen .

torek
quelle
Vielen Dank. Ich möchte einen ausgeklügelten Befehl ( zum Beispiel lsmit sedund cut) verwenden und dann die Ergebnisse in rsync und anderen Befehlen verwenden. Muss ich den langen Befehl immer wieder wiederholen? Kann ich die Ergebnisse nicht in einer internen Make-Variablen speichern?
Adam Matan
1
Gnu make hat vielleicht eine Möglichkeit, das zu tun, aber ich habe es nie benutzt, und all die schrecklich komplizierten Makefiles, die wir verwenden, verwenden nur Shell-Variablen und riesige einzeilige Shell-Befehle, die mit "; \" am Ende jeder Zeile als erstellt wurden erforderlich. (kann nicht die Code-Codierung bekommen, um mit der Backslash-Sequenz hier zu arbeiten, hmm)
torek
1
Vielleicht so etwas wie: FILE = $(shell ls *.c | sed -e "s^fun^bun^g")
William Morris
2
@ William: makekann das ohne die Shell tun : FILE = $(subst fun,bun,$(wildcard *.c)).
Idelic
1
Ich möchte darauf hinweisen, dass Sie in diesem Fall, obwohl dies nicht sehr wichtig zu sein scheint, die Ausgabe von ls nicht automatisch analysieren sollten. ls soll Informationen für Menschen zeigen, nicht in Skripten verkettet werden. Weitere Informationen hier: mywiki.wooledge.org/ParsingLs Für den Fall, dass 'make' Ihnen keine geeignete Platzhaltererweiterung bietet, ist "find" wahrscheinlich besser als "ls".
Raúl Salinas-Monteagudo
53

Zusätzlich zu Toreks Antwort: Eine Sache, die auffällt, ist, dass Sie eine träge ausgewertete Makrozuweisung verwenden.

Wenn Sie auf GNU Make sind, verwenden Sie die :=Zuweisung anstelle von =. Diese Zuordnung bewirkt, dass die rechte Seite sofort erweitert und in der linken Variablen gespeichert wird.

FILES := $(shell ...)  # expand now; FILES is now the result of $(shell ...)

FILES = $(shell ...)   # expand later: FILES holds the syntax $(shell ...)

Wenn Sie die =Zuweisung verwenden, bedeutet dies, dass jedes einzelne Vorkommen von $(FILES)die $(shell ...)Syntax erweitert und somit den Shell-Befehl aufruft. Dies verlangsamt Ihren Make-Job oder hat sogar einige überraschende Konsequenzen.

Kaz
quelle
1
Wie können wir nun, da wir die Liste haben, über jedes Element in der Liste iterieren und einen Befehl darauf ausführen? Wie bauen oder testen?
anon58192932
3
@ anon58192932 Diese spezifische Iteration zum Ausführen eines Befehls wird normalerweise in einem Shell-Syntaxfragment in einem Build-Rezept ausgeführt, sodass sie in der Shell und nicht in make: auftritt for x in $(FILES); do command $$x; done. Beachten Sie die Verdoppelung, $$die eine einzelne $an die Shell weitergibt. Auch Schalenfragmente sind Einzeiler; Um mehrzeiligen Shell-Code zu schreiben, verwenden Sie die Backslash-Fortsetzung, die von makeselbst verarbeitet und in eine Zeile gefaltet wird. Dies bedeutet, dass Semikolons mit Shell-Befehlstrennung obligatorisch sind.
Kaz