0

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 Aug 24 '20 at 13:38
1

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 Aug 20 '20 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 Aug 20 '20 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. Aug 20 '20 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. – Jordan Deyton Aug 19 '20 at 23:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.