我在 GNU Make 中遇到了一个意想不到的奇怪现象,并且找不到合理配置它的方法。我将
SHELL
分配给我自己设计的自定义 shell,但是导出哪些变量的问题很容易用简单的 bash shell 重现:
SHELL := /bin/bash
export FOO := bar
.PHONY: all a b c
all: a b
a:
@echo A: $$(env | grep FOO)
b:
@echo B: $(shell env | grep FOO)
c:
@echo === main target ===
@echo $$(env | sed -e 's/=.*//')
@echo === shell function ===
@echo $(shell env | sed -e 's/=.*//')
这会产生一些意想不到的结果:
$ make
A: FOO=bar
B:
$ FOO=baz make
A: FOO=bar
B: FOO=baz
如通信变量中所述,导出的
FOO
变量作为环境变量公开给在配方中运行行的shell。令人意想不到的是,
shell函数没有得到同样的待遇!事实上,快速浏览一下 make c
中出现的内容,有几个变量没有被传递。我可以通过手动传入每个变量来解决这个问题(例如$(shell FOO="$(FOO)" env)
),但这很快就会变得愚蠢(而且危险)。
这是特别有问题的,因为我希望导出的变量是
PATH
。事实上,构建我的目标的 shell 有一个 PATH
u200c 变量,而在 shell 函数中运行的任何内容都有不同的 PATH
,这是有问题的。如果你看一下完整的环境
如何标记
Makefile
环境中的一些变量,以便它们作为环境的一部分自动传递到从 shell 函数运行的 shell?
正如@MadScientist 所指出的,
$(shell ...)
未传递导出变量。根据具体情况,如果环境变量的数量已知,则可以在 shell 命令中使用显式变量。例如
c:
@echo === shell function ===
@echo $(shell FOO=$FOO env | sed -e 's/=.*//')
当然,这个解决方案假设可以枚举所有要传递的变量,并且变量的数量相当小,否则代码会变得很难看。
作为替代方案,请考虑使用临时文件,而不是使用值来传递值
c:
# env var are set, store result in var.txt
do-something > var.txt
# Uses the result of the previous step, from var.txt
step2 $$(cat var.txt)
默认 make 接收环境变量。如果导出并取消设置 make 变量,则该 make 变量将具有相应环境变量的值并出现在所有 shell 上下文中。
make -e
将环境变量作为覆盖而不是 make 变量,覆盖 make 内部设置的变量。因此,通常更喜欢 make -e
从调用上下文继承 shell 环境变量。
因此,要使其正常工作,只需导出您想要在配方执行环境中出现的变量名称即可。我还没有测试过非 sh-family
SHELL
替代品,比如 python:
# makefile
# export the var in make!
export FOO
# '$$' escapes the '$', so you are seeing the shell version of the variable
# @ prefixed recipes do not print
env:
@echo "FOO: $$FOO" # normal env override
@echo $(shell echo FOO: $(FOO)) # make var value in $(shell ...) from calling env
@echo $(shell echo FOO: $$FOO) # env overridden in $(shell ...)
# shell
$ make env
FOO:
FOO:
FOO:
$ export FOO="BAR"
$ make -e env
FOO: BAR
FOO: BAR
FOO: BAR
这是我编写的第二个 makefile,以类似于原始问题的方式证明它:
export FOO
foo1=$(shell echo FOO: $(FOO))
foo2=$(shell echo FOO: $$FOO)
env:
@echo "FOO: $$FOO"
@echo $(foo1)
@echo $(foo2)
底线:导出您想要出现在子 shell 中的变量,并且当您想要严格继承环境变量并覆盖任何内部定义时更喜欢使用
make -e
。
内部定义也可以根据环境来编写。因此,如果您是唯一作者,您可以定义变量以适应环境,删除
-e
:
export FOO
FOO?=BIZ
Shell 函数的作用是解析输出并扩展它在其中找到的变量。考虑以下代码:
export BANANA = I am a banana
foo: G := $(shell echo who am I? '$$'BANANA)
foo:
@echo $(G)
输出:我是谁?我是香蕉