是否可以在 Makefile 中创建多行字符串变量

我想创建一个 makefile 变量,它是一个多行字符串(例如,电子邮件发布公告的正文)。比如

ANNOUNCE_BODY="
Version $(VERSION) of $(PACKAGE_NAME) has been released


It can be downloaded from $(DOWNLOAD_URL)


etc, etc"

但我好像找不到办法,有可能吗?

96616 次浏览

是的。你用 \逃过了新台词:

VARIABLE="\
THIS IS A VERY LONG\
TEXT STRING IN A MAKE VARIABLE"

更新

你想要新台词吗?那么不,我不认为有任何办法,在香草制造。但是,您总是可以在命令部分使用 here-document 吗

[这不起作用,见 MadScientist 的评论]

foo:
echo <<EOF
Here is a multiple line text
with embedded newlines.
EOF

这并不是一个有用的答案,只是为了说明“定义”并不像 Ax 所回答的那样有效(不适合在注释中使用) :

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)

它给出了一个错误,即无法找到命令‘ It’,因此它尝试将 ANNOUNCE BODY 的第二行解释为命令。

是的,您可以使用 Definition 关键字声明一个 多行变量多行变量,如下所示:

define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.


It can be downloaded from $(DOWNLOAD_URL).


etc, etc.
endef

棘手的部分是将多行变量从 makefile 中取出。如果您只是使用“ echo $(ANNOUNCE _ BODY)”,您将看到其他人在这里发布的结果—— shell 尝试将变量的第二行和后续行作为命令处理。

然而,你可以将变量值原封不动地导出到 shell 中作为一个环境变量,然后从 shell 中引用它作为一个环境变量(而不是 make 变量)。例如:

export ANNOUNCE_BODY
all:
@echo "$$ANNOUNCE_BODY"

请注意使用的是表示 shell 环境变量引用的 $$ANNOUNCE_BODY,而不是表示常规 make 变量引用的 $(ANNOUNCE_BODY)。还要确保在变量引用周围使用引号,以确保新行不会被 shell 本身解释。

当然,这个特殊的技巧可能对平台和 shell 敏感。我用 GNU bash 3.2.13; YMMV 在 Ubuntu Linux 上测试了它。

为什么不使用字符串中的 n 个字符来定义行尾呢?还要添加额外的反斜杠,将其添加到多行中。

ANNOUNCE_BODY=" \n\
Version $(VERSION) of $(PACKAGE_NAME) has been released \n\
\n\
It can be downloaded from $(DOWNLOAD_URL) \n\
\n\
etc, etc"

这对我很有效:

ANNOUNCE_BODY="first line\\nsecond line"


all:
@echo -e $(ANNOUNCE_BODY)

Eric Melski 的回答的后记: 您可以在文本中包含命令的输出,但是必须使用 Makefile 语法“ $(shell foo)”而不是 shell 语法“ $(foo)”。例如:

define ANNOUNCE_BODY
As of $(shell date), version $(VERSION) of $(PACKAGE_NAME) has been released.


It can be downloaded from $(DOWNLOAD_URL).


endef

另一种“让多行变量退出 makefile”的方法(Eric Melski 称之为“棘手的部分”)是计划使用 subst函数来替换多行字符串中用 define引入的换行符。然后与 echo一起使用-e 来解释它们。您可能需要设置。SHELL = bash 获取执行此操作的回显。

这种方法的一个优点是,您还可以在文本中放入其他这样的转义字符,并尊重它们。

这种方法综合了目前为止提到的所有方法。

你会得到:

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})'

注意最后一个 echo 上的单引号是至关重要的。

GNUMakefile 可以执行以下操作。这是丑陋的,我不会说你应该这样做,但我做在某些情况下。

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

如果不存在. profile 文件,则 make .profile创建一个。

在应用程序只在 POSIX shell 环境中使用 GNUMakefile 的情况下,使用了此解决方案。该项目不是一个平台兼容性是一个问题的开源项目。

我们的目标是创建一个 Makefile,以促进特定类型工作区的设置和使用。Makefile 带来了各种简单的资源,而不需要另一个特殊的归档文件等等。从某种意义上说,它是一个 shell 归档文件。然后,过程可以将此 Makefile 放入要在其中工作的文件夹中。设置您的工作区输入 make workspace,然后执行等等操作,输入 make blah,等等。

需要技巧的是找出 shell 引用的内容。上面的内容完成了这项工作,并且接近于在 Makefile 指定一个 here 文档的想法。至于它是否是一个普遍适用的好主意,则完全是另一个问题。

我认为跨平台使用最安全的方法是每行使用一个 echo:

  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" >> $@

这样可以避免对回显的版本做出任何假设。

