如何在 Makefile 中使用 shell 命令

Posted

技术标签:

【中文标题】如何在 Makefile 中使用 shell 命令【英文标题】:How to use shell commands in Makefile 【发布时间】: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 $FILESecho $(FILES),但没有成功。

【问题讨论】:

【参考方案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 的某些版本在某些情况下会尝试清理输出)。因此,正如l0b0 和idelic 注意,如果你使用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命令分隔分号是强制性的。

以上是关于如何在 Makefile 中使用 shell 命令的主要内容,如果未能解决你的问题,请参考以下文章

Shell脚本——make命令和Makefile文件

如何写 makefile

Makefile中要引用一个系统环境变量该怎么办

Makefile使用

Makefile,Shell command,Shell Language 之间的联系

Linux makefile&shell的一个问题