1

To illustrate the issue, I created a makefile which first defines a list of cars and then for each car, a list of colours:

CARS = Ford Ferrari Mercedes Toyota
Ford_COLOURS = black blue red white
Ferrari_COLOURS = red
Mercedes_COLOURS = black blue silver
Toyota_COLOURS = blue red white

Then, I'd like to loop through all colours of every car in one of my make targets:

.PHONY: list
list:
    for car in ${CARS} ; do \
        car_colours=$${car}_COLOURS ; \
        echo $${car_colours} = "$${!car_colours}" ; \
        eval colours="$${!car_colours}" ; \
        for colour in $${colours} ; do \
            echo $${colour} ; \
        done ; \
    done

As you can see I tried to synthesise the ideas from this question about generating dynamic variable names in bash and some answers to this question about how to evaluate (expand) dynamic names in bash.

Unfortunately I get the following output:

Ford_COLOURS = 
Ferrari_COLOURS = 
Mercedes_COLOURS = 
Toyota_COLOURS =

while I was hoping to get something like this:

Ford_COLOURS = black blue red white
black 
blue 
red 
white
Ferrari_COLOURS = red
red
Mercedes_COLOURS = black blue silver
black 
blue 
silver
Toyota_COLOURS = blue red white
blue 
red 
white

How would I fix that?

Community
  • 1
  • 1
FriendFX
  • 2,929
  • 1
  • 34
  • 63

2 Answers2

3

Following the idea in my comment to Jonathan's answer, I found a way that involves starting another make process from within the fragment which executes the inner for loop:

.PHONY: list
list:
    for car in ${CARS} ; do \
        car_colours_var=$${car}_COLOURS ; \
        ${MAKE} CAR_COLOURS_VAR=$${car_colours_var} single_car ; \
    done

.PHONY: single_car
single_car:
    for colour in ${${CAR_COLOURS_VAR}} ; do \
        echo $${colour} ; \
    done

The key is the intermediary CAR_COLOURS_VAR which is passed to the child make process as a parameter, which can subsequently be expanded by that process via the ${${CAR_COLOURS_VAR}} syntax.

FriendFX
  • 2,929
  • 1
  • 34
  • 63
1

The trouble is that the shell doesn't know about the lists of 'make_COLOURS'. You can use:

CARS = Ford Ferrari Mercedes Toyota
Ford_COLOURS = black blue red white
Ferrari_COLOURS = red
Mercedes_COLOURS = black blue silver
Toyota_COLOURS = blue red white

.PHONY: list
list:
        Ford_COLOURS="${Ford_COLOURS}"; \
        Ferrari_COLOURS="${Ferrari_COLOURS}"; \
        Mercedes_COLOURS="${Mercedes_COLOURS}"; \
        Toyota_COLOURS="${Toyota_COLOURS}"; \
        for car in ${CARS} ; do \
            car_colours=$${car}_COLOURS ; \
            echo $${car_colours} = "$${!car_colours}" ; \
            colours="$${!car_colours}" ; \
            for colour in $${colours} ; do \
                echo $${colour} ; \
            done ; \
        done

Note that the eval in eval colours="$${!car_colours}" in the original has lost the eval. When it is present, you get errors such as /bin/sh: blue: command not found because the eval has colours=black blue red white and tries to execute blue with the environment variable colours set to black.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Thanks for your answer, unfortunately it pretty much defeats the purpose of the `CARS` list because it'd need to be repeated within the loop. I see the `eval` in my code is probably superfluous, but the error you're talking about should have been prevented by the quotes around `$${!car_colours}`, or am I missing something? – FriendFX Sep 15 '15 at 04:58
  • 1
    You're missing something; the eval effectively removes the quotes. – Jonathan Leffler Sep 15 '15 at 04:59
  • I'm sorry that you can't do what you want, but at least you know why you got the result you did (I got the same result too). Sometimes, life is a bear. Are you sure you wouldn't be better of with a real shell script rather than a fragment in a recipe? – Jonathan Leffler Sep 15 '15 at 05:01
  • It is definitely good to know that the expansion of variables defined within the makefile aren't available within the fragments, which probably also means that they are expanded only once; before the fragment is executed. I like the makefile to remain self-contained - maybe I'll create another target that executes the inner loop for a single `car` which is passed in by the `list` target which calls `${MAKE}` with an option. If that is even possible. – FriendFX Sep 15 '15 at 05:09