是否可以在Makefile中创build一个多行stringvariables

我想创build一个多行string的makefilevariables(例如,电子邮件发布公告的正文)。 就像是

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

但我似乎无法find办法做到这一点。 可能吗?

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

 define ANNOUNCE_BODY Version $(VERSION) of $(PACKAGE_NAME) has been released. It can be downloaded from $(DOWNLOAD_URL). etc, etc. endef 

棘手的部分是让你的多行variables退出生成文件。 如果你只是使用“echo $(ANNOUNCE_BODY)”这个显而易见的东西,你会看到其他人在这里发布的结果 – shell试图把variables的第二行和后续行作为命令本身来处理。

但是,可以将variables值原样导出到shell作为环境variables,然后将其作为环境variables(不是makevariables)从shell引用。 例如:

 export ANNOUNCE_BODY all: @echo "$$ANNOUNCE_BODY" 

注意使用$$ANNOUNCE_BODY ,表示一个shell环境variables引用,而不是$(ANNOUNCE_BODY) ,这将是一个常规的makevariables引用。 还要确保在variables引用周围使用引号,以确保换行符不被shell本身解释。

当然,这个特殊的技巧可能是平台和shell敏感的。 我在Ubuntu Linux上用GNU bash 3.2.13testing了它; 因人而异。

另一种将多行variables从makefile中取出的方法(Eric Melski指出的“棘手的部分”)是计划使用subst函数来replace多行string中用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})' 

注意最后的回声中的单引号是至关重要的。

假设您只想在标准输出中打印variables的内容,还有另一个解决scheme:

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

GNU`make'手册,6.8:定义多行variables

是。 你用\避开换行符:

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

更新

啊,你想换行吗? 那么不,我不认为香草制造商有什么办法。 但是,您始终可以在命令部分使用here-document

[这不行,看疯狂科学家的评论]

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

只是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 

你应该使用“define / endef”构造:

 define ANNOUNCE_BODY Version $(VERSION) of $(PACKAGE_NAME) has been released. It can be downloaded from $(DOWNLOAD_URL). etc, etc. endef 

那么你应该把这个variables的值传递给shell命令。 但是,如果使用Makevariablesreplace,则会导致命令拆分为多个:

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

Qouting也无济于事。

传递值的最好方法是通过环境variables传递它:

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

注意:

  1. variables被导出为这个特定的目标,这样你可以重用的环境不会受到太多的污染;
  2. 使用环境variables(variables名称周围的两个qoutes和大括号);
  3. 围绕variables使用引号。 没有它们,换行符就会丢失,所有的文本都会出现在同一行上。

为什么不使用string中的\ 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.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" >> $@ 

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

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

这并不给出这里的文档,但是它确实显示了一个适合于pipe道的多行消息。

=====

 MSG = this is a\\n\ multi-line\\n\ message method1: @$(SHELL) -c "echo '$(MSG)'" | sed -e 's/^ //' 

=====

你也可以使用Gnu的可调用的macros:

=====

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

=====

按照.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/ 

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

 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选项将会:

  • 执行时打印每个命令
  • 退出第一个失败的命令
  • 将未定义的shellvariables用作错误

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

我最喜欢阿尔哈迪斯的回答。 但要保持列格式,添加一件事。

 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 

不是一个真正有用的答案,而只是表明“定义”不能像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”无法find的错误,所以它试图将ANNOUNCE BODY的第二行解释为命令。

它为我工作:

 ANNOUNCE_BODY="first line\\nsecond line" all: @echo -e $(ANNOUNCE_BODY) 

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会创build一个.profile文件(如果不存在的话)。

这个解决scheme被用在应用程序只能在POSIX shell环境中使用GNU Makefile的地方。 该项目不是一个平台兼容性问题的开源项目。

目标是创build一个Makefile,以便于设置和使用特定types的工作区。 Makefile带来了各种简单的资源,而不需要像其他特殊的档案等东西。从某种意义上说,这是一个shell归档。 然后程序可以说这样的东西放在文件夹中的这个Makefile工作。设置你的工作区进入make workspace ,然后做等等,进入make blah

什么可以变得棘手是找出什么壳报价。 上面做的工作,接近在Makefile中指定here文件的想法。 对于一般用途来说是否是一个好主意是另外一个问题。

使用stringreplace :

 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'是否可用,但是如果你可以访问单个换行符(甚至通过从外部文件读取一个string),基本原理是一样的。

如果你有很多这样的消息,你可以通过使用macros来减less噪音:

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

你可以像这样调用它:

 @$(call print,$(ANNOUNCE_BODY)) 

希望这有助于某人。 =)