Martin Melka
Martin Melka

Reputation: 7799

Run deferred shell command in Make

In my Makefile I want to execute multiple steps as a part of a single target. Those steps should be done sequentially, because they depend on one another. This is the simplified case:

target:
    git archive --remote=some-remote master --format zip -o ./zipfile.zip
    echo "$(VARIABLE_IN_MAKE):$(shell unzip -z ./zipfilezip | tail -1)" > ./textfile
    $(shell cat ./textfile)

The problem here is that the shell command - $(shell unzip -z ./zipfilezip | tail -1) is executed as soon as the rule is "loaded", i.e. before the zipfile even exists. That returns errors. The cat command is also expanded too early.

What is the correct way to execute a subshell not before, but only after all the steps above have finished? Do I have to wrap all the commands in a bash -c call? Or chain them via &&?

Upvotes: 1

Views: 545

Answers (1)

Renaud Pacalet
Renaud Pacalet

Reputation: 29250

Get rid of these $(shell...). Each line of a make recipe is already a shell script:

target:
    git archive --remote=some-remote master --format zip -o ./zipfile.zip
    echo "$(VARIABLE_IN_MAKE):$$(unzip -z ./zipfilezip | tail -1)" > ./textfile
    cat ./textfile

Note that, in the second line, $$(unzip -z ./zipfilezip | tail -1) is expanded twice: a first time by make before passing the recipe to the shell, leading to $(unzip -z ./zipfilezip | tail -1), and a second time by the shell that treats it as a command substitution. This is why the $$ is needed: to escape the first expansion by make. If you were using $(unzip -z ./zipfilezip | tail -1) directly, make would expand it as the empty string (unless you have a make variable which name is unzip -z ./zipfilezip | tail -1, but this is very unlikely).

Upvotes: 3

Related Questions