假设您只想在标准输出上打印变量的内容,还有另一种解决方案:

do-echo:
$(info $(YOUR_MULTILINE_VAR))

你应该使用“ Definition/endf”构造:

define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.


It can be downloaded from $(DOWNLOAD_URL).


etc, etc.
endef

然后您应该将这个变量的值传递给 shell 命令。但是,如果使用 Make 变量替换进行此操作,则会导致 command 分裂为多个:

ANNOUNCE.txt:
echo $(ANNOUNCE_BODY) > $@               # doesn't work

引号也没用。

传递价值的最佳方式是通过环境变量传递:

ANNOUNCE.txt: export ANNOUNCE_BODY:=$(ANNOUNCE_BODY)
ANNOUNCE.txt:
echo "$${ANNOUNCE_BODY}" > $@

注意:

  1. 变量是为这个特定的目标导出的,这样您就可以重复使用这个环境不会受到太大的污染;
  2. 使用环境变量(双引号和变量名前后的花括号) ;
  3. 在变量周围使用引号。如果没有引号,换行将丢失,所有文本将显示在一行上。

对于 GNU Make 3.82及以上版本,当涉及到多行 shell 代码片段时,.ONESHELL选项是您的朋友。把其他答案的提示放在一起,我得到了:

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))"

请确保,当复制和粘贴上述示例到您的编辑器,任何 <tab>字符保留,否则 version目标将中断!

请注意,abc0将导致 Makefile 的所有目标使用一个 shell 来执行所有命令。

这并没有给出 here 文档,但是它以一种适合于管道的方式显示了多行消息。

=====

MSG = this is a\\n\
multi-line\\n\
message


method1:
@$(SHELL) -c "echo '$(MSG)'" | sed -e 's/^ //'

=====

你也可以使用 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 "---"

=====

输出如下:

=====

$ make method1 method2
Method 1:
this is a
multi-line
message
---
Method 2:
this is a
multi-line
message
---
$

=====

使用 字符串替换字符串替换:

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

然后在你的食谱里

    @echo $(subst | ,$$'\n',$(ANNOUNCE_BODY))

这是因为 Make 正在替换所有出现的 (注意空格)并用换行符($$'\n')交换它。您可以将相应的 shell 脚本调用想象成这样的东西:

以前:

$ 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"

之后:

$ 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"

我不确定 $'\n'在非 POSIX 系统上是否可用,但是如果您可以访问单个换行符(即使是通过从外部文件读取字符串) ,其基本原理是相同的。

如果你有很多这样的信息,你可以使用 宏观来减少噪音:

print = $(subst | ,$$'\n',$(1))

你可以这样调用它:

@$(call print,$(ANNOUNCE_BODY))

希望这对谁有帮助。 =)

本着. ONESHELL 的精神,有可能非常接近. ONESHELL 挑战的环境:

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

使用的一个例子是这样的:

define TEST
printf '>\n%s\n' "Hello
World\n/$$$$/"
endef


all:
$(call oneshell,$(TEST))

这显示了输出(假设 pid 为27801) :

>
Hello
World\n/27801/

这种方法确实允许一些额外的功能:

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

这些 shell 选项将:

  • 在执行每个命令时打印它
  • 退出第一个失败的命令
  • 将使用未定义的 shell 变量视为错误

其他有趣的可能性也会出现。

我最喜欢阿哈迪斯的答案。但是为了保持柱状格式,还需要添加一样东西。

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

产出:

:: 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

作为一个替代方案,你可以使用 printf 命令,这在 OSX 或其他功能较少的平台上很有帮助。

简单地输出多行消息:

all:
@printf '%s\n' \
'Version $(VERSION) has been released' \
'' \
'You can download from URL $(URL)'

如果您试图将字符串作为参数传递给另一个程序,您可以这样做:

all:
/some/command "`printf '%s\n' 'Version $(VERSION) has been released' '' 'You can download from URL $(URL)'`"

不完全相关的 OP,但希望这将有助于在未来的人。 (因为这个问题在谷歌搜索中出现得最多)。

在 Makefile 中,我希望将文件的内容传递给 docker build 命令, 惊愕之后,我决定:

 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)

看下面的例子。

Nb: 在我的特殊情况下,我想在构建映像的过程中使用来自 https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/的例子传递一个 ssh 键(使用一个多阶段的 docker build 来克隆一个 git repo,然后在构建的第二阶段从最终的映像中删除 ssh 键)

马克菲尔

...
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 .
...

文件夹

...
ARG MY_VAR_ENCODED


RUN mkdir /root/.ssh/  && \
echo "${MY_VAR_ENCODED}" | base64 -d >  /path/to/my/file/in/container
...