How to use shell commands in Makefile

Adam Matan picture Adam Matan · Apr 5, 2012 · Viewed 194.2k times · Source

I'm trying to use the result of ls in other commands (e.g. echo, rsync):

all:
    <Building, creating some .tgz files - removed for clarity>
    FILES = $(shell ls)
    echo $(FILES)

But I get:

make
FILES = Makefile file1.tgz file2.tgz file3.tgz
make: FILES: No such file or directory
make: *** [all] Error 1

I've tried using echo $$FILES, echo ${FILES} and echo $(FILES), with no luck.

Answer

torek picture torek · Apr 5, 2012

With:

FILES = $(shell ls)

indented underneath all like that, it's a build command. So this expands $(shell ls), then tries to run the command FILES ....

If FILES is supposed to be a make variable, these variables need to be assigned outside the recipe portion, e.g.:

FILES = $(shell ls)
all:
        echo $(FILES)

Of course, that means that FILES will be set to "output from ls" before running any of the commands that create the .tgz files. (Though as Kaz notes the variable is re-expanded each time, so eventually it will include the .tgz files; some make variants have FILES := ... to avoid this, for efficiency and/or correctness.1)

If FILES is supposed to be a shell variable, you can set it but you need to do it in shell-ese, with no spaces, and quoted:

all:
        FILES="$(shell ls)"

However, each line is run by a separate shell, so this variable will not survive to the next line, so you must then use it immediately:

        FILES="$(shell ls)"; echo $$FILES

This is all a bit silly since the shell will expand * (and other shell glob expressions) for you in the first place, so you can just:

        echo *

as your shell command.

Finally, as a general rule (not really applicable to this example): as esperanto notes in comments, using the output from ls is not completely reliable (some details depend on file names and sometimes even the version of ls; some versions of ls attempt to sanitize output in some cases). Thus, as l0b0 and idelic note, if you're using GNU make you can use $(wildcard) and $(subst ...) to accomplish everything inside make itself (avoiding any "weird characters in file name" issues). (In sh scripts, including the recipe portion of makefiles, another method is to use find ... -print0 | xargs -0 to avoid tripping over blanks, newlines, control characters, and so on.)


1The GNU Make documentation notes further that POSIX make added ::= assignment in 2012. I have not found a quick reference link to a POSIX document for this, nor do I know off-hand which make variants support ::= assignment, although GNU make does today, with the same meaning as :=, i.e., do the assignment right now with expansion.

Note that VAR := $(shell command args...) can also be spelled VAR != command args... in several make variants, including all modern GNU and BSD variants as far as I know. These other variants do not have $(shell) so using VAR != command args... is superior in both being shorter and working in more variants.