Makefile变量初始化和导出
somevar := apple
export somevar
update := $(shell echo "v=$$somevar")
all:
@echo $(update)
我希望将 apple 作为命令的输出,但是它是空的,这让我认为导出和 :=
变量扩展发生在不同的阶段。如何克服这个问题?
somevar := apple
export somevar
update := $(shell echo "v=$somevar")
all:
@echo $(update)
I was hoping to apple as output of command, however it's empty, which makes me think export and :=
variable expansion taking place on different phases. how to overcome this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
问题在于
export
将变量导出到命令使用的子 shell;它不可用于其他任务的扩展。所以不要试图从规则之外的环境中获取它。输出是:
The problem is that
export
exports the variable to the subshells used by the commands; it is not available for expansion in other assignments. So don't try to get it from the environment outside a rule.The output is:
@Beta 的回答包含关键指针:使用 GNU
make
,变量标记为 < code>export 仅适用于[为]启动的 shell 配方命令(属于规则一部分的命令),遗憾的是不适用于的调用$(shell ...)
(它们只能看到make
本身在启动时看到的环境)。,有一个解决方法:显式将导出的变量作为环境变量传递给
shell
函数:不过 在 shell 命令前面加上
=
,该定义将作为环境变量添加到该命令所看到的环境中 - 这是一个通用的 shell 功能。警告:@Kaz 在评论中指出,如果
$(somevar)
包含某些字符,此方法会出现错误,因为变量扩展是逐字 (无转义),这可能会破坏生成的 shell 命令,并建议以下变体也可以与嵌入式'
实例一起使用(将输入值分解为单引号子字符串)引用的'
拼接):这应该适用于除多行值之外的所有值(这种情况很少见;据我所知,没有针对多行值的解决方法的)。
附带说明一下,文字
$
字符。 in 值必须表示为$$
,否则make
会将它们解释为对其自身变量的引用。请注意,我故意不选择 OP 的原始语句
update := $(shell echo "v=$$somevar")
进行演示,因为它包含混淆问题的陷阱:由于 shell 评估命令行的方式,somevar=apple echo v=$somevar
不会评估为v=apple
,因为>$somevar
引用在somevar=apple
生效之前扩展。在这种情况下,要达到所需的效果,您必须使用 2 语句:update := $(shell export somevar="$(somevar)"; echo "v=$$某些变量”)
至于 bug 与功能的争论:
虽然可以说
shell
函数应该看到与配方命令相同的环境,但文档使得没有这样的承诺 - 请参阅 http://www.gnu.org/ software/make/manual/make.html#Shell-Function。相反,http://www.gnu.org/software/make/manual /make.html#Variables_002fRecursion 仅提到使导出的变量可用于recipe 命令。@Beta's answer contains the crucial pointer: with GNU
make
, variables marked withexport
are only available to [the shells launched for] recipe commands (commands that are part of rules), regrettably not to invocations of$(shell ...)
(they only see the environment thatmake
itself saw when it was launched).There is a workaround, however: explicitly pass the exported variable as an environment variable to the
shell
function:By prepending the shell command with
<var>=<val>
, that definition is added as an environment variable to the environment that the command sees - this is a generic shell feature.Caveat: @Kaz points out in a comment that this method misbehaves if
$(somevar)
contains certain chars., because the variable expansion is verbatim (no escaping), which can break the resulting shell command, and suggests the following variant to also work with embedded'
instances (breaks the input value into single-quoted substrings with quoted'
spliced in):This should work with all values except multi-line ones (which are rare; there is no workaround for multi-line values that I'm aware of).
On a side note, literal
$
chars. in values must be represented as$$
, otherwisemake
will interpret them as references to its own variables.Note that I've deliberately NOT chosen the OP's original statement,
update := $(shell echo "v=$$somevar")
, for demonstration, because it contains a pitfall that muddles the issue: due to how the shell evaluates a command line,somevar=apple echo v=$somevar
does NOT evaluate tov=apple
, because the$somevar
reference is expanded beforesomevar=apple
takes effect. To achieve the desired effect in this case, you'd have to use 2 statements:update := $(shell export somevar="$(somevar)"; echo "v=$$somevar")
As for the bug-vs.-feature debate:
While it can be argued that the
shell
function should see the same environment as recipe commands, the documentation makes no such promise - see http://www.gnu.org/software/make/manual/make.html#Shell-Function. Conversely, http://www.gnu.org/software/make/manual/make.html#Variables_002fRecursion only mentions making exported variables available to recipe commands.运行 makefile
为我提供了(环境中未定义 foo)
尝试使用引用的形式 (
update := "v=$$somevar"
) 并让 shell 在运行命令时处理扩展 (您仍然需要导出)Running the makefile
gives for me (with foo undefined in the environment)
Try using the quoted form (
update := "v=$$somevar"
) and let the shell handle expansion when a command is run (you'll still need the export)虽然
export
与$(shell ...)
配合得不太好,但有一个简单的解决方法。我们可以通过命令行将数据传递给shell脚本。当然,现在环境通道对于转义和引用问题是稳健的。然而,shell 语言有一个单引号引用方法
'...'
来处理所有事情。唯一的问题是无法在其中获得单引号;当然,这可以通过终止引号、反斜杠转义所需的单引号并开始一个新引号来解决:换句话说:在
$(shell ...)
执行的 shell 脚本中,我们只是生成var='$(...)'
形式的变量赋值,其中$(...)
是一些插入适当转义材料的 make 表达式。因此,Makefile
:示例运行:
如果我们想要将环境变量传递给命令,我们可以使用 shell 语法
VAR0=val0 VAR1=val1 ... VARn=valn 命令来实现arg ...
。这可以通过对上面的Makefile
进行一些小的修改来说明:Run:
file.txt
包含环境变量的转储,我们可以在其中看到somevar
>。如果 GNU Make 中的export
做了正确的事情,我们就可以这样做:但最终结果是相同的。
由于您想要的最终结果是
echo $(update)
,因此无论如何您都会shell_escape
,即使 GNU Make 将导出的变量传递给$(shell ... )
。也就是说,再看一个Makefile
:输出:
Although
export
does not play nicely with$(shell ...)
, there is a simple workaround. We can pass the data to the shell script via the command line.Now of course, environment passage is robust against issues of escaping and quoting. However, the shell language has a single quote quoting method
'...'
which handles everything. The only problem is that there is no way to get a single quote in there; but of course that is solved by terminating the quote, backslash-escaping the needed single quote and starting a new quote: In other words:In the shell script executed by
$(shell ...)
we just generate a variable assignment of the formvar='$(...)'
, where$(...)
is some make expression that interpolates suitably escaped material. Thus,Makefile
:Sample run:
If we want to communicate an environment variable to a command, we can do that using the shell syntax
VAR0=val0 VAR1=val1 ... VARn=valn command arg ...
. This can be illustrated by some minor alterations to the aboveMakefile
:Run:
file.txt
contains a dump of environment variables, where we can seesomevar
. Ifexport
in GNU Make did the right thing, we would have been able to just do:but the end result is the same.
Since the end result you want is to
echo $(update)
, you wouldshell_escape
anyway, even if GNU Make passed exported vars to$(shell ...)
. That is to say, look at one moreMakefile
:Output: