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

时间:2009-03-16 04:15:11

标签: variables makefile

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

之类的东西
ANNOUNCE_BODY="
Version $(VERSION) of $(PACKAGE_NAME) has been released

It can be downloaded from $(DOWNLOAD_URL)

etc, etc"

但我似乎找不到办法做到这一点。有可能吗?

19 个答案:

答案 0 :(得分:148)

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

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作为环境变量,然后将其作为环境变量(不是make变量)从shell引用。例如:

export ANNOUNCE_BODY
all:
    @echo "$$ANNOUNCE_BODY"

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

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

答案 1 :(得分:20)

“让你的多行变量退出makefile”的另一种方法(由Eric Melski指出为“棘手的部分”)是计划使用subst函数来替换引入的新行使用define的多行字符串中的\n。然后使用-e和echo来解释它们。您可能需要设置.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})'

请注意最终回声的单引号至关重要。

答案 2 :(得分:10)

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

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

答案 3 :(得分:3)

是。您使用\转义换行符:

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

答案 4 :(得分:2)

答案 5 :(得分:2)

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

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

It can be downloaded from $(DOWNLOAD_URL).  

endef

答案 6 :(得分:1)

为什么不使用字符串中的\ 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"

答案 7 :(得分:1)

我最喜欢alhadis的答案。但要保持列式格式,请添加一个。

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

答案 8 :(得分:1)

本着.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变量视为错误

其他有趣的可能性可能会表明自己。

答案 9 :(得分:1)

你应该使用“define / endef”Make construct:

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

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

然后你应该将这个变量的值传递给shell命令。但是,如果你使用Make变量替换它,它将导致命令分成多个:

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

Qouting也无济于事。

传递值的最佳方法是通过环境变量传递它:

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

注意:

  1. 为此特定目标导出变量,以便您可以重复使用该环境不会受到太多污染;
  2. 使用环境变量(变量名称周围的双qoutes和花括号);
  3. 围绕变量使用引号。没有它们,新行将丢失,所有文本将出现在一行中。

答案 10 :(得分:1)

使用GNU Make时,.ONESHELL选项是您的朋友,因为它涉及多行shell代码段。从其他答案中得出一些提示,我得到:

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目标将会中断!

答案 11 :(得分:1)

这不提供此处的文档,但它确实以适合管道的方式显示多行消息。

=====

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

=====

答案 12 :(得分:0)

与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构建来克隆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 .
...

Dockerfile

...
ARG MY_VAR_ENCODED

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

答案 13 :(得分:0)

作为替代方法,您可以使用printf命令。这在OSX或功能较少的其他平台上很有用。

简单地输出多行消息:

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

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

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

答案 14 :(得分:0)

使用string substitution

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"

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

如果你有很多这样的消息,可以使用macro

来减少噪音
print = $(subst | ,$$'\n',$(1))

你可以这样调用它:

@$(call print,$(ANNOUNCE_BODY))

希望这有助于某人。 =)

答案 15 :(得分:0)

我认为跨平台使用最安全的答案是每行使用一个回声:

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

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

答案 16 :(得分:0)

GNU Makefile可以执行以下操作。它很难看,我不会说你应该这样做,但我在某些情况下会这样做。

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会创建.profile文件(如果不存在)。

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

目标是创建一个Makefile,以便于设置和使用特定类型的工作区。 Makefile带来了各种简单的资源,而不需要像其他特殊存档那样的东西等。从某种意义上说,它是一个shell存档。然后,程序可以说将此Makefile放在要处理的文件夹中。设置工作区输入make workspace,然后执行等等,输入make blah等。

什么可能变得棘手的是弄清楚要引用什么。上面做了这个工作,并接近在Makefile中指定here文档的想法。对于一般用途来说,这是一个好主意是另一个问题。

答案 17 :(得分:0)

它对我有用:

ANNOUNCE_BODY="first line\\nsecond line"

all:
    @echo -e $(ANNOUNCE_BODY)

答案 18 :(得分:0)

不是一个非常有用的答案,只是表明'定义'不能像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的第二行解释为命令。