How recipes run in the shell

Make · Practical · 12 min

Most confusing Makefile bugs are really about how recipe lines are handed to the shell.

make passes each recipe line to a separate /bin/sh -c invocation. Shell state therefore doesn't carry over between lines: a cd dir on one line has no effect on the next, and neither does a shell variable assignment. Join dependent commands on one line with &&, or continue a line with a trailing backslash.

Line prefixes:

  • @ don't echo the command before running it.
  • - ignore a failure of this command and keep going.
  • + run even under make -n (used for recursive make calls).

Shell syntax and make syntax mix in recipes. make expands $(VAR) first, then the shell sees the result. So shell variables and command substitution need doubled dollars: $$f, $$(date).

Useful settings:

  • SHELL := /bin/bash and .SHELLFLAGS := -eu -o pipefail -c to use bash with strict mode.
  • .ONESHELL: runs the whole recipe in one shell, so cd and variables persist (only the first line's @ and - apply).
  • .DELETE_ON_ERROR: deletes a target whose recipe failed. Without it, a half-written output file is newer than its inputs and the next make happily treats it as up to date.

Recipes run sequentially within a rule and stop at the first non-zero exit status, unless the line has -.

Example

SHELL := /bin/bash
.SHELLFLAGS := -eu -o pipefail -c
.DELETE_ON_ERROR:

wrong:
	cd /tmp
	pwd                       # still the project directory!

right:
	cd /tmp && pwd            # /tmp

loop:
	@for f in a b c; do \
	  echo "item $$f"; \
	done                      # item a / item b / item c

tolerant:
	-rm nonexistent-file      # error printed, but make continues
	@echo "stamp: $$(date +%F)"

report.txt: data.txt
	grep -c ok data.txt | tee $@    # if grep fails, report.txt is deleted

Remember

  • Every recipe line runs in a new shell: use && or backslashes to join
  • @ silences, - ignores errors; $$ passes a literal $ to the shell
  • .DELETE_ON_ERROR removes half-written targets after a failure

Watch out for

  • Splitting cd build and make on two lines and running the second command in the wrong directory.

Check yourself

  1. A recipe has two lines: export X=1, then echo $$X. What is printed?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.gnu.org/software/make/manual/make.html#Execution