0

Indirect variable setting in bash can be done like so:

#!/bin/bash
foo=bar
ind=foo
echo ${!ind}   # prints `bar'

Trying to run the same as a (GNU) Makefile recipe

# Makefile
test:
    foo=bar
    ind=foo
    echo $$\{!ind\}
    echo $${!ind}

both with and without escaping the {} characters fails with the following message:

foo=bar
ind=foo
echo $\{!ind\}
${!ind}
echo ${!ind}
/bin/sh: 1: Bad substitution
/tmp/Makefile:2: recipe for target 'test' failed
make: *** [test] Error 2

The problem is probably some missing/wrong escaping of Makefile's special characters.

Or it could be the order/timing of variable expansion, see make's secondary expansion.

Any ideas?

If it matters, this is bash 4.4.12(1)-release and make GNU Make 4.1

2 Answers2

1

You guys are just incredible. Less than ten minutes after I posted my question, you led me to the correct solution, which is:

# Makefile
SHELL=/bin/bash
test:
    foo=bar; \
    ind=foo; \
    echo $${!ind}

Thank you steeldriver, thank you choroba.

1

There are two problems here:

  1. The default shell used for recipes is a sh, so you need to use SHELL=/bin/bash for the recipe to even understand syntax like ${!ind}
  2. Each line in the recipe is executed by a separate shell, so if you define a variable in a recipe it is lost for the next line of the same recipe. This is solved by a .ONESHELL special target

So the working example looks like this:

# Makefile
.ONESHELL:
SHELL=/bin/bash
test:
    foo=bar
    ind=foo
    echo $$\{!ind\}
    echo $${!ind}

For a more detailed (and official) explanation, go to https://www.gnu.org/software/make/manual/make.html#Execution

White Owl
  • 5,129