Comandos bash multilínea en makefile

120

Tengo una forma muy cómoda de compilar mi proyecto a través de unas pocas líneas de comandos bash. Pero ahora necesito compilarlo a través de Makefile. Teniendo en cuenta que cada comando se ejecuta en su propio shell, mi pregunta es ¿cuál es la mejor manera de ejecutar el comando bash de varias líneas, dependientes entre sí, en un archivo MAKE? Por ejemplo, así:

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

Además, ¿alguien puede explicar por qué incluso el comando de una sola línea bash -c 'a=3; echo $a > file'funciona correctamente en la terminal, pero crea un archivo vacío en el caso del archivo MAKE?

Jofsey
fuente
4
La segunda parte debería ser una pregunta separada. Agregar otra pregunta solo crea ruido.
l0b0

Respuestas:

136

Puede utilizar la barra invertida para la continuación de la línea. Sin embargo, tenga en cuenta que el shell recibe todo el comando concatenado en una sola línea, por lo que también debe terminar algunas de las líneas con un punto y coma:

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

Pero si solo desea tomar la lista completa devuelta por la findinvocación y pasarla gcc, en realidad no necesita un comando de varias líneas:

foo:
    gcc `find`

O, utilizando un $(command)enfoque más convencional de shell ( $aunque observe el escape):

foo:
    gcc $$(find)
Eldar Abusalimov
fuente
2
Para multilínea, aún necesita escapar de los signos de dólar, como se indica en los comentarios en otra parte.
tripleee
1
Sí, una respuesta corta 1. $: = $$ 2. agregar multilínea con \ BTW bash, los chicos generalmente recomiendan reemplazar la llamada `find` con $$ (find)
Yauhen Yakimovich
89

Como se indica en la pregunta, cada subcomando se ejecuta en su propio shell . Esto hace que escribir scripts de shell no triviales sea un poco complicado, ¡ pero es posible! La solución es consolidar su script en lo que se considerará un único subcomando (una sola línea).

Consejos para escribir scripts de shell dentro de archivos MAKE:

  1. Escapa del uso de la secuencia de comandos $reemplazando con$$
  2. Convierta el script para que funcione como una sola línea insertando ;entre comandos
  3. Si desea escribir el script en varias líneas, salga del final de línea con \
  4. Opcionalmente, comience con set -epara que coincida con la disposición de make para abortar en caso de falla del subcomando
  5. Esto es totalmente opcional, pero puede poner entre corchetes el script ()o {}para enfatizar la cohesión de una secuencia de líneas múltiples, que esta no es una secuencia de comandos típica de makefile

Aquí hay un ejemplo inspirado en el 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
fuente
4
Es posible que también desee SHELL := /bin/bashen su archivo MAKE habilitar funciones específicas de BASH, como la sustitución de procesos .
Brent Bradburn
8
Punto sutil: el espacio después {es crucial para evitar la interpretación de {setun comando desconocido.
Brent Bradburn
3
Punto sutil n. ° 2: en el archivo MAKE probablemente no importe, pero la distinción entre encapsular con {}y ()hace una gran diferencia si a veces desea copiar el script y ejecutarlo directamente desde un indicador de shell. Puede causar estragos en su instancia de shell declarando variables, y especialmente modificando el estado con set, dentro de {}. ()evita que el script modifique su entorno, lo que probablemente sea el preferido. Ejemplo ( esto va a terminar su sesión de shell ): { set -e ; } ; false.
Brent Bradburn
Puede incluir comentarios utilizando el siguiente formulario command ; ## my comment \` (the comment is between :; `y` \ `). Esto parece funcionar bien, excepto que si ejecuta el comando manualmente (copiando y pegando), el historial de comandos incluirá el comentario de una manera que rompe el comando (si intenta reutilizarlo). [Nota: El resaltado de sintaxis está roto para este comentario debido al uso de la barra invertida dentro de la tilde.]
Brent Bradburn
Si desea romper una cadena en bash / perl / script dentro de makefile, cierre la cadena entre comillas, barra invertida, nueva línea (sin sangría) y abra la cadena entre comillas. Ejemplo; perl -e CITA imprimir 1; COTIZAR BACKSLASH NEWLINE COTIZAR imprimir 2 COTIZAR
mosh
2

¿Qué hay de malo en invocar los comandos?

foo:
       echo line1
       echo line2
       ....

Y para su segunda pregunta, debe escapar $usando en su $$lugar, es decir bash -c '... echo $$a ...'.

EDITAR: Su ejemplo podría reescribirse en un script de una sola línea como este:

gcc $(for i in `find`; do echo $i; done)
JesperE
fuente
5
Porque "cada comando se ejecuta en su propio shell", mientras que necesito usar el resultado de command1 en command2. Como en el ejemplo.
Jofsey
Si necesita propagar información entre invocaciones de shell, debe utilizar algún tipo de almacenamiento externo, como un archivo temporal. Pero siempre puede reescribir su código para ejecutar múltiples comandos en el mismo shell.
JesperE
+1, especialmente para la versión simplificada. ¿Y no es lo mismo que hacer simplemente `` gcc `find```?
Eldar Abusalimov
1
Sí lo es. Pero, por supuesto, podría hacer cosas más complejas que solo 'hacer eco'.
JesperE
2

Por supuesto, la forma correcta de escribir un Makefile es documentar qué destinos dependen de qué fuentes. En el caso trivial, la solución propuesta foodependerá de sí misma, pero, por supuesto, makees lo suficientemente inteligente como para eliminar una dependencia circular. Pero si agrega un archivo temporal a su directorio, "mágicamente" se convertirá en parte de la cadena de dependencias. Es mejor crear una lista explícita de dependencias de una vez por todas, quizás a través de un script.

GNU make sabe cómo ejecutarse gccpara producir un ejecutable a partir de un conjunto de archivos .cy .h, por lo que quizás todo lo que realmente necesite sea

foo: $(wildcard *.h) $(wildcard *.c)
triples
fuente
1

La directiva ONESHELL permite escribir varias recetas de línea para ejecutarlas en la misma invocación de shell.

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

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

Sin embargo, hay un inconveniente: los caracteres de prefijo especiales ('@', '-' y '+') se interpretan de manera diferente.

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

Jean-Baptiste Poittevin
fuente