【问题标题】:How to use shell commands in Makefile如何在 Makefile 中使用 shell 命令
【发布时间】:2012-04-18 22:30:31
【问题描述】:

我正在尝试在其他命令(例如 echo、rsync)中使用 ls 的结果:

all:
    <Building, creating some .tgz files - removed for clarity>
    FILES = $(shell ls)
    echo $(FILES)

但我明白了:

make
FILES = Makefile file1.tgz file2.tgz file3.tgz
make: FILES: No such file or directory
make: *** [all] Error 1

我尝试过使用echo $$FILESecho ${FILES}echo $(FILES),但没有成功。

【问题讨论】:

    标签: bash makefile echo


    【解决方案1】:

    与:

    FILES = $(shell ls)
    

    像这样在all 下缩进,这是一个构建命令。所以这会扩展$(shell ls),然后尝试运行命令FILES ...

    如果FILES 应该是make 变量,则需要在配方部分之外分配这些变量,例如:

    FILES = $(shell ls)
    all:
            echo $(FILES)
    

    当然,这意味着FILES 将被设置为“ls 的输出”运行任何创建 .tgz 文件的命令之前。 (虽然Kaz notes 变量每次都会重新扩展,所以最终它会包含 .tgz 文件;为了提高效率和/或正确性,一些 make 变体有 FILES := ... 以避免这种情况。1)

    如果FILES 应该是一个 shell 变量,你可以设置它,但你需要用 shell-ese 来做,不带空格,并用引号引起来:

    all:
            FILES="$(shell ls)"
    

    但是,每一行都是由一个单独的 shell 运行的,所以这个变量不会存在到下一行,所以你必须立即使用它:

            FILES="$(shell ls)"; echo $$FILES
    

    这有点傻,因为 shell 会首先为你扩展 *(和其他 shell glob 表达式),所以你可以:

            echo *
    

    作为你的 shell 命令。

    最后,作为一般规则(并不真正适用于本示例):正如 cmets 中的 esperanto 注释,使用来自 ls 的输出并不完全可靠(某些细节取决于文件名,有时甚至取决于版本lsls 的某些版本在某些情况下会尝试清理输出)。因此,正如l0b0idelic 注意,如果你使用GNU make,你可以使用$(wildcard)$(subst ...) 来完成make 本身内部的所有事情(避免任何“文件名中的奇怪字符”问题)。 (在sh 脚本中,包括makefile 的recipe 部分,另一种方法是使用find ... -print0 | xargs -0 来避免跳过空格、换行符、控制字符等。)


    1The GNU Make documentation notes further that POSIX make added ::= assignment in 2012。我还没有找到一个指向 POSIX 文档的快速参考链接,我也不知道哪个make 变体支持::= 分配,尽管今天 GNU make 支持,与:= 具有相同的含义,即,立即展开作业。

    请注意,VAR := $(shell command args...) 也可以在多个 make 变体中拼写为 VAR != command args...,据我所知,包括所有现代 GNU 和 BSD 变体。这些其他变体没有$(shell),因此使用VAR != command args... 在更短在更多变体中工作方面都有优势。

    【讨论】:

    • 谢谢。我想使用一个精心设计的命令(例如lssed 和剪切),然后在 rsync 和其他命令中使用结果。我必须一遍又一遍地重复冗长的命令吗?我不能将结果存储在内部 Make 变量中吗?
    • Gnu make 可能有办法做到这一点,但我从未使用过它,我们使用的所有极其复杂的 makefile 只使用 shell 变量和用 "; \ 构建的巨大单行 shell 命令。 " 根据需要在每行的末尾。 (无法让代码编码与此处的反斜杠序列一起使用,嗯)
    • 可能类似于:FILE = $(shell ls *.c | sed -e "s^fun^bun^g")
    • @William: make 可以在不使用 shell 的情况下做到这一点:FILE = $(subst fun,bun,$(wildcard *.c))
    • 我想指出,虽然在这种情况下它似乎不是很重要,但你不应该自动解析 ls 的输出。 ls 旨在向人类显示信息,而不是被束缚在脚本中。更多信息在这里:mywiki.wooledge.org/ParsingLs 可能,如果“make”没有为您提供适当的通配符扩展,“find”比“ls”更好。
    【解决方案2】:

    此外,除了 torek 的回答:突出的一件事是您正在使用延迟评估的宏赋值。

    如果您使用 GNU Make,请使用 := 分配而不是 =。此赋值导致右侧立即展开,并存储在左侧变量中。

    FILES := $(shell ...)  # expand now; FILES is now the result of $(shell ...)
    
    FILES = $(shell ...)   # expand later: FILES holds the syntax $(shell ...)
    

    如果您使用= 赋值,则意味着$(FILES) 的每一次出现都将扩展$(shell ...) 语法并因此调用shell 命令。这会使你的 make 工作运行得更慢,甚至会产生一些令人惊讶的后果。

    【讨论】:

    • 现在我们有了列表,我们如何遍历列表中的每个项目并对其执行命令?比如构建还是测试?
    • @anon58192932 执行命令的特定迭代通常在构建配方的 shell 语法片段中完成,因此它发生在 shell 中,而不是在 make:for x in $(FILES); do command $$x; done 中。注意加倍的$$ 将单个$ 传递给shell。此外,贝壳碎片是单层的;要编写多行 shell 代码,请使用由 make 本身处理并折叠成一行的反斜杠延续。这意味着shell命令分隔分号是强制性的。
    猜你喜欢
    • 2019-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-20
    • 1970-01-01
    • 1970-01-01
    • 2013-10-14
    • 1970-01-01
    相关资源
    最近更新 更多