如何解决Makefile:如何在Shell函数中正确扩展变量?
我的makefile文件中有一段重复发生的代码,我想将其放入函数中。我的代码的简化版本如下所示:
IDS=4 5
MY_FUNC = $(shell echo "max=2; \
counter=1;\
while [ \$$counter -le \$$max ]; do\
id=$$(echo $(IDS) | cut -d" " -f \$${counter}); \
counter=\$$((counter+1)); \
done");
.PHONY: all
all:
@$(call MY_FUNC)
不幸的是,cut
从字面上解释了$ counter,并因此引发了错误消息cut: invalid field value ‘$counter’
。
我不明白为什么会这样,因为命令\$$command
访问此变量中存储的值。您可能知道如何正确地将counter
传递给cut
吗?
解决方法
这是您的Makefile
的固定形式。
IDS=4 5
MY_FUNC = $(shell echo 'max=2; \
counter=1; \
while [ $$counter -le $$max ]; do\
id=$$(echo $(IDS) | cut -d" " -f $${counter}); \
counter=$$((counter+1)); \
echo "debug: id: $$id"; \
done');
.PHONY: all
all:
@$(call MY_FUNC)
我添加了echo "debug: id: $$id";
命令来帮助调试并证明脚本的行为符合预期。输出为:
$ make
debug: id: 4
debug: id: 5
以下是固定脚本中值得注意的重点:
- 最外面的
echo
语句的参数的最外面的定界符是单引号(不是双引号),以防止$counter
,$max
等扩展为echo
的{{1}}调用正在执行$(shell echo ...)
语句时为空字符串。这样还可以在外部单引号内正确嵌套Makefile
中使用的双引号。 - 现在我们使用单引号作为外部定界符,其中的
cut -d" "
符号(在$
中转义为$$
)不再需要使用Makefile
进行转义。
但是,如果您想使用双引号作为最外面的定界符,那么对代码进行最少更改的替代解决方案如下所示:
\
再次输出为:
IDS=4 5
MY_FUNC = $(shell echo "max=2; \
counter=1;\
while [ \$$counter -le \$$max ]; do\
id=\$$(echo $(IDS) | cut -d\" \" -f \$${counter}); \
counter=\$$((counter+1)); \
echo \"debug: id: \$$id\"; \
done");
.PHONY: all
all:
@$(call MY_FUNC)
以下是该替代解决方案中要注意的重点:
- shell的所有
$ make debug: id: 4 debug: id: 5
符号(在$
中转义为$$
)都需要小心转义。它们都应在Makefile
中以\$$
的形式出现。在您的代码中,Makefile
缺少此代码。此问题已固定为$$(echo
。 - 外部双引号中的所有双引号都需要小心地转义为
\$$(echo
,因此\"
应该写为cut -d" "
。
Susam Pal的答案说明了为什么使用双引号是错误的。使用建议的两种解决方案中的一种。
然后,您还可以考虑其他一些方面:
-
您正在没有真正意义的配方中使用
$(shell...)
make函数:配方已经是shell脚本。而且您也不需要call
函数。一个更简单的Makefile可能是:IDS = 4 5 define MY_FUNC max=2; \ counter=1; \ while [ $$counter -le $$max ]; do \ id=$$(echo $(IDS) | cut -d" " -f $${counter}); \ counter=$$((counter+1)); \ done endef .PHONY: all all: @$(MY_FUNC)
-
由于您的食谱没有副作用,因此它不是很有用。但是我想您已经知道了,这只是一个例子。
-
硬连接食谱中
IDS
(max=2
)中的项目数不是最佳的。如果您使用的是GNU make,则可以使用其words
函数:max=$(words $(IDS)); \
-
有许多更简单的方法可以用Shell实现您想要的。假设您只想打印id值:
for id in $(IDS); do; \ echo $$id; \ done
更容易。但是我想您已经知道了,这只是一个例子。
-
如果要使用
call
函数,可以向其传递一个参数(当前ID)并使用foreach
make函数进行迭代,而不是使用shell循环:IDS = 4 5 define MY_FUNC echo $(1) endef .PHONY: all all: $(foreach id,$(IDS),@$(call MY_FUNC,$(id)))
请注意
MY_FUNC
的最后一行为空。需要获得真正的多行配方。单行配方的替代解决方案:IDS = 4 5 MY_FUNC = echo $(1) .PHONY: all all: @$(foreach id,$(call MY_FUNC,$(id));)
-
GNU make提供了许多方便的功能,并且具有许多非常有用的功能。对于您的情况(并假设您只想在标准输出上打印每个
id
),则可以使用patsubst
创建一个伪造目标列表,IDS
中每个单词一个,并编写一个静态目标他们所有的模式规则:IDS = 4 5 ALLS = $(patsubst %,all-%,$(IDS)) .PHONY: all $(ALLS) all: $(ALLS) $(ALLS): all-%: @echo $*
最后一种解决方案的一个优点是,您的ID可以分布到许多独立的规则(
all-X
)中,并且如果允许的话,它们的配方可以通过make并行运行({{1} }),而使用单规则解决方案则必须按顺序运行。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。