148

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.

kyb
  • 7,233
  • 5
  • 52
  • 105
Adam Matan
  • 128,757
  • 147
  • 397
  • 562

2 Answers2

233

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.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks. I want to use an elaborate command (`ls` with `sed` and cut, for example) and then use the results in rsync and other commands. Do I have to repeat the lengthy command over and over? Can't I store the results in an internal Make variable? – Adam Matan Apr 05 '12 at 07:42
  • 1
    Gnu make might have a way to do that, but I've never used it, and all the horribly complicated makefiles we use just use shell variables and giant one-liner shell commands built with "; \" at the end of each line as needed. (can't get the code encoding to work with the backslash sequence here, hmm) – torek Apr 05 '12 at 07:44
  • Also, [instead of `ls`](http://mywiki.wooledge.org/ParsingLs) you'll want to use the [`wildcard` make builtin](https://www.gnu.org/software/make/manual/make.html#Wildcard-Function). – l0b0 Apr 05 '12 at 11:09
  • 1
    Perhaps something like: `FILE = $(shell ls *.c | sed -e "s^fun^bun^g")` – William Morris Apr 05 '12 at 15:17
  • 4
    @William: `make` can do that without using the shell: `FILE = $(subst fun,bun,$(wildcard *.c))`. – Idelic Apr 05 '12 at 18:18
  • Thanks, that looks much better. I'll have to re-read the manual - it's been a long time. Up until a few days ago I was still happily using suffix rules (which have served me well for a few decades) without knowing how out of date I was ;-) – William Morris Apr 05 '12 at 23:43
  • 1
    I would like to point out that although in this case it does not seem to be very important, you should not automatically parse ls's output. ls is meant to show information to human beings, not to be chained in scripts. More info here: http://mywiki.wooledge.org/ParsingLs Probably, in case 'make' does not offer you appropiate wildcard expansion, "find" is better than "ls". – Raúl Salinas-Monteagudo Nov 04 '13 at 16:30
  • This is very helpful! Thx! I just want to add one minor point, but it took me some minutes to figure out: In FILE = $(shell ls ....), make sure there is no space between ( and shell, meaning this syntax would not work: FILE = $( shell ls ....), but FILE = $(shell ls ....) would be OK. [This is different from bash where the presence of space within ( ) does not make a difference] – HAltos Mar 25 '22 at 21:47
  • `::=` is for some reason not documented on pubs.opengroup.org, but after digging through GNU Make's Git history I found the commit where they added the `::=` syntax: https://git.savannah.gnu.org/cgit/make.git/commit/?id=ef6461611b8fb7cd4a92168d4c319153462afb5a - the NEWS diff links to a Posix working group page where they have indeed discussed about and added the operator. – Tachi Aug 02 '22 at 17:37
  • @Tachi: nice find! I didn't know about the subtle "re-evaluate $(V)" that's remarked on in that discussion, nor about the ":= as conditional assignment" in some variants of `make`. – torek Aug 03 '22 at 10:41
71

Also, in addition to torek's answer: one thing that stands out is that you're using a lazily-evaluated macro assignment.

If you're on GNU Make, use the := assignment instead of =. This assignment causes the right hand side to be expanded immediately, and stored in the left hand variable.

FILES := $(shell ...)  # expand now; FILES is now the result of $(shell ...)

FILES = $(shell ...)   # expand later: FILES holds the syntax $(shell ...)

If you use the = assignment, it means that every single occurrence of $(FILES) will be expanding the $(shell ...) syntax and thus invoking the shell command. This will make your make job run slower, or even have some surprising consequences.

Kaz
  • 55,781
  • 9
  • 100
  • 149
  • 1
    Now that we have the list how do we iterate over each item in the list and execute a command on it? Such as build or test? – fIwJlxSzApHEZIl Oct 12 '16 at 19:56
  • 5
    @anon58192932 That specific iteration, to execute a command, is usually done in a shell syntax fragment in a build recipe, so it occurs in the shell, rather than in make: `for x in $(FILES); do command $$x; done`. Note the doubled up `$$` which passes a single `$` to the shell. Also, shell fragments are one-liners; to write multi-line shell code, you use backslash continuation that is processed by `make` itself and folded into one line. Which means shell-command-separating semicolons are mandatory. – Kaz Oct 12 '16 at 21:11