3

why doesn't this simple recipe work ?

.PHONY: test
test:
    foo := $(shell ls | grep makefile) ;\
    echo $(foo)

results in

$> make test
makefile:65: warning: undefined variable 'foo'
foo := makefile ;\
echo 
/bin/sh: 1: foo: not found

So, as far as I understand, the variable foo is well set to value makefile but it cannot be used afterwards ? However, it is a single line command, executed in the same shell ?

However, this works

@$(eval export foo := $(shell ls | grep makefile)) \
echo $(foo)

So I guess that the variable in the first example is not accessible because the assignment is not evaluated yet at the time we try the echo ?

And if I dig a little further, how to do this work

.PHONY: test
test:
    @$(eval export files = $(shell ls))
    for f in $(files) ; do \
        t = $(ls | grep $$f) ; \
        echo $$t;\
    done
1
  • The term receipe is not a common practice together with make. Are you talking about a command from a rule?
    – schily
    Commented Aug 24, 2020 at 13:38

2 Answers 2

2

I looked at your loop... quoted here:

.PHONY: test
test:
    @$(eval export files = $(shell ls))
    for f in $(files) ; do \
        t = $(ls | grep $$f) ; \
        echo $$t;\
    done

So... $(eval ... ) runs a command in make.

$(shell ls) runs command ls in the shell, and substitutes its output.

The command run by the $(eval ... ) is thus something like export files = file file2 makefile source.c. This command makes a make variable called files and exports it to child makes. Thus, the export probably isn't needed.

The entire $(eval ... ) could probably be replaced with files = $(wildcard *) And it could probably use := and be placed outside of a rule.

The for loop, four lines, is run in the shell. The first thing that is done, the make variables and functions are substituted. The one that is weird is $(ls | grep $$f). Since ls is not a make function, this will try to expand a variable, which isn't defined. This is an empty string. If this was meant to be the shell's $(...) operator, you need to double the $. $$ is expanded to $. $(files) is expanded based on the eval.

This becomes (using my previous example):

for f in file file2 makefile source.c ; do
    t =
    echo $t;
done

At first glance, this might echo four blank lines, but no. The command t = actually runs the program t and passes the equal sign as an argument. t probably doesn't exist. Thus, we get four errors that t isn't a valid program, each followed by a blank line (unless t is elsewhere defined).

Something closer to what you wanted might be:

files := $(wildcard *)
.PHONY: test
test:
    for f in $(files) ; do \
        t=$$(ls | grep $$f) ; \
        echo $$t ; \
    done

This will output:

file file2
file2
makefile
source.c

Note that the first line listed two files, as the both include "file" in the name. If that isn't what you want, you might consider:

files := $(wildcard *)
.PHONY: test
test:
    for f in $(files) ; do \
        echo $$f ; \
    done

or even (may be GNU make specific):

files := $(wildcard *)
.PHONY: test
test:
    $(foreach f, $(files), echo $f ; )
3
  • awesome, thanks for the very pedagogic answer. It is my very first try with makefiles and I'm trying to understand them. You surely helped me !
    – GuillaumeA
    Commented Aug 20, 2020 at 12:14
  • The solution you propose is one I tried, however, I got errors like : "/bin/sh: 2: t: not found" for each file
    – GuillaumeA
    Commented Aug 20, 2020 at 12:53
  • 1
    /bin sh: 2: t: not found is a result of trying to execute the command t. In make, assignment is <variable> <whitespace> = <whitespace> <value>. in sh, assignment does NOT allow whitespace. <variable>=<value> In fact in sh, you can write: A=1 B=2 myprogram and it will set two the variable for the execution of a program, but not retain them beyond that. There is also a shell option that will allow you to write it as myprogram A=1 B=2 -- but I've never heard of anyone seriously using that option.
    – David G.
    Commented Aug 20, 2020 at 15:22
1

Inside a make target recipe, commands are processed differently (by spawning a shell) than logic outside a recipe.

You can either move the variable outside the recipe:

.PHONY: test
foo := $(shell ls | grep makefile)
test:
    echo $(foo)

or, courtesy of this question, use an eval:

.PHONY: test
test:
    $(eval foo=$(shell ls | grep makefile))
    echo $(foo)

Both output the below:

echo makefile
makefile
1
  • I'm also not sure what you're trying with your for loop. It looks like you are trying to run ls in a very roundabout way. Also, beware consuming the output of ls. Commented Aug 19, 2020 at 23:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .