如何在 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 $$FILES
、echo $FILES
和echo $(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
的输出并不完全可靠(某些细节取决于文件名,有时甚至取决于版本ls
;ls
的某些版本在某些情况下会尝试清理输出)。因此,正如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...
在更短和在更多变体中工作方面都有优势。
【讨论】:
谢谢。我想使用一个精心设计的命令(例如ls
和sed
和剪切),然后在 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 命令的主要内容,如果未能解决你的问题,请参考以下文章