web-dev-qa-db-fra.com

Commandes bash multilignes dans le makefile

J'ai un moyen très confortable de compiler mon projet via quelques lignes de commandes bash. Mais maintenant, j'ai besoin de le compiler via makefile. Étant donné que chaque commande est exécutée dans son propre shell, ma question est quel est le meilleur moyen d’exécuter une commande multi-ligne bash, dépendant l’une de l’autre, dans le fichier 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 une 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 fichier makefile?

92
Jofsey

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

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

Mais si vous voulez simplement prendre la liste entière renvoyée par l'invocation find et la transmettre à gcc, vous n'avez pas nécessairement besoin d'une commande multiligne:

foo:
    gcc `find`

Ou, en utilisant une approche plus conventionnelle $(command) (notez le $ s'échappant cependant):

foo:
    gcc $$(find)
108
Eldar Abusalimov

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

Conseils pour écrire des scripts Shell dans les makefiles:

  1. Échapper à l'utilisation du script de $ en remplaçant par $$
  2. Convertissez le script pour qu’il fonctionne sur une seule ligne en insérant ; entre les commandes
  3. Si vous voulez écrire le script sur plusieurs lignes, échappez en fin de ligne avec \
  4. Vous pouvez éventuellement commencer par set -e pour faire correspondre la disposition de make pour annuler en cas d'échec de la sous-commande
  5. Ceci est totalement optionnel, mais vous pouvez accoler le script avec () ou {} pour souligner la cohésion d'une séquence de plusieurs lignes - qu'il ne s'agit pas d'une séquence de commande makefile typique

Voici un exemple inspiré par 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 ;\
    }
70
nobar

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 que foo dépende de lui-même, mais il est évident que make est suffisamment intelligent pour supprimer une dépendance circulaire. Mais si vous ajoutez un fichier temporaire à votre répertoire, il deviendra "par magie" une partie de la chaîne de dépendance. Mieux vaut créer une liste explicite de dépendances une fois pour toutes, éventuellement via un script.

GNU make sait comment exécuter gcc pour produire un exécutable à partir d’un ensemble de .c et .h fichiers, alors peut-être que tout ce dont vous avez vraiment besoin revient à

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

Quel est le problème avec juste en invoquant les commandes?

foo:
       echo line1
       echo line2
       ....

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

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

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