Comment utiliser les commandes shell dans Makefile

99

J'essaye d'utiliser le résultat de lsdans d'autres commandes (par exemple echo, rsync):

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

Mais j'obtiens:

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

Je l' ai essayé d' utiliser echo $$FILES, echo ${FILES}et echo $(FILES), sans chance.

Adam Matan
la source

Réponses:

149

Avec:

FILES = $(shell ls)

en retrait allcomme ça, c'est une commande de construction. Donc, cela se développe $(shell ls), puis essaie d'exécuter la commande FILES ....

Si FILESest supposé être une makevariable, ces variables doivent être affectées en dehors de la partie recette, par exemple:

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

Bien sûr, cela signifie qu'il FILESsera défini sur "sortie de ls" avant d' exécuter l'une des commandes qui créent les fichiers .tgz. (Bien que, comme le note Kaz, la variable est ré-développée à chaque fois, elle inclura finalement les fichiers .tgz; certaines variantes doivent FILES := ...éviter cela, pour plus d'efficacité et / ou d'exactitude. 1 )

Si FILESest censé être une variable shell, vous pouvez le définir mais vous devez le faire en shell-ese, sans espaces, et entre guillemets:

all:
        FILES="$(shell ls)"

Cependant, chaque ligne est exécutée par un shell séparé, donc cette variable ne survivra pas à la ligne suivante, vous devez donc l'utiliser immédiatement:

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

Tout cela est un peu idiot car le shell se développera *(et d'autres expressions shell glob) pour vous en premier lieu, vous pouvez donc simplement:

        echo *

comme commande shell.

Enfin, en règle générale (pas vraiment applicable à cet exemple): comme le note l' espéranto dans les commentaires, l'utilisation de la sortie de lsn'est pas complètement fiable (certains détails dépendent des noms de fichiers et parfois même de la version de ls; certaines versions de lstentative de nettoyage de la sortie dans certains cas). Ainsi, comme le note l0b0 et idelic , si vous utilisez GNU make, vous pouvez utiliser $(wildcard)et $(subst ...)tout accomplir en makelui-même (en évitant les problèmes de "caractères étranges dans le nom de fichier"). (Dans les shscripts, y compris la partie recette des makefiles, une autre méthode consiste à utiliser find ... -print0 | xargs -0pour éviter de trébucher sur les blancs, les retours à la ligne, les caractères de contrôle, etc.)


1 La documentation GNU Make note en outre que POSIX a ajouté une ::=affectation en 2012 . Je n'ai pas trouvé de lien de référence rapide vers un document POSIX pour cela, et je ne sais pas non plus quelles makevariantes prennent en charge l' ::=affectation, bien que GNU le fasse aujourd'hui, avec le même sens que :=, c'est-à-dire, faire l'affectation maintenant avec l'expansion.

Notez que cela VAR := $(shell command args...)peut également être orthographié VAR != command args...en plusieurs makevariantes, y compris toutes les variantes modernes GNU et BSD pour autant que je sache. Ces autres variantes n'ont pas, $(shell)donc l'utilisation VAR != command args...est supérieure à la fois en étant plus courte et en fonctionnant dans plusieurs variantes.

Torek
la source
Merci. Je veux utiliser une commande élaborée ( lsavec sedet couper, par exemple), puis utiliser les résultats dans rsync et d'autres commandes. Dois-je répéter la longue commande encore et encore? Puis-je stocker les résultats dans une variable Make interne?
Adam Matan
1
Gnu make pourrait avoir un moyen de le faire, mais je ne l'ai jamais utilisé, et tous les makefiles horriblement compliqués que nous utilisons utilisent simplement des variables shell et des commandes shell géantes à une ligne construites avec "; \" à la fin de chaque ligne comme nécessaire. (impossible d'obtenir le codage du code pour fonctionner avec la séquence de barre oblique inverse ici, hmm)
torek
1
Peut-être quelque chose comme: FILE = $(shell ls *.c | sed -e "s^fun^bun^g")
William Morris
2
@William: makepeut le faire sans utiliser le shell: FILE = $(subst fun,bun,$(wildcard *.c)).
Idelic
1
Je tiens à souligner que même si dans ce cas cela ne semble pas très important, vous ne devez pas analyser automatiquement la sortie de ls. ls est destiné à montrer des informations aux êtres humains, et non à être enchaînés dans des scripts. Plus d'infos ici: mywiki.wooledge.org/ParsingLs Probablement, dans le cas où 'make' ne vous offre pas une extension de joker appropriée, "find" est meilleur que "ls".
Raúl Salinas-Monteagudo
53

De plus, en plus de la réponse de torek: une chose qui ressort est que vous utilisez une affectation de macro évaluée paresseusement.

Si vous êtes sur GNU Make, utilisez l' :=affectation au lieu de =. Cette affectation entraîne l'expansion immédiate du côté droit et le stockage dans la variable de gauche.

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

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

Si vous utilisez l' =affectation, cela signifie que chaque occurrence de $(FILES)développera la $(shell ...)syntaxe et invoquera ainsi la commande shell. Cela ralentira votre travail de ralentissement, voire aura des conséquences surprenantes.

Kaz
la source
1
Maintenant que nous avons la liste, comment itérer sur chaque élément de la liste et exécuter une commande dessus? Tels que construire ou tester?
anon58192932
3
@ anon58192932 cette itération spécifique, pour exécuter une commande, se fait habituellement dans un fragment de syntaxe shell dans une recette de construction, de sorte qu'il se produit dans la coquille, plutôt que dans la marque: for x in $(FILES); do command $$x; done. Notez le doublé $$qui passe un simple $à la coque. En outre, les fragments de coquille sont à une seule ligne; pour écrire du code shell multiligne, vous utilisez la continuation de la barre oblique inverse qui est traitée par makeelle-même et pliée en une seule ligne. Ce qui signifie que les points-virgules séparant les commandes shell sont obligatoires.
Kaz du