Commandes bash multilignes dans makefile

121

J'ai un moyen très confortable de compiler mon projet via quelques lignes de commandes bash. Mais maintenant, je dois le compiler via makefile. Étant donné que chaque commande est exécutée dans son propre shell, ma question est de savoir quel est le meilleur moyen d'exécuter une commande bash multiligne, dépendante les unes des autres, dans makefile? Par exemple, comme ceci:

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

Aussi, quelqu'un peut-il expliquer pourquoi même la commande sur une seule ligne bash -c 'a=3; echo $a > file'fonctionne correctement dans le terminal, mais crée un fichier vide dans le cas du makefile?

Jofsey
la source
4
La deuxième partie devrait être une question distincte. Ajouter une autre question ne fait que créer du bruit.
l0b0

Réponses:

137

Vous pouvez utiliser une barre oblique inverse pour la continuation de ligne. Cependant, notez que le shell reçoit toute la commande concaténée en une seule ligne, vous devez donc également terminer certaines lignes par un point-virgule:

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

Mais si vous voulez juste prendre toute la liste retournée par l' findinvocation et la transmettre à gcc, vous n'avez pas nécessairement besoin d'une commande multiligne:

foo:
    gcc `find`

Ou, en utilisant une $(command)approche plus conventionnelle du shell (remarquez cependant l' $échappement):

foo:
    gcc $$(find)
Eldar Abusalimov
la source
2
Pour la multiligne, vous devez toujours échapper aux signes dollar, comme indiqué dans les commentaires ailleurs.
tripleee
1
Oui, une réponse courte 1. $: = $$ 2. ajouter multiligne avec \ BTW bash les gars recommandent généralement de remplacer l'appel `find` par $$ (find)
Yauhen Yakimovich
89

Comme indiqué dans la question, chaque sous-commande est exécutée dans son propre shell . Cela rend l'écriture de scripts shell non triviaux un peu compliquée - mais c'est possible! La solution est de consolider votre script dans ce que make considérera comme une seule sous-commande (une seule ligne).

Conseils pour écrire des scripts shell dans des makefiles:

  1. Échapper à l'utilisation du script de $en le remplaçant par$$
  2. Convertissez le script pour qu'il fonctionne sur une seule ligne en l'insérant ;entre les commandes
  3. Si vous souhaitez écrire le script sur plusieurs lignes, échappez en fin de ligne avec \
  4. Commencez éventuellement par set -epour correspondre à la disposition de make pour abandonner en cas d'échec de la sous-commande
  5. C'est totalement facultatif, mais vous pouvez mettre le script entre parenthèses avec ()ou {}pour souligner la cohésion d'une séquence de plusieurs lignes - que ce n'est pas une séquence de commandes makefile typique

Voici un exemple inspiré de l'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 ;\
    }
Brent Bradburn
la source
4
Vous pouvez également souhaiter SHELL := /bin/bashdans votre makefile pour activer les fonctionnalités spécifiques à BASH telles que la substitution de processus .
Brent Bradburn
8
Point subtil: L'espace après {est crucial pour empêcher l'interprétation de {setcomme une commande inconnue.
Brent Bradburn
3
Point subtil n ° 2: Dans le makefile, cela n'a probablement pas d'importance, mais la distinction entre envelopper avec {}et ()fait une grande différence si vous voulez parfois copier le script et l'exécuter directement à partir d'une invite du shell. Vous pouvez faire des ravages sur votre instance de shell en déclarant des variables, et en particulier en modifiant l'état avec set, à l'intérieur de {}. ()empêche le script de modifier votre environnement, ce qui est probablement préférable. Exemple ( cela va finir votre session shell ): { set -e ; } ; false.
Brent Bradburn
Vous pouvez inclure des commentaires en utilisant le formulaire suivant:; command ; ## my comment \` (the comment is between `et` \ `). Cela semble fonctionner correctement sauf que si vous exécutez la commande manuellement (par copier-coller), l'historique des commandes inclura le commentaire d'une manière qui rompt la commande (si vous essayez de la réutiliser). [Remarque: la coloration syntaxique est interrompue pour ce commentaire en raison de l'utilisation de la barre oblique inverse dans le backtick.]
Brent Bradburn
Si vous voulez casser une chaîne dans bash / perl / script dans makefile, fermez la chaîne quote, backslash, newline, (sans indentation) ouvrez la chaîne quote. Exemple; perl -e QUOTE print 1; CITATION BACKSLASH NEWLINE CITATION print 2 CITATION
mosh
2

Quel est le problème avec le simple appel des commandes?

foo:
       echo line1
       echo line2
       ....

Et pour votre deuxième question, vous devez échapper au $en utilisant à la $$place, ie bash -c '... echo $$a ...'.

EDIT: Votre exemple pourrait être réécrit en un script sur une seule ligne comme ceci:

gcc $(for i in `find`; do echo $i; done)
JesperE
la source
5
Parce que "chaque commande est exécutée dans son propre shell", alors que je dois utiliser le résultat de command1 dans command2. Comme dans l'exemple.
Jofsey
Si vous avez besoin de propager des informations entre les appels du shell, vous devez utiliser une forme de stockage externe, comme un fichier temporaire. Mais vous pouvez toujours réécrire votre code pour exécuter plusieurs commandes dans le même shell.
JesperE
+1, en particulier pour la version simplifiée. Et n'est-ce pas la même chose que de faire simplement `` gcc `find ''?
Eldar Abusalimov
1
Oui, ça l'est. Mais vous pouvez bien sûr faire des choses plus complexes qu'un simple «écho».
JesperE
2

Bien sûr, la bonne façon d'écrire un Makefile est de documenter quelles cibles dépendent de quelles sources. Dans le cas trivial, la solution proposée fera foodépendre d'elle-même, mais bien sûr, elle makeest suffisamment intelligente pour supprimer une dépendance circulaire. Mais si vous ajoutez un fichier temporaire à votre répertoire, il fera "comme par magie" partie de la chaîne de dépendances. Mieux vaut créer une fois pour toutes une liste explicite de dépendances, peut-être via un script.

GNU make sait comment exécuter gccpour produire un exécutable à partir d'un ensemble de.c.h fichiers et , donc peut-être que tout ce dont vous avez vraiment besoin se résume à

foo: $(wildcard *.h) $(wildcard *.c)
tripleee
la source
1

La directive ONESHELL permet d'écrire plusieurs recettes de ligne à exécuter dans le même appel de shell.

SOURCE_FILES = $(shell find . -name '*.c')

.ONESHELL:
foo: ${SOURCE_FILES}
    for F in $^; do
        gcc -c $$F
    done

Il y a cependant un inconvénient: les préfixes spéciaux («@», «-» et «+») sont interprétés différemment.

https://www.gnu.org/software/make/manual/html_node/One-Shell.html

Jean-Baptiste Poittevin
la source