如何在Makefile中使用shell命令

149

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

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),但都没有成功。

3个回答

236

使用:

FILES = $(shell ls)

这是一个构建命令,所有缩进在all下面的内容都是如此。因此,它会展开$(shell ls),然后尝试运行命令FILES ...

如果FILES应该是一个make变量,这些变量需要在配方部分之外进行赋值,例如:

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

当然,这意味着在运行任何创建.tgz文件的命令之前,“FILES”将被设置为“来自‘ls’的输出”。(尽管如Kaz notes所述,该变量每次都会重新扩展,因此最终将包括.tgz文件;一些make变体具有“FILES:=...”以避免这种情况,以提高效率和/或正确性。1
如果“FILES”应该是一个shell变量,则可以设置它,但必须使用无空格的shell语言进行引用。
all:
        FILES="$(shell ls)"

然而,每行都由单独的 shell 运行,因此这个变量将不会在下一行生效,所以你必须立即使用它:

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

这有点儿愚蠢,因为 shell 会在首次展开 *(以及其他 shell glob 表达式)时为您完成,所以您只需:

        echo *

作为您的Shell命令。

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


1GNU Make文档进一步指出,POSIX make在2012年添加了::=赋值语句。我没有找到一个快速参考链接来查看POSIX文件的内容,也不知道哪些make变种支持::=赋值语句,尽管GNU make今天支持,其含义与:=相同,即立即执行带有扩展的赋值语句。

请注意,在几个make变种中,包括所有现代GNU和BSD变种,VAR := $(shell command args...)也可以写作VAR != command args...。这些其他变种没有$(shell),因此使用VAR != command args...既更短适用于更多变种。


1
GNU Make可能有一种方法可以做到这一点,但我从未使用过。我们使用的所有可怕复杂的Makefile都只是使用Shell变量和巨大的单行Shell命令构建,并在每行末尾带有“; \”(根据需要)。 - torek
1
此外,你应该使用通配符 make内置函数,而不是使用ls命令。 - l0b0
1
可能是这样的内容:FILE = $(shell ls *.c | sed -e "s^fun^bun^g") - William Morris
6
@William说:可以使用make而不需要使用shell来实现这个功能:FILE = $(subst fun,bun,$(wildcard *.c)) - Idelic
1
我想指出的是,尽管在这种情况下似乎并不是非常重要,但您不应自动解析ls的输出。ls旨在向人类展示信息,而不是被链接在脚本中。更多信息请参见:http://mywiki.wooledge.org/ParsingLs可能情况下,如果“make”没有为您提供适当的通配符扩展,“find”比“ls”更好。 - Raúl Salinas-Monteagudo
显示剩余5条评论

71

除了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任务运行变慢,甚至会产生一些意想不到的后果。

1
既然我们有了列表,那么如何迭代列表中的每个项目并对其执行命令呢?例如构建或测试? - fIwJlxSzApHEZIl
5
通常在构建配方的 shell 语法片段中执行特定迭代的命令,因此它发生在 shell 中而不是 make 中:for x in $(FILES); do command $$x; done。请注意双重 $$ 传递单个 $ 给 shell。此外,shell 片段是单行的;要编写多行 shell 代码,您需要使用反斜杠续行符,这由 make 自身处理并折叠成一行。这意味着分隔 shell 命令的分号是强制性的。 - Kaz

-1
我已经尝试使用这个命令,在make v4.4.1版本中它是有效的。
all:
    echo `pwd`
    echo `ls -al`
    echo `ls | grep 'filename'`
    cd ..; pwd
    pwd
    ls
    ls -al
    ls -al | grep 'exe'

那些是无用的“echo”;而且在脚本中不要使用“ls”。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接