Mehrzeilige Bash-Befehle im Makefile


120

Ich habe eine sehr komfortable Möglichkeit, mein Projekt über einige Zeilen mit Bash-Befehlen zu kompilieren. Aber jetzt muss ich es über Makefile kompilieren. In Anbetracht der Tatsache, dass jeder Befehl in einer eigenen Shell ausgeführt wird, ist meine Frage, wie man einen mehrzeiligen Bash-Befehl, der voneinander abhängig ist, am besten in einem Makefile ausführt. Zum Beispiel so:

for i in `find`
do
    all="$all $i"
done
gcc $all

Kann jemand erklären, warum selbst einzeilige Befehle bash -c 'a=3; echo $a > file'im Terminal korrekt funktionieren, aber im Makefile-Fall eine leere Datei erstellen?


4
Der zweite Teil sollte eine separate Frage sein. Das Hinzufügen einer weiteren Frage erzeugt nur Rauschen.
10b0

Antworten:


136

Sie können einen Backslash für die Zeilenfortsetzung verwenden. Beachten Sie jedoch, dass die Shell den gesamten Befehl in einer einzigen Zeile verkettet empfängt. Sie müssen daher auch einige Zeilen mit einem Semikolon abschließen:

foo:
    for i in `find`;     \
    do                   \
        all="$$all $$i"; \
    done;                \
    gcc $$all

Wenn Sie jedoch nur die gesamte vom findAufruf zurückgegebene Liste übernehmen und an übergeben möchten, gccbenötigen Sie nicht unbedingt einen mehrzeiligen Befehl:

foo:
    gcc `find`

Oder verwenden Sie einen eher konventionellen $(command)Ansatz (beachten Sie jedoch die $Flucht):

foo:
    gcc $$(find)

2
Bei Multiline müssen Sie sich immer noch den Dollarzeichen entziehen, wie in den Kommentaren an anderer Stelle angegeben.
Tripleee

1
Ja, eine kurze Antwort 1 $: = $$ 2. Append mehrzeilige mit \ BTW bash Jungs in der Regel empfehlen `find` Aufruf zu ersetzen $$ (find)
Yauhen Yakimovich

89

Wie in der Frage angegeben, wird jeder Unterbefehl in einer eigenen Shell ausgeführt . Dies macht das Schreiben von nicht trivialen Shell-Skripten etwas chaotisch - aber es ist möglich! Die Lösung besteht darin, Ihr Skript so zu konsolidieren, dass make einen einzelnen Unterbefehl (eine einzelne Zeile) berücksichtigt.

Tipps zum Schreiben von Shell-Skripten in Makefiles:

  1. Vermeiden Sie die Verwendung des Skripts $durch Ersetzen durch$$
  2. Konvertieren Sie das Skript so, dass es als einzelne Zeile funktioniert, indem Sie ;zwischen Befehle einfügen
  3. Wenn Sie das Skript in mehreren Zeilen schreiben möchten, schließen Sie das Zeilenende mit \
  4. Beginnen Sie optional mit set -eder Übereinstimmung von make, um bei einem Unterbefehlsfehler abzubrechen
  5. Dies ist völlig optional, aber Sie können das Skript mit ()oder {}um die Kohäsivität einer mehrzeiligen Sequenz zu klammern - dies ist keine typische Makefile-Befehlssequenz

Hier ist ein vom OP inspiriertes Beispiel:

mytarget:
    { \
    set -e ;\
    msg="header:" ;\
    for i in $$(seq 1 3) ; do msg="$$msg pre_$${i}_post" ; done ;\
    msg="$$msg :footer" ;\
    echo msg=$$msg ;\
    }

4
Möglicherweise möchten Sie SHELL := /bin/bashin Ihrem Makefile auch BASH-spezifische Funktionen wie die Prozessersetzung aktivieren .
Brent Bradburn

