在Makefile shell中使用循环变量

问题描述 投票:0回答:2

我正在尝试使用以下脚本在我的Makefile中创建一个docker pull:

ARCH=amd64
IMAGE=k8s.gcr.io/debian-base
TAG=v1.0.0

all:
        docker pull $(shell echo $(ARCH) | sed -e "s~[^ ]*~$(IMAGE)\-&:$(TAG)~g");

当我运行make命令时,我可以拉出图像:

# make
docker pull k8s.gcr.io/debian-base-amd64:v1.0.0;
v1.0.0: Pulling from debian-base-amd64
39fafc05754f: Pull complete
Digest: sha256:5f25d97ece9076612b64bb551e12f1e39a520176b684e2d663ce1bd53c5d0618
Status: Downloaded newer image for k8s.gcr.io/debian-base-amd64:v1.0.0

我想对此做一点说明。我想为具有多个标签的多个拱形图像创建清单,例如:

ARCH=amd64 ppc64le
IMAGE=k8s.gcr.io/debian-base
export DOCKER_CLI_EXPERIMENTAL=enabled

all:
        for tag in v1.0.0 v1.0.1 ; do \
                docker manifest create $(IMAGE):$$tag $(shell echo $(ARCH) | sed -e "s~[^ ]*~$(IMAGE)\-&:$$tag~g"); \
        done

不幸的是,失败并显示以下错误:

# make
for tag in v1.0.0 v1.0.1 ; do \
    docker manifest create k8s.gcr.io/debian-base:$tag k8s.gcr.io/debian-base-amd64: k8s.gcr.io/debian-base-ppc64le:; \
done
invalid reference format
invalid reference format
make: *** [Makefile:6: all] Error 1

如果您在上方看到标签字段,则无法正确填充。在Makefile中可以这样做吗?我应该以其他方式或在此处进行一些修改吗? TIA。

makefile
2个回答
0
投票

如果您在第二个Makefile前面添加\,则[[您的$$tag应该可以正常工作。这是因为$(shell中的内容两次传递到外壳程序(一次$(shell调用一次,docker manifest ...调用一次)。

ARCH=amd64 ppc64le
IMAGE=k8s.gcr.io/debian-base
export DOCKER_CLI_EXPERIMENTAL=enabled

all:
    for tag in v1.0.0 v1.0.1 ; do \
        docker manifest create $(IMAGE):$$tag $(shell echo $(ARCH) | sed -e "s~[^ ]*~$(IMAGE)\-&:\$$tag~g"); \
    done

也许让make建立名称会更简单?

ARCH=amd64 ppc64le
IMAGE=k8s.gcr.io/debian-base

all:
    for tag in v1.0.0 v1.0.1 ; do \
        docker manifest create $(IMAGE):$$tag $(foreach a,$(ARCH),$(IMAGE)-$a:$$tag); \
    done

0
投票

您似乎有执行顺序问题。您希望make会在每次在规则配方中执行循环达到$(shell)引用按词法出现的点时展开$(shell)函数,但是如果您考虑一下,那将无法正常工作。 make将配方中的每个命令移交给外壳程序以使其执行,这时它已由make掌握。因此,在将命令传递给外壳程序之前,它必须(并且确实)扩展make函数调用。

为此,配方中的整个for循环是并且必须是单个命令(这是尾随反斜杠所针对的),因此$(shell)函数在设置循环变量之前就已展开。在该命令中,$tag扩展为空。

除了执行顺序外,$(shell)函数中的代码执行在其自己的外壳中进行,在该外壳中无论如何都不会设置$tag变量。

有几种不错的选择:

替代1:摆脱循环

您有多个要构建的东西。大!就是在make的操舵室中。让make为您提供帮助。例如:

# Note: make syntax permits whitespace around the "=" in variable assignments
ARCH = amd64 ppc64le
IMAGE = k8s.gcr.io/debian-base
export DOCKER_CLI_EXPERIMENTAL = enabled

TAGS = v1.0.0 v1.0.1

all: $(TAGS)

$(TAGS):
        docker manifest create $(IMAGE):$@ $(shell echo $(ARCH) | sed -e "s~[^ ]*~$(IMAGE)\-&:$@~g")

.PHONY: $(TAGS)

这使用了一个事实,即将具有多个目标的规则分别应用于构建每个目标。它不需要显式的迭代变量,因为在任何make配方中,自动变量$@都会扩展为当前正在构建的目标的名称。

此处的$(shell)函数调用在命令运行之前仍会扩展,但这不是问题,因为其中的make变量(包括$@首先被扩展。

替代2:使用shell命令替换代替make$(shell)函数

老实说,除非在[[有意地中使用执行属性的顺序,否则在配方中使用$(shell)实在太钝了,因为建模make函数的shell功能几乎总是比较简单”和更合适的选择。例如:

ARCH = amd64 ppc64le IMAGE = k8s.gcr.io/debian-base export DOCKER_CLI_EXPERIMENTAL = enabled all: for tag in v1.0.0 v1.0.1 ; do \ docker manifest create $(IMAGE):$$tag $$(echo $(ARCH) | sed -e "s~[^ ]*~$(IMAGE)\-&:$$tag~g"); \ done
展开后,在这种情况下,make传递给外壳的命令等效于

*

for tag in v1.0.0 v1.0.1 ; do \ docker manifest create k8s.gcr.io/debian-base:$tag $(echo amd64 ppc64le | sed -e "s~[^ ]*~k8s.gcr.io/debian-base\-&:$tag~g"); \ done
在外壳程序代码中,构造$(any command)被称为“命令替换”。执行括号内的命令,并捕获并替换其标准输出。使用它就不会对执行顺序产生任何疑问。

替代3:以上两个都

没有什么要说的了,它像这样出来:

ARCH = amd64 ppc64le IMAGE = k8s.gcr.io/debian-base export DOCKER_CLI_EXPERIMENTAL = enabled TAGS = v1.0.0 v1.0.1 all: $(TAGS) $(TAGS): docker manifest create $(IMAGE):$@ $$(echo $(ARCH) | sed -e "s~[^ ]*~$(IMAGE)\-&:$@~g") .PHONY: $(TAGS)

而且,我想这是到目前为止我最喜欢的替代方法。我一般不关心make函数,因为它们是GNU扩展,并且它们中的许多趋向于产生模糊的编程范例。也许“编程范例”的措辞会更好,因为我通常不认为将makefile写为“编程” 

本身]。

替代4:还改进了命令替换命令

sed只是将一个字符串追加到其他多个字符串而已,这有点矫kill过正,其表达语法有点奥秘。我实际上非常喜欢sed,但是对于在makefile中使用,我非常重视清晰度。出于这个原因,我可能会自己做这些事情:

ARCH = amd64 ppc64le IMAGE = k8s.gcr.io/debian-base export DOCKER_CLI_EXPERIMENTAL = enabled TAGS = v1.0.0 v1.0.1 all: $(TAGS) $(TAGS): docker manifest create $(IMAGE):$@ $$(for arch in $(ARCH); do echo "$(IMAGE)-$$arch:$@"; done) .PHONY: $(TAGS)


*

是等效的,但不完全相同,因为make将执行线连接,而不是将其留给外壳程序。我提供的是行拆分版本,以便于阅读。
© www.soinside.com 2019 - 2024. All rights reserved.