variables - ¿Es posible crear una variable de cadena multilínea en un archivo Makefile?
(19)
¿Por qué no utiliza el carácter / n en su cadena para definir el final de línea? También agregue la barra invertida adicional para agregarla en múltiples líneas.
ANNOUNCE_BODY=" /n/
Version $(VERSION) of $(PACKAGE_NAME) has been released /n/
/n/
It can be downloaded from $(DOWNLOAD_URL) /n/
/n/
etc, etc"
Quiero crear una variable de archivo MAKE que sea una cadena de varias líneas (por ejemplo, el cuerpo de un anuncio de lanzamiento de correo electrónico). algo como
ANNOUNCE_BODY="
Version $(VERSION) of $(PACKAGE_NAME) has been released
It can be downloaded from $(DOWNLOAD_URL)
etc, etc"
Pero parece que no puedo encontrar una manera de hacer esto. ¿Es posible?
Como alternativa, puede usar el comando printf. Esto es útil en OSX u otras plataformas con menos funciones.
Para simplemente emitir un mensaje multilínea:
all:
@printf ''%s/n'' /
''Version $(VERSION) has been released'' /
'''' /
''You can download from URL $(URL)''
Si intenta pasar la cadena como un arg a otro programa, puede hacerlo así:
all:
/some/command "`printf ''%s/n'' ''Version $(VERSION) has been released'' '''' ''You can download from URL $(URL)''`"
Con GNU Make, la opción .ONESHELL
es tu amiga cuando se trata de fragmentos de shell multilínea. Al juntar pistas de otras respuestas, obtengo:
VERSION = 1.2.3
PACKAGE_NAME = foo-bar
DOWNLOAD_URL = $(PACKAGE_NAME).somewhere.net
define nwln
endef
define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.
It can be downloaded from $(DOWNLOAD_URL).
etc, etc.
endef
.ONESHELL:
# mind the *leading* <tab> character
version:
@printf "$(subst $(nwln),/n,$(ANNOUNCE_BODY))"
Asegúrese de que, al copiar y pegar el ejemplo anterior en su editor, se conserve cualquier carácter <tab>
, de lo contrario, el objetivo de la version
se romperá.
Creo que la respuesta más segura para el uso multiplataforma sería usar un eco por línea:
ANNOUNCE.txt:
rm -f $@
echo "Version $(VERSION) of $(PACKAGE_NAME) has been released" > $@
echo "" >> $@
echo "It can be downloaded from $(DOWNLOAD_URL)" >> $@
echo >> $@
echo etc, etc" >> $@
Esto evita hacer suposiciones sobre la versión de eco disponible.
Deberías usar "define / endef" Make construction:
define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.
It can be downloaded from $(DOWNLOAD_URL).
etc, etc.
endef
Entonces debería pasar el valor de esta variable al comando de shell. Pero, si haces esto usando Hacer sustitución de variable, hará que el comando se divida en múltiples:
ANNOUNCE.txt:
echo $(ANNOUNCE_BODY) > $@ # doesn''t work
Qouting tampoco ayudará.
La mejor forma de pasar el valor es pasarlo a través de la variable de entorno:
ANNOUNCE.txt: export ANNOUNCE_BODY:=$(ANNOUNCE_BODY)
ANNOUNCE.txt:
echo "$${ANNOUNCE_BODY}" > $@
Darse cuenta:
- La variable se exporta para este objetivo en particular, por lo que puede reutilizar ese entorno y no se contaminará demasiado;
- Use la variable de entorno (double qoutes y llaves dentro del nombre de la variable);
- Uso de comillas alrededor de la variable. Sin ellos, las nuevas líneas se perderán y todo el texto aparecerá en una línea.
En el espíritu de .ONESHELL, es posible acercarse bastante en ambientes desafiados de .NETHELL:
define _oneshell_newline_
endef
define oneshell
@eval "$$(printf ''%s/n'' ''$(strip /
$(subst $(_oneshell_newline_),/n, /
$(subst /,//, /
$(subst /,//, /
$(subst '',''"''"'',$(1))))))'' | /
sed -e ''s,//n,/n,g'' -e ''s,///,//,g'' -e ''s,//,/,g'')"
endef
Un ejemplo de uso sería algo como esto:
define TEST
printf ''>/n%s/n'' "Hello
World/n/$$$$/"
endef
all:
$(call oneshell,$(TEST))
Eso muestra la salida (asumiendo pid 27801):
>
Hello
World/n/27801/
Este enfoque permite alguna funcionalidad adicional:
define oneshell
@eval "set -eux ; $$(printf ''%s/n'' ''$(strip /
$(subst $(_oneshell_newline_),/n, /
$(subst /,//, /
$(subst /,//, /
$(subst '',''"''"'',$(1))))))'' | /
sed -e ''s,//n,/n,g'' -e ''s,///,//,g'' -e ''s,//,/,g'')"
endef
Estas opciones de shell van a:
- Imprime cada comando mientras se ejecuta
- Salir en el primer comando fallido
- Trate el uso de variables de shell indefinidas como un error
Otras posibilidades interesantes probablemente se sugerirán a sí mismas.
Esto no proporciona un documento aquí, pero muestra un mensaje de varias líneas de una manera adecuada para las tuberías.
=====
MSG = this is a//n/
multi-line//n/
message
method1:
@$(SHELL) -c "echo ''$(MSG)''" | sed -e ''s/^ //''
=====
También puede usar las macros invocables de Gnu:
=====
MSG = this is a//n/
multi-line//n/
message
method1:
@echo "Method 1:"
@$(SHELL) -c "echo ''$(MSG)''" | sed -e ''s/^ //''
@echo "---"
SHOW = $(SHELL) -c "echo ''$1''" | sed -e ''s/^ //''
method2:
@echo "Method 2:"
@$(call SHOW,$(MSG))
@echo "---"
=====
Aquí está el resultado:
=====
$ make method1 method2
Method 1:
this is a
multi-line
message
---
Method 2:
this is a
multi-line
message
---
$
=====
Funcionó para mí:
ANNOUNCE_BODY="first line//nsecond line"
all:
@echo -e $(ANNOUNCE_BODY)
GNU Makefile puede hacer cosas como las siguientes. Es feo, y no diré que debas hacerlo, pero lo hago en ciertas situaciones.
PROFILE = /
/#!/bin/sh.exe/n/
/#/n/
/# A MinGW equivalent for .bash_profile on Linux. In MinGW/MSYS, the file/n/
/# is actually named .profile, not .bash_profile./n/
/#/n/
/# Get the aliases and functions/n/
/#/n/
if [ -f /$${HOME}/.bashrc ]/n/
then/n/
. /$${HOME}/.bashrc/n/
fi/n/
/n/
export CVS_RSH="ssh"/n
#
.profile:
echo -e "$(PROFILE)" | sed -e ''s/^[ ]//'' >.profile
make .profile
crea un archivo .profile si no existe uno.
Esta solución se usó donde la aplicación solo usará GNU Makefile en un entorno de shell POSIX. El proyecto no es un proyecto de código abierto donde la compatibilidad de la plataforma es un problema.
El objetivo era crear un Makefile que facilitara la configuración y el uso de un tipo particular de espacio de trabajo. El Makefile trae consigo varios recursos simples sin requerir cosas como otro archivo especial, etc. Es, en cierto sentido, un archivo de shell. Un procedimiento puede entonces decir cosas como soltar este Makefile en la carpeta para trabajar. Configure su espacio de trabajo ingrese make workspace
, luego haga blah, ingrese make blah
, etc.
Lo que puede ser complicado es averiguar qué citar citar. Lo anterior hace el trabajo y está cerca de la idea de especificar un documento aquí en el Makefile. Si es una buena idea para el uso general es otro problema.
Me gusta más la respuesta de Alhadis. Pero para mantener el formato columnar, agregue una cosa más.
SYNOPSIS := :: Synopsis: Makefile/
| ::/
| :: Usage:/
| :: make .......... : generates this message/
| :: make synopsis . : generates this message/
| :: make clean .... : eliminate unwanted intermediates and targets/
| :: make all ...... : compile entire system from ground-up/
endef
Productos:
:: Synopsis: Makefile
::
:: Usage:
:: make .......... : generates this message
:: make synopsis . : generates this message
:: make clean .... : eliminate unwanted intermediates and targets
:: make all ...... : compile entire system from ground-up
No está completamente relacionado con el OP, pero espero que esto ayude a alguien en el futuro. (ya que esta pregunta es la que más aparece en las búsquedas de Google).
En mi Makefile, quería pasar el contenido de un archivo a un comando de compilación de docker, después de mucha consternación, decidí:
base64 encode the contents in the Makefile (so that I could have a single line and pass them as a docker build arg...)
base64 decode the contents in the Dockerfile (and write them to a file)
ver el ejemplo a continuación.
nb: en mi caso particular, quería pasar una clave ssh, durante la creación de la imagen, usando el ejemplo de https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/ (usando una estructura acoplable de varias etapas para clonar un repositorio git, luego suelte la clave ssh de la imagen final en la segunda etapa de la compilación)
Makefile
...
MY_VAR_ENCODED=$(shell cat /path/to/my/file | base64)
my-build:
@docker build /
--build-arg MY_VAR_ENCODED="$(MY_VAR_ENCODED)" /
--no-cache /
-t my-docker:build .
...
Dockerfile
...
ARG MY_VAR_ENCODED
RUN mkdir /root/.ssh/ && /
echo "${MY_VAR_ENCODED}" | base64 -d > /path/to/my/file/in/container
...
Otro enfoque para ''sacar la variable multilínea del archivo makefile'' (señalado por Eric Melski como ''la parte difícil'') es planear usar la función subst
para reemplazar las líneas nuevas introducidas con define
en su cadena multilínea. con /n
. Luego usa -e con echo
para interpretarlos. Es posible que necesite establecer .SHELL = bash para obtener un eco que lo haga.
Una ventaja de este enfoque es que también incluye otros caracteres de escape en el texto y los respeta.
Este tipo de sintetiza todos los enfoques mencionados hasta ahora ...
Terminas con:
define newline
endef
define ANNOUNCE_BODY=
As of $(shell date), version $(VERSION) of $(PACKAGE_NAME) has been released.
It can be downloaded from $(DOWNLOAD_URL).
endef
someTarget:
echo -e ''$(subst $(newline),/n,${ANNOUNCE_BODY})''
Tenga en cuenta que las comillas simples en el eco final son cruciales.
Realmente no es una respuesta útil, solo para indicar que ''definir'' no funciona como lo respondió Ax (no encajaba en un comentario):
VERSION=4.3.1
PACKAGE_NAME=foobar
DOWNLOAD_URL=www.foobar.com
define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released
It can be downloaded from $(DOWNLOAD_URL)
etc, etc
endef
all:
@echo $(ANNOUNCE_BODY)
Da un error que el comando ''It'' no se puede encontrar, por lo que intenta interpretar la segunda línea de ANNOUNCE BODY como un comando.
Sí, puede usar la palabra clave define para declarar una variable multilínea, como esta:
define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.
It can be downloaded from $(DOWNLOAD_URL).
etc, etc.
endef
La parte más complicada es sacar su variable multilínea del archivo MAKE. Si solo hace lo obvio de usar "echo $ (ANNOUNCE_BODY)", verá el resultado que otros publicaron aquí: el intérprete intenta manejar la segunda línea y las siguientes líneas de la variable como comandos.
Sin embargo, puede exportar el valor de la variable como está al shell como una variable de entorno, y luego hacer referencia al mismo desde el shell como una variable de entorno (NO una variable make). Por ejemplo:
export ANNOUNCE_BODY
all:
@echo "$$ANNOUNCE_BODY"
Tenga en cuenta el uso de $$ANNOUNCE_BODY
, que indica una referencia de variable de entorno de shell, en lugar de $(ANNOUNCE_BODY)
, que sería una referencia de variable de variable habitual. También asegúrese de usar comillas alrededor de su referencia de variable, para asegurarse de que las nuevas líneas no sean interpretadas por el propio shell.
Por supuesto, este truco particular puede ser sensible a la plataforma y al shell. Lo probé en Ubuntu Linux con GNU bash 3.2.13; YMMV.
Sí. Escapas de las líneas nuevas con /
:
VARIABLE="/
THIS IS A VERY LONG/
TEXT STRING IN A MAKE VARIABLE"
actualizar
Ah, ¿quieres las nuevas líneas? Entonces no, no creo que haya ninguna forma en Make de vainilla. Sin embargo, siempre puede usar un documento aquí en la parte de comando
[Esto no funciona, mira el comentario de MadScientist]
foo:
echo <<EOF
Here is a multiple line text
with embedded newlines.
EOF
Simplemente una postdata a la respuesta de Eric Melski: puede incluir la salida de comandos en el texto, pero debe usar la sintaxis Makefile "$ (shell foo)" en lugar de la sintaxis del intérprete de comandos "$ (foo)". Por ejemplo:
define ANNOUNCE_BODY
As of $(shell date), version $(VERSION) of $(PACKAGE_NAME) has been released.
It can be downloaded from $(DOWNLOAD_URL).
endef
Suponiendo que solo desea imprimir el contenido de su variable en salida estándar, hay otra solución:
do-echo:
$(info $(YOUR_MULTILINE_VAR))
Use la sustitución de cadena :
VERSION := 1.1.1
PACKAGE_NAME := Foo Bar
DOWNLOAD_URL := https://go.get/some/thing.tar.gz
ANNOUNCE_BODY := Version $(VERSION) of $(PACKAGE_NAME) has been released. /
| /
| It can be downloaded from $(DOWNLOAD_URL) /
| /
| etc, etc
Luego en tu receta, pon
@echo $(subst | ,$$''/n'',$(ANNOUNCE_BODY))
Esto funciona porque Make está sustituyendo todas las apariciones de |
(tenga en cuenta el espacio) y cambiarlo por un carácter de nueva línea ( $$''/n''
). Puede pensar en las invocaciones de shell-script equivalentes como algo así:
Antes de:
$ echo "Version 1.1.1 of Foo Bar has been released. | | It can be downloaded from https://go.get/some/thing.tar.gz | | etc, etc"
Después:
$ echo "Version 1.1.1 of Foo Bar has been released.
>
> It can be downloaded from https://go.get/some/thing.tar.gz
>
> etc, etc"
No estoy seguro de si $''/n''
está disponible en sistemas que no sean POSIX, pero si puede obtener acceso a un solo carácter de línea nueva (incluso leyendo una cadena desde un archivo externo), el principio subyacente es el mismo.
Si tiene muchos mensajes como este, puede reducir el ruido utilizando una macro :
print = $(subst | ,$$''/n'',$(1))
Donde lo invocarías así:
@$(call print,$(ANNOUNCE_BODY))
Espero que esto ayude a alguien. =)