Cómo asignar la salida de un comando a una variable Makefile

225

Necesito ejecutar algunas reglas de creación condicionalmente, solo si el Python instalado es mayor que cierta versión (digamos 2.5).

Pensé que podría hacer algo como ejecutar:

python -c 'import sys; print int(sys.version_info >= (2,5))'

y luego usando la salida ('1' si está bien, '0' de lo contrario) en una ifeqdeclaración make.

En un simple script de shell bash es solo:

MY_VAR=`python -c 'import sys; print int(sys.version_info >= (2,5))'`

pero eso no funciona en un Makefile.

¿Alguna sugerencia? Podría usar cualquier otra solución razonable para lograr esto.

fortran
fuente
Extraños ticks alrededor del trabajo de comando para ejecutar otros scripts para mí en un Makefile. Podría ser otra cosa.
Leif Gruenwoldt

Respuestas:

346

Utilice el Make shellincorporado como enMY_VAR=$(shell echo whatever)

me@Zack:~$make
MY_VAR IS whatever

me@Zack:~$ cat Makefile 
MY_VAR := $(shell echo whatever)

all:
    @echo MY_VAR IS $(MY_VAR)
Arkaitz Jiménez
fuente
34
shell no es un comando estándar de creación de builtin. Este es un GNU Make incorporado.
Dereckson
12
stackoverflow.com/a/2373111/12916 agrega una nota importante sobre el escape $.
Jesse Glick
66
Este simple ejemplo funciona. También funciona con la tubería de comandos de shell. Pero es esencial que use $$ para representar $ en el comando de shell
Sergey P., también conocido como azure el
28
Si bien la pregunta es un poco antigua, es mejor hacer MY_VAR: = $ (shell ...), de lo contrario, cada vez que se evalúe MY_VAR, se ejecutará $ (shell ...) nuevamente.
Russ Schultz
Tenía un espacio entre el shell y los paréntesis de apertura, y solo después de eliminar el espacio
apareció el
29

Ajustar la tarea en una evalestá funcionando para mí.

# dependency on .PHONY prevents Make from 
# thinking there's `nothing to be done`
set_opts: .PHONY
  $(eval DOCKER_OPTS = -v $(shell mktemp -d -p /scratch):/output)
Dave
fuente
66
"Nota: el @true aquí evita que Make piense que no hay nada que hacer". Um, para eso .PHONY always make these targetsestá.
underscore_d
¡Gracias! Esto me ayuda a pasar por alto "bash raro" en el makefile
Nam G VU
19

Aquí hay un ejemplo un poco más complicado con tuberías y asignación de variables dentro de la receta:

getpodname:
    # Getting pod name
    @eval $$(minikube docker-env) ;\
    $(eval PODNAME=$(shell sh -c "kubectl get pods | grep profile-posts-api | grep Running" | awk '{print $$1}'))
    echo $(PODNAME)
Francesco Casula
fuente
2
En caso de que sea útil, estoy usando un enfoque un poco similar para obtener el PODNAMEnombre de implementación:$(eval PODNAME=$(shell sh -c "kubectl get pod -l app=sqlproxy -o jsonpath='{.items[0].metadata.name}'"))
Jan Richter
La sintaxis me confundió por un segundo hasta que me di cuenta de que estabas usando tanto la evaluación interna de shell (en la línea docker-env) como la función make eval (en la siguiente línea).
Brian Gordon
17

Estoy escribiendo una respuesta para aumentar la visibilidad de la sintaxis real que resuelve el problema. Desafortunadamente, lo que alguien podría ver como trivial puede convertirse en un dolor de cabeza muy significativo para alguien que busca una respuesta simple a una pregunta razonable.

Ponga lo siguiente en el archivo "Makefile".

MY_VAR := $(shell python -c 'import sys; print int(sys.version_info >= (2,5))')

all:
    @echo MY_VAR IS $(MY_VAR)

El comportamiento que le gustaría ver es el siguiente (suponiendo que haya instalado Python recientemente).

make
MY_VAR IS 1

Si copia y pega el texto anterior en el Makefile, ¿obtendrá esto? Probablemente no. Probablemente obtendrá un error como el que se informa aquí:

makefile: 4: *** falta separador. Detener

Por qué: porque aunque utilicé personalmente una pestaña genuina, Stack Overflow (intentando ser útil) convierte mi pestaña en varios espacios. Usted, ciudadano frustrado de Internet, ahora copie esto, pensando que ahora tiene el mismo texto que yo usé. El comando make, ahora lee los espacios y encuentra que el comando "all" está formateado incorrectamente. Así que copie el texto anterior, péguelo y luego convierta el espacio en blanco antes de "@echo" en una pestaña, y este ejemplo, al final, con suerte, funcionará para usted.

