Come usare i comandi della shell in Makefile


99

Sto cercando di utilizzare il risultato di lsin altri comandi (ad esempio echo, rsync):

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

Ma ottengo:

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

Ho provato con echo $$FILES, echo ${FILES}e echo $(FILES), senza fortuna.

Risposte:


149

Con:

FILES = $(shell ls)

rientrato sotto in allquesto modo, è un comando di compilazione. Quindi questo si espande $(shell ls), quindi prova a eseguire il comando FILES ....

Se FILESsi suppone che sia una makevariabile, queste variabili devono essere assegnate al di fuori della porzione della ricetta, ad esempio:

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

Ovviamente, ciò significa che FILESverrà impostato su "output da ls" prima di eseguire qualsiasi comando che crea i file .tgz. (Anche se, come nota Kaz, la variabile viene nuovamente espansa ogni volta, quindi alla fine includerà i file .tgz; alcune varianti devono FILES := ...evitarlo, per efficienza e / o correttezza. 1 )

Se FILESsi suppone che sia una variabile di shell, puoi impostarla ma devi farlo in shell-ese, senza spazi e tra virgolette:

all:
        FILES="$(shell ls)"

Tuttavia, ogni riga viene eseguita da una shell separata, quindi questa variabile non sopravviverà alla riga successiva, quindi è necessario utilizzarla immediatamente:

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

È tutto un po 'sciocco poiché la shell si espanderà *(e altre espressioni glob di shell) per te in primo luogo, quindi puoi semplicemente:

        echo *

come comando della shell.

Infine, come regola generale (non realmente applicabile a questo esempio): come fa notare l' esperanto nei commenti, l'utilizzo dell'output di lsnon è completamente affidabile (alcuni dettagli dipendono dai nomi dei file e talvolta anche dalla versione di ls; alcune versioni lstentano di disinfettare l'output in alcuni casi). Quindi, come l0b0 e nota idelica , se stai usando GNU make puoi usare $(wildcard)e $(subst ...)per realizzare tutto al makesuo interno (evitando qualsiasi problema di "caratteri strani nel nome del file"). (Negli shscript, inclusa la porzione della ricetta dei makefile, un altro metodo è quello find ... -print0 | xargs -0di evitare di inciampare in spazi vuoti, nuove righe, caratteri di controllo e così via.)


1 La documentazione di GNU Make rileva inoltre che POSIX ha aggiunto ::=assegnazioni nel 2012 . Non ho trovato un collegamento di riferimento rapido a un documento POSIX per questo, né so da vicino quali makevarianti supportano l' ::=assegnazione, anche se GNU make lo fa oggi, con lo stesso significato di :=, cioè, fare l'assegnazione in questo momento con espansione.

Nota che VAR := $(shell command args...)può anche essere scritto VAR != command args...in diverse makevarianti, incluse tutte le varianti GNU e BSD moderne per quanto ne so. Queste altre varianti non hanno $(shell)quindi l'utilizzo VAR != command args...è superiore sia per essere più brevi che per lavorare in più varianti.


Grazie. Voglio usare un comando elaborato ( lscon sede cut, per esempio) e poi usare i risultati in rsync e altri comandi. Devo ripetere il lungo comando più e più volte? Non posso memorizzare i risultati in una variabile Make interna?
Adam Matan

1
Gnu make potrebbe avere un modo per farlo, ma non l'ho mai usato, e tutti i makefile orribilmente complicati che usiamo usano solo variabili di shell e giganteschi comandi di shell one-liner costruiti con "; \" alla fine di ogni riga come necessario. (non è possibile far funzionare la codifica del codice con la sequenza del backslash qui, hmm)
torek

1
Forse qualcosa del tipo: FILE = $(shell ls *.c | sed -e "s^fun^bun^g")
William Morris

2
@William: makepuò farlo senza usare la shell: FILE = $(subst fun,bun,$(wildcard *.c)).
Idelic

1
Vorrei sottolineare che sebbene in questo caso non sembri molto importante, non dovresti analizzare automaticamente l'output di ls. Ha lo scopo di mostrare informazioni agli esseri umani, non di essere incatenato in copioni. Maggiori informazioni qui: mywiki.wooledge.org/ParsingLs Probabilmente, nel caso in cui 'make' non ti offra un'espansione con caratteri jolly appropriata, "find" è meglio di "ls".
Raúl Salinas-Monteagudo

53

Inoltre, oltre alla risposta di torek: una cosa che spicca è che stai usando un'assegnazione di macro pigramente valutata.

Se sei su GNU Make, usa l' :=assegnazione invece di =. Questa assegnazione fa sì che il lato destro venga espanso immediatamente e memorizzato nella variabile di sinistra.

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

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

Se usi l' =assegnazione, significa che ogni singola occorrenza di $(FILES)amplierà la $(shell ...)sintassi e quindi richiamerà il comando della shell. Questo renderà il tuo lavoro più lento o addirittura avrà conseguenze sorprendenti.


1
Ora che abbiamo l'elenco, come iteriamo su ogni elemento nell'elenco ed eseguiamo un comando su di esso? Come costruire o testare?
anon58192932

3
@ anon58192932 Tale specifica iterazione, per eseguire un comando, di solito è fatto in un frammento sintassi shell in una ricetta generazione, così che si verifica nel guscio, piuttosto che nel make: for x in $(FILES); do command $$x; done. Nota il raddoppiato $$che passa un singolo $alla shell. Inoltre, i frammenti di shell sono one-liners; per scrivere codice shell su più righe, si utilizza la continuazione del backslash che viene elaborato da makesolo e piegato in una riga. Il che significa che i punti e virgola che separano il comando di shell sono obbligatori.
Kaz
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.