Comandi bash multilinea nel makefile


120

Ho un modo molto comodo per compilare il mio progetto tramite poche righe di comandi bash. Ma ora ho bisogno di compilarlo tramite makefile. Considerando che ogni comando viene eseguito nella propria shell, la mia domanda è qual è il modo migliore per eseguire il comando bash multilinea, dipendenti l'uno dall'altro, nel makefile? Ad esempio, in questo modo:

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

Inoltre, qualcuno può spiegare perché anche il comando a riga singola bash -c 'a=3; echo $a > file'funziona correttamente nel terminale, ma crea un file vuoto nel caso del makefile?


4
La seconda parte dovrebbe essere una domanda separata. L'aggiunta di un'altra domanda crea solo rumore.
l0b0

Risposte:


136

È possibile utilizzare la barra rovesciata per la continuazione della riga. Tuttavia si noti che la shell riceve l'intero comando concatenato in una singola riga, quindi è necessario terminare anche alcune righe con un punto e virgola:

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

Ma se vuoi solo prendere l'intero elenco restituito finddall'invocazione e passarlo a gcc, in realtà non hai necessariamente bisogno di un comando multilinea:

foo:
    gcc `find`

Oppure, utilizzando un $(command)approccio più convenzionale della shell (notare però l' $escape):

foo:
    gcc $$(find)

2
Per multilinea, è ancora necessario sfuggire ai segni del dollaro, come indicato nei commenti altrove.
tripla

1
Sì, una risposta breve 1. $: = $$ 2. aggiungi multilinea con \ BTW bash i ragazzi di solito raccomandano di sostituire la chiamata `find` con $$ (find)
Yauhen Yakimovich

89

Come indicato nella domanda, ogni sottocomando viene eseguito nella propria shell . Questo rende la scrittura di script di shell non banali un po 'complicata, ma è possibile! La soluzione è consolidare lo script in quello che make considererà un singolo sottocomando (una singola riga).

Suggerimenti per scrivere script di shell all'interno di makefile:

  1. Evita l'uso di script $sostituendo con$$
  2. Converti lo script in modo che funzioni come una singola riga inserendo ; tra i comandi
  3. Se vuoi scrivere lo script su più righe, esci dalla fine della riga con \
  4. Facoltativamente inizia con set -e per abbinare la disposizione di make per interrompere in caso di errore del sottocomando
  5. Questo è totalmente opzionale, ma puoi mettere lo script tra parentesi con ()o {}per enfatizzare la coesione di una sequenza di più righe - che questa non è una tipica sequenza di comandi di makefile

Ecco un esempio ispirato all'OP:

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
Potresti anche voler SHELL := /bin/bashabilitare nel tuo makefile funzionalità specifiche di BASH come la sostituzione del processo .
Brent Bradburn

8
Punto sottile: lo spazio dopo {è fondamentale per impedire l'interpretazione di {setun comando sconosciuto.
Brent Bradburn

3
Sottile punto n. 2: nel makefile probabilmente non ha importanza, ma la distinzione tra wrapping con {}e ()fa una grande differenza se a volte si desidera copiare lo script ed eseguirlo direttamente da un prompt della shell. Puoi devastare la tua istanza di shell dichiarando le variabili, e in particolare modificando lo stato con set, all'interno di {}. ()impedisce allo script di modificare il tuo ambiente, cosa probabilmente preferita. Esempio ( questo sarà terminare la sessione di shell ): { set -e ; } ; false.
Brent Bradburn

È possibile includere commenti utilizzando il modulo seguente:; command ; ## my comment \` (the comment is between `e` \ `). Questo sembra funzionare bene tranne che se esegui il comando manualmente (tramite copia e incolla), la cronologia dei comandi includerà il commento in un modo che interrompe il comando (se provi a riutilizzarlo). [Nota: l'evidenziazione della sintassi è interrotta per questo commento a causa dell'uso del backslash all'interno del backtick.]
Brent Bradburn

Se vuoi spezzare una stringa in bash / perl / script all'interno di makefile, chiudi le virgolette della stringa, la barra rovesciata, la nuova riga, (nessun rientro) apri le virgolette della stringa. Esempio; perl -e QUOTE print 1; QUOTE BACKSLASH NEWLINE QUOTE stampa 2 QUOTE
mosh

2

Cosa c'è di sbagliato nel richiamare semplicemente i comandi?

foo:
       echo line1
       echo line2
       ....

E per la tua seconda domanda, devi uscire $da usando $$invece, ie bash -c '... echo $$a ...'.

EDIT: il tuo esempio potrebbe essere riscritto in uno script a riga singola come questo:

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

5
Perché "ogni comando viene eseguito nella propria shell", mentre devo usare il risultato di comando1 in comando2. Come nell'esempio.
Jofsey

Se è necessario propagare le informazioni tra le invocazioni della shell, è necessario utilizzare una qualche forma di memoria esterna, come un file temporaneo. Ma puoi sempre riscrivere il codice per eseguire più comandi nella stessa shell.
JesperE

+1, soprattutto per la versione semplificata. E non è la stessa cosa che fare semplicemente `` gcc `find```?
Eldar Abusalimov

1
Sì. Ma ovviamente potresti fare cose più complesse del semplice "eco".
JesperE

2

Naturalmente, il modo corretto per scrivere un Makefile è documentare effettivamente quali target dipendono da quali sorgenti. Nel caso banale, la soluzione proposta foodipenderà da se stessa, ma ovviamente makeè abbastanza intelligente da eliminare una dipendenza circolare. Ma se aggiungi un file temporaneo alla tua directory, "magicamente" diventerà parte della catena delle dipendenze. Meglio creare una volta per tutte un elenco esplicito di dipendenze, magari tramite uno script.

GNU make sa come eseguire gccper produrre un eseguibile da un insieme di file .ce .h, quindi forse tutto ciò di cui hai veramente bisogno è pari a

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.