8

I'm reading a book about the make command and there is a paragraph:

If any of the prerequisites has an associated rule, make attempts to update those first. Next, the target file is considered. If any prerequisite is newer than the target, the target is remade by executing the commands. Each command line is passed to the shell and is executed in its own subshell.

Could you explain the concept of subshell used there and why is it necessary to use such a subshell?

xralf
  • 15,415

4 Answers4

17

make(1) itself does not know how to run shell commands. It could have been made to do so, but the Unix Way is to have well-separated concerns: make(1) knows how to build dependency graphs that determine what has to be made, and sh(1) knows how to run commands.

The point the author is trying to make there is that you must not write those command lines such that a later one depends on a former one, except through the filesystem. For example, this won't work:

sometarget: some.x list.y of-dependencies.z
    foo=`run-some-command-here`
    run-some-other-command $$foo

If this were a two-line shell script, the first command's output would be passed as an argument to the second command. But since each of these commands gets run in a separate sub-shell, the $foo variable's value gets lost after the first sub-shell exists, so there is nothing to pass to the first.

One way around this, as hinted above, is to use the filesystem:

TMPDIR=/tmp
TMPFILE=$(TMPDIR)/example.tmp
sometarget: some.x list.y of-dependencies.z
    run-some-command-here > $(TMPFILE)
    run-some-other-command `cat $(TMPFILE)`

That stores the output of the first command in a persistent location so the second command can load the value up.

Another thing that trips make(1) newbies up sometimes is that constructs that are usually broken up into multiple lines for readability in a shell script have to be written on a single line or wrapped up into an external shell script when you do them in a Makefile. Loops are a good example; this doesn't work:

someutilitytarget:
    for f in *.c
    do
        munch-on $f
    done

You have to use semicolons to get everything onto a single line instead:

someutilitytarget:
    for f in *.c ; do munch-on $f ; done

For myself, whenever doing that gives me a command line longer than 80 characters or so, I move it into an external shell script so it's readable.

Warren Young
  • 72,032
  • 1
    ++ for nice examples – ktf Oct 31 '11 at 20:07
  • 1
    " It could have been made to do so" It could also have been made to pass all the commands to a single shell; do you have an explanation why this was not done? – Random832 Nov 02 '11 at 13:56
  • 1
    make(1) dates from 1977. Command line length limits were short and disks — hence temporary script files — were slow. – Warren Young Nov 02 '11 at 15:19
3

Make isn't itself a shell, so it needs to execute them in a shell. The book is calling them "subshells" because make is itself (most likely) running in a shell. What the book is actually trying to get across there is that each command is given its own shell, so if, e.g. you want to export a variable on one line and use it on the next (or another subsequent) line, it won't work (the export ends with the shell it's used in, and the next line is run in a separate shell).

Kevin
  • 40,767
2

Old question but you could also run the command in the shell and get the output

http://electric-cloud.com/blog/2009/03/makefile-performance-shell/

OUTPUT = $(shell pwd)

some-target:
    @echo $(OUTPUT)
1

What's meant is that every command line within a rule will be called in its own shell process. Reason is that make executes one command line and then wants to check it before calling the next command line.

One could think of other ways to call the commands but this is how make was designed back then.

Of course make could just fork()/exec() the commands but using a shell subprocess allows the user to use shell features, too.

ktf
  • 2,717