Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use shell commands in Makefile

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.

like image 604
Adam Matan Avatar asked Sep 29 '22 17:09

Adam Matan


People also ask

Can you use bash commands in a makefile?

So put SHELL := /bin/bash at the top of your makefile, and you should be good to go. See "Target-specific Variable Values" in the documentation for more details. That line can go anywhere in the Makefile, it doesn't have to be immediately before the target.

How do I run a command in makefile?

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 ...)

What shell is used in makefile?

$(shell) $(shell) is a special function in gmake that runs an external command and captures the output for use in the makefile.


1 Answers

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.

like image 215
torek Avatar answered Oct 18 '22 23:10

torek