Shell Escaping and Multi-line Commands in Make

Handle shell escaping in Makefiles: dollar signs, single-line execution, .ONESHELL, multi-line commands with backslash, and passing variables between Make and shell.

Advanced Patterns

Detailed Explanation

Shell Escaping in Makefiles

One of the trickiest aspects of Makefiles is the interaction between Make's variable expansion and shell interpretation. Understanding escaping rules prevents subtle bugs.

The Dollar Sign Problem

# WRONG: Make interprets $( as variable expansion
bad:
	echo $(HOME)

# RIGHT: Double dollar for shell variables
good:
	echo $$HOME

# RIGHT: Shell command substitution
also-good:
	echo $$(date +%Y-%m-%d)

Make processes $ before passing the recipe to the shell. A single $ triggers Make variable expansion; $$ produces a literal $ for the shell. This means $$(command) becomes $(command) in the shell.

Each Line is a Separate Shell

# WRONG: cd only affects the first line's shell
bad-cd:
	cd src
	gcc -o main main.c

# RIGHT: Chain commands
good-cd:
	cd src && gcc -o main main.c

# RIGHT: Use semicolons
also-good-cd:
	cd src; gcc -o main main.c

By default, each line in a recipe runs in a separate shell process. Changing directory on one line does not affect the next.

Multi-line Commands

deploy:
	rsync -avz \
	  --exclude='.git' \
	  --exclude='node_modules' \
	  ./dist/ \
	  user@server:/var/www/app/

Backslash continuation joins lines into a single shell command. Each continuation line must start with a tab.

.ONESHELL

.ONESHELL:
SHELL := /bin/bash

setup-env:
	set -euo pipefail
	cd /tmp
	if [ ! -d myrepo ]; then
	  git clone https://github.com/user/repo.git myrepo
	fi
	cd myrepo
	git pull

.ONESHELL makes all lines in a recipe execute in a single shell invocation. This allows multiline shell constructs like if/then/fi and for/do/done without backslash escaping.

Passing Make Variables to Shell

export DATABASE_URL
export API_KEY

run:
	./start-server.sh

export makes Make variables available as environment variables in recipe shells.

Quoting Guidelines

  • Use single quotes in shell commands to prevent both Make and shell expansion
  • Use double quotes when you need shell variable expansion but not Make expansion
  • Use $$ for shell variables, $() for Make variables
  • Use $$$$ when you need a literal $$ in the shell (rare)

Use Case

Debugging Makefile recipes that behave unexpectedly due to shell escaping issues, multi-line command failures, or variable expansion conflicts between Make and the shell.

Try It — Makefile Generator

Open full tool