8
Subtiler Punkt: Das Leerzeichen danach {ist entscheidend, um die Interpretation {setals unbekannter Befehl zu verhindern .
Brent Bradburn

3
Subtiler Punkt 2: Im Makefile spielt es wahrscheinlich keine Rolle, aber die Unterscheidung zwischen Wrapping mit {}und ()macht einen großen Unterschied, wenn Sie das Skript manchmal kopieren und direkt über eine Shell-Eingabeaufforderung ausführen möchten. Sie können Ihre Shell-Instanz verwüsten, indem Sie Variablen deklarieren und insbesondere den Status setinnerhalb von ändern {}. ()verhindert, dass das Skript Ihre Umgebung ändert, was wahrscheinlich bevorzugt wird. Beispiel ( dies beendet Ihre Shell-Sitzung ) : { set -e ; } ; false.
Brent Bradburn

Sie können Kommentare mithilfe des folgenden Formulars command ; ## my comment \` (the comment is between einfügen : ; `und` \ `). Dies scheint gut zu funktionieren, außer dass, wenn Sie den Befehl manuell ausführen (durch Kopieren und Einfügen), der Befehlsverlauf den Kommentar so enthält, dass der Befehl unterbrochen wird (wenn Sie versuchen, ihn wiederzuverwenden). [Hinweis: Die Syntaxhervorhebung ist für diesen Kommentar aufgrund der Verwendung von Backslash im Backtick unterbrochen.]
Brent Bradburn

Wenn Sie eine Zeichenfolge in bash / perl / script in makefile unterbrechen möchten, schließen Sie das Zeichenfolgenzitat, den Backslash, den Zeilenumbruch und das offene Zeichenfolgenzitat (ohne Einzug). Beispiel; perl -e QUOTE print 1; QUOTE BACKSLASH NEWLINE QUOTE Druck 2 QUOTE
Mosh

2

Was ist falsch daran, nur die Befehle aufzurufen?

foo:
       echo line1
       echo line2
       ....

Und für Ihre zweite Frage müssen Sie dem entkommen, $indem Sie $$stattdessen verwenden, dh bash -c '... echo $$a ...'.

BEARBEITEN: Ihr Beispiel könnte wie folgt in ein einzeiliges Skript umgeschrieben werden:

gcc $(for i in `find`; do echo $i; done)

5
Ursache "Jeder Befehl wird in einer eigenen Shell ausgeführt", während ich das Ergebnis von Befehl1 in Befehl2 verwenden muss. Wie im Beispiel.
Jofsey

Wenn Sie Informationen zwischen Shell-Aufrufen weitergeben müssen, müssen Sie einen externen Speicher verwenden, z. B. eine temporäre Datei. Sie können Ihren Code jedoch jederzeit neu schreiben, um mehrere Befehle in derselben Shell auszuführen.
JesperE

+1, insbesondere für die vereinfachte Version. Und ist es nicht dasselbe wie nur "gcc" find ""?
Eldar Abusalimov

1
Ja, so ist es. Aber Sie könnten natürlich komplexere Dinge tun als nur "Echo".
JesperE

2

Der richtige Weg, ein Makefile zu schreiben, besteht natürlich darin, tatsächlich zu dokumentieren, welche Ziele von welchen Quellen abhängen. Im trivialen Fall wird die vorgeschlagene Lösung von sich selbst fooabhängen, ist aber natürlich makeklug genug, um eine zirkuläre Abhängigkeit aufzuheben. Wenn Sie Ihrem Verzeichnis jedoch eine temporäre Datei hinzufügen, wird diese "auf magische Weise" Teil der Abhängigkeitskette. Es ist besser, ein für alle Mal eine explizite Liste von Abhängigkeiten zu erstellen, möglicherweise über ein Skript.

GNU make weiß, wie man gcceine ausführbare Datei aus einer Reihe von .cund .hDateien erstellt, also beträgt alles, was Sie wirklich brauchen, vielleicht

foo: $(wildcard *.h) $(wildcard *.c)

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.