AlanSE
fuente
Depende del editor en el que estés pegando. Acabo de copiar y pegar en un editor de Eclipse Makefile, y obtuve una pestaña principal (según sea necesario).
Technophile
Oh, no pensé en eso. Átomo aquí.
AlanSE
11

Cuidado con recetas como esta

target:
    MY_ID=$(GENERATE_ID);
    echo $MY_ID;

Hace dos cosas mal. La primera línea de la receta se ejecuta en una instancia de shell separada de la segunda línea. La variable se pierde mientras tanto. La segunda cosa incorrecta es que $no se ha escapado.

target:
    MY_ID=$(GENERATE_ID); \
    echo $$MY_ID;

Ambos problemas se han solucionado y la variable es utilizable. La barra invertida combina ambas líneas para ejecutarse en un único shell, por lo tanto, la configuración de la variable y la lectura de las palabras clave variables funcionan.

Me doy cuenta de que la publicación original decía cómo obtener los resultados de un comando de shell en una variable MAKE, y esta respuesta muestra cómo obtenerlo en una variable de shell. Pero otros lectores pueden beneficiarse.

Una mejora final, si el consumidor espera que se establezca una "variable de entorno", entonces debe exportarla.

my_shell_script
    echo $MY_ID

necesitaría esto en el archivo MAKE

target:
    export MY_ID=$(GENERATE_ID); \
    ./my_shell_script;

Espero que ayude a alguien. En general, uno debe evitar hacer un trabajo real fuera de las recetas, porque si alguien usa el archivo MAKE con la opción '--dry-run', solo para VER lo que hará, no tendrá efectos secundarios indeseables. Cada $(shell)llamada se evalúa en tiempo de compilación y se puede realizar accidentalmente un trabajo real. Es mejor dejar el trabajo real, como generar identificadores, en el interior de las recetas cuando sea posible.

Juraj
fuente
9

Con GNU Make, puede usar shelly evalalmacenar, ejecutar y asignar resultados de invocaciones arbitrarias de línea de comandos. La diferencia entre el siguiente ejemplo y los que usan :=es que la :=asignación ocurre una vez (cuando se encuentra) y para todos. Las variables expandidas recursivamente establecidas con =son un poco más "perezosas"; las referencias a otras variables permanecen hasta que se hace referencia a la variable en sí, y la expansión recursiva posterior tiene lugar cada vez que se hace referencia a la variable , lo cual es deseable para hacer "fragmentos consistentes, invocables". Consulte el manual sobre configuración de variables para obtener más información.

# Generate a random number.
# This is not run initially.
GENERATE_ID = $(shell od -vAn -N2 -tu2 < /dev/urandom)

# Generate a random number, and assign it to MY_ID
# This is not run initially.
SET_ID = $(eval MY_ID=$(GENERATE_ID))

# You can use .PHONY to tell make that we aren't building a target output file
.PHONY: mytarget
mytarget:
# This is empty when we begin
    @echo $(MY_ID)
# This recursively expands SET_ID, which calls the shell command and sets MY_ID
    $(SET_ID)
# This will now be a random number
    @echo $(MY_ID)
# Recursively expand SET_ID again, which calls the shell command (again) and sets MY_ID (again)
    $(SET_ID)
# This will now be a different random number
    @echo $(MY_ID)
Mitchell Tracy
fuente
Tienes la primera explicación agradable que he encontrado. Gracias. Aunque una cosa para agregar es que si $(SET_ID)vive dentro de una ifcláusula que es falsa, entonces todavía se llama .
Romano
Todavía se llama porque los stmts if se evalúan en tiempo de compilación de makefile y no en tiempo de ejecución. Para instrucciones específicas de tiempo de ejecución, póngalas en recetas. Si son condicionales, agregue stmts if, escritos en bash / shell, como parte de la receta.
Juraj
0

En el ejemplo a continuación, he almacenado la ruta de la carpeta Makefile LOCAL_PKG_DIRy luego uso la LOCAL_PKG_DIRvariable en los objetivos.

Makefile:

LOCAL_PKG_DIR := $(shell eval pwd)

.PHONY: print
print:
    @echo $(LOCAL_PKG_DIR)

Terminal de salida:

$ make print
/home/amrit/folder
Amritpal Singh
fuente