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