Cómo usar comandos de shell en Makefile

99

Estoy tratando de usar el resultado de lsen otros comandos (por ejemplo, echo, rsync):

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

Pero obtengo:

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

He intentado usar echo $$FILES, echo ${FILES}y echo $(FILES), sin suerte.

Adam Matan
fuente

Respuestas:

149

Con:

FILES = $(shell ls)

sangrado debajo allasí, es un comando de compilación. Entonces esto se expande $(shell ls), luego intenta ejecutar el comando FILES ....

Si FILESse supone que es una makevariable, estas variables deben asignarse fuera de la parte de la receta, por ejemplo:

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

Por supuesto, eso significa que FILESse configurará como "salida desde ls" antes de ejecutar cualquiera de los comandos que crean los archivos .tgz. (Aunque, como señala Kaz, la variable se vuelve a expandir cada vez, por lo que eventualmente incluirá los archivos .tgz; algunas variantes de make tienen FILES := ...que evitar esto, por eficiencia y / o corrección. 1 )

Si FILESse supone que es una variable de shell, puede configurarlo, pero debe hacerlo en shell-ese, sin espacios y entre comillas:

all:
        FILES="$(shell ls)"

Sin embargo, cada línea se ejecuta en un shell separado, por lo que esta variable no sobrevivirá a la siguiente línea, por lo que debe usarla inmediatamente:

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

Todo esto es un poco tonto ya que el shell se expandirá *(y otras expresiones de shell glob) para usted en primer lugar, por lo que puede simplemente:

        echo *

como su comando de shell.

Finalmente, como regla general (no realmente aplicable a este ejemplo): como se observa en esperanto en los comentarios, usar la salida de lsno es completamente confiable (algunos detalles dependen de los nombres de los archivos y, a veces, incluso de la versión de ls; algunas versiones de lsintento de desinfectar la salida en algunos casos). Por lo tanto, como nota l0b0 e idelic , si estás usando GNU make puedes usar $(wildcard)y $(subst ...)para lograr todo dentro de makesí mismo (evitando cualquier problema de "caracteres extraños en el nombre del archivo"). (En los shscripts, incluida la parte de recetas de los archivos MAKE, se utiliza otro método find ... -print0 | xargs -0para evitar tropezar con espacios en blanco, nuevas líneas, caracteres de control, etc.).


1 La documentación de GNU Make señala además que POSIX realiza una ::=asignación adicional en 2012 . No he encontrado un enlace de referencia rápida a un documento POSIX para esto, ni sé de antemano qué makevariantes admiten la ::=asignación, aunque GNU lo hace hoy, con el mismo significado que :=, es decir, hacer la asignación ahora mismo con expansión.

Tenga en cuenta que VAR := $(shell command args...)también se puede escribir VAR != command args...en varias makevariantes, incluidas todas las variantes modernas de GNU y BSD que yo sepa. Estas otras variantes no tienen, $(shell)por lo que su uso VAR != command args...es superior tanto por ser más cortas como por trabajar en más variantes.

torek
fuente
Gracias. Quiero usar un comando elaborado ( lscon sedy cortar, por ejemplo) y luego usar los resultados en rsync y otros comandos. ¿Tengo que repetir el comando largo una y otra vez? ¿No puedo almacenar los resultados en una variable Make interna?
Adam Matan
1
Gnu make podría tener una forma de hacer eso, pero nunca lo he usado, y todos los archivos MAKE horriblemente complicados que usamos solo usan variables de shell y comandos de shell gigantes de una sola línea construidos con "; \" al final de cada línea como necesario. (no puedo hacer que la codificación del código funcione con la secuencia de barra invertida aquí, hmm)
torek
1
Quizás algo como: FILE = $(shell ls *.c | sed -e "s^fun^bun^g")
William Morris
2
@William: makepuede hacer que sin utilizar el shell: FILE = $(subst fun,bun,$(wildcard *.c)).
Idelic
1
Me gustaría señalar que aunque en este caso no parece ser muy importante, no debería analizar automáticamente la salida de ls. El objetivo de ls es mostrar información a los seres humanos, no para estar encadenado en guiones. Más información aquí: mywiki.wooledge.org/ParsingLs Probablemente, en caso de que 'make' no le ofrezca la expansión comodín adecuada, "find" es mejor que "ls".
Raúl Salinas-Monteagudo
53

Además, además de la respuesta de torek: una cosa que se destaca es que está utilizando una asignación de macro evaluada de forma perezosa.

Si está en GNU Make, use la :=asignación en lugar de =. Esta asignación hace que el lado derecho se expanda inmediatamente y se almacene en la variable del lado izquierdo.

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

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

Si usa la =asignación, significa que cada aparición de $(FILES)expandirá la $(shell ...)sintaxis y, por lo tanto, invocará el comando de shell. Esto hará que su trabajo de creación se ejecute más lento o incluso tendrá algunas consecuencias sorprendentes.

Kaz
fuente
1
Ahora que tenemos la lista, ¿cómo iteramos sobre cada elemento de la lista y ejecutamos un comando en él? ¿Como construir o probar?
anon58192932
3
@ anon58192932 Eso específica iteración, para ejecutar un comando, se hace generalmente en un fragmento de la sintaxis del shell en una receta de construcción, por lo que se produce en la cáscara, en lugar de en el maquillaje: for x in $(FILES); do command $$x; done. Tenga en cuenta el doble $$que pasa un sencillo $al caparazón. Además, los fragmentos de concha son frases ingeniosas; para escribir código de shell de varias líneas, utiliza la continuación de la barra invertida que se procesa por makesí misma y se dobla en una línea. Lo que significa que los puntos y comas de separación de comandos de shell son obligatorios.
Kaz