【问题标题】:Makefile variable initialization and exportMakefile 变量初始化和导出
【发布时间】:2011-02-19 18:57:34
【问题描述】:
somevar := apple
export somevar
update := $(shell echo "v=$$somevar")

all:
    @echo $(update)

我希望将 apple 作为命令的输出,但它是空的,这让我认为 export 和 := 变量扩展发生在不同的阶段。如何克服?

【问题讨论】:

    标签: variables makefile export


    【解决方案1】:

    问题在于export 将变量导出到命令使用的子shell;它不可用于其他任务的扩展。所以不要试图从规则之外的环境中获取它。

    somevar := apple
    export somevar
    
    update1 := $(shell perl -e 'print "method 1 $$ENV{somevar}\n"')
    # Make runs the shell command, the shell does not know somevar, so update1 is "method 1 ".
    
    update2 := perl -e 'print "method 2 $$ENV{somevar}\n"'
    # Now update2 is perl -e 'print "method 2 $$ENV{somevar}\n"'
    
    # Lest we forget:
    update3 := method 3 $(somevar)
    
    all:
        echo $(update1)
        $(update2)
        echo $(update3)
        perl -e 'print method 4 "$$ENV{somevar}\n"'
    

    【讨论】:

    • 实际上是 bug[savannah.gnu.org/bugs/?10593] 没有修复,也许永远不会修复。谢谢,我会尽量不要用奇怪的 makefile 问题来打扰你。
    • @Michael:根据我对 GNUMake 手册的阅读,这不是错误。 (而且您不必在我的答案的评论前加上@Beta。)
    • 这并不奇怪。我需要这个。为什么我需要这个?因为环境是将任意字符串传递给外部脚本的唯一防弹方法。例如,假设我有一个变量 V 包含以下内容:各种类型的引号、重要的 shell 元字符(如美元)、反斜杠转义等等。我想要一个使用变量 $V 的 shell 脚本。
    • @Kaz:这听起来很危险,但可行。上述方法之一是否可以解决问题?
    • @Beta 过时的评论。我通过使用$(subst ..) 将单引号转义为'\'' 解决了这个问题。然后将结果插入到单引号之间的 shell 脚本中。
    【解决方案2】:

    @Beta's answer 包含关键指针:对于 GNU make标有 export 的变量仅适用于 [为这些 shell 启动的]recipe 命令(作为规则一部分的命令),遗憾的是不适用于$(shell ...)的调用(它们只看到make在启动时看到的环境)。

    有一个解决方法,但是:明确将导出的变量作为环境变量传递给shell函数:

    update := $(shell somevar='$(somevar)' perl -e 'print "$$ENV{somevar}"')
    

    通过在 shell 命令前添加 <var>=<val>,该定义将作为环境变量添加到命令看到的环境中 - 这是一个通用的 shell 功能。

    警告:@Kaz 在评论中指出,如果$(somevar) 包含某些字符,则此方法会出现错误,因为变量扩展是逐字逐句(没有转义),这可能会破坏生成的 shell 命令,并建议以下 变体也可以与嵌入的 ' 实例一起使用(将输入值分解为单引号子字符串,并在其中拼接了引号 '): p>

    update := $(shell somevar='$(subst ','\'',$(somevar))' perl -e 'print "$$ENV{somevar}"')
    

    这应该适用于除 multi-line 以外的所有值(这种情况很少见;据我所知,没有针对多行值的解决方法)。

    附带说明,文字 $ 字符。 in 值必须表示为$$,否则make 会将它们解释为对其自身变量的引用。

    请注意,我故意不选择 OP 的 original 语句 update := $(shell echo "v=$$somevar") 进行演示,因为它包含一个混淆问题的陷阱:由于 shell 如何评估命令行,somevar=apple echo v=$somevar不会评估为v=apple,因为$somevar 引用在somevar=apple 生效之前扩展。为了在这种情况下达到预期的效果,您必须使用 2 语句:update := $(shell export somevar="$(somevar)"; echo "v=$$somevar")


    关于 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 命令。

    【讨论】:

    • 此解决方法不是解决方法。通过环境传递材料已经是解决引用和转义问题的解决方法。通过构造“VAR=val command ...”语法,我们重新引入了这些问题。
    • @Kaz:感谢您指出问题 - 我已经更新了答案,但需要注意。从一般意义上解决问题并非易事,但如果可以忍受这些限制(例如在分配受约束的值时,例如$(PATH)),手头的方法可能仍然是一种选择。
    • 最简单的解决方法是使用$(subst ...) 将出现的' 替换为'\''。然后,在 shell 端的单引号之间插入结果数据。 (我今天为此苦苦挣扎并以这种方式解决了。)
    • @Kaz:这很有希望,因为它可以正确转义嵌入的 ' 字符。但不能解决文字 $ 字符的问题。:make 总是会自行扩展它们(如引用它自己的变量),之前 $(subst …) 被应用 - 请参阅我的更新。
    • 我认为这不是问题。如果我有FOO := $(BAR),然后在某个地方将$(FOO) 插入到shell 脚本中,我希望扩展$(BAR)。该扩展在分配FOO 时完成,因为我使用了立即变量(:=)。如果$(BAR) 产生一个美元符号,那么该美元符号现在只是FOO 中的数据,不会引起更多问题。如果我们在 shell 脚本中的单引号之间得到$(FOO),那么那个美元符号也不会在那里引起问题。 I am $HOME 在我们考虑与 shell 通信之前就已经损坏了。
    【解决方案3】:

    运行生成文件

    foo:=apple
    export foo
    all:
            @echo ">"$(shell echo "$$foo")
            @echo ">""$$foo"
    

    为我提供(环境中未定义 foo)

    $ make
    >
    >apple
    
    $ make foo=bar
    >
    >apple
    
    $ export foo=bar; make
    >bar
    >apple
    
    $ export foo=bar; make foo=bar
    >bar
    >bar
    

    尝试使用带引号的形式 (update := "v=$$somevar") 并在运行命令时让 shell 处理扩展(您仍然需要导出)

    【讨论】:

    • 我需要保持 shell 函数不受目标命令影响的方式,因为它在现实生活中具有条件语句。
    • +1 用于显示变化;简而言之:(a)$(shell ...) 只看到启动时make 自己的环境中已经存在的变量; (b) 在make 的命令行上定义变量会覆盖makefile 中同名的定义。
    • 第一个表达式的“apple”输出怎么样,并且在makefile之外不做任何事情??
    【解决方案4】:

    虽然export 不能很好地与$(shell ...) 配合使用,但有一个简单的解决方法。我们可以通过命令行将数据传递给shell脚本。

    当然,环境通道对于转义和引用问题是健壮的。但是,shell 语言有一个单引号引用方法'...',它可以处理所有事情。唯一的问题是无法在其中获得单引号;但当然可以通过终止引号、反斜杠转义所需的单引号并开始新引号来解决:换句话说:

    ab'cd -> 'ab'\''cd'
    

    $(shell ...) 执行的shell 脚本中,我们只生成var='$(...)' 形式的变量赋值,其中$(...) 是一些插入适当转义材料的make 表达式。因此,Makefile:

    somevar := apple with 'quoted' "stuff" and dollar $$signs
    
    shell_escape = $(subst ','\'',$(1))
    
    update := $(shell v='$(call shell_escape,$(somevar))'; echo $$v > file.txt)
    
    .phony: all
    
    all:
        cat file.txt
    

    示例运行:

    $ make
    cat file.txt
    apple with 'quoted' "stuff" and dollar $signs
    

    如果我们想将环境变量传递给命令,我们可以使用 shell 语法 VAR0=val0 VAR1=val1 ... VARn=valn command arg ...。这可以通过对上述Makefile的一些小改动来说明:

    somevar := apple with 'quoted' "stuff" and dollar $$signs
    
    shell_escape = $(subst ','\'',$(1))
    
    update := $(shell somevar='$(call shell_escape,$(somevar))' env > file.txt)
    
    .phony: all
    
    all:
            grep somevar file.txt
    

    运行:

    $ make
    grep somevar file.txt
    somevar=apple with 'quoted' "stuff" and dollar $signs
    

    file.txt 包含环境变量的转储,我们可以在其中看到somevar。如果 GNU Make 中的 export 做了正确的事情,我们就可以做到:

    export somevar
    update := $(shell env > file.txt)
    

    但最终结果是一样的。

    因为你想要的最终结果是echo $(update),所以无论如何你都会shell_escape,即使GNU Make 将导出的变量传递给$(shell ...)。也就是说,再看一张Makefile

    somevar := apple with 'quoted' "stuff" and dollar $$signs
    
    shell_escape = $(subst ','\'',$(1))
    
    update := $(shell v='$(call shell_escape,$(somevar))'; echo $$v)
    
    .phony: all
    
    all:
        @echo '$(call shell_escape,$(update))'
        @echo $(update)
    

    输出:

    apple with 'quoted' "stuff" and dollar $signs
    apple with quoted stuff and dollar
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-08
      • 1970-01-01
      • 2015-12-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-21
      相关资源
      最近更新 更多