Makeにおけるシェルエスケープと複数行コマンド

Makefileでのシェルエスケープの扱い:ドルサイン、単一行実行、.ONESHELL、バックスラッシュによる複数行コマンド、MakeとShell間の変数の受け渡し。

Advanced Patterns

詳細な説明

Makefileにおけるシェルエスケープ

Makefileの最もトリッキーな側面の1つは、Makeの変数展開とシェルの解釈の相互作用です。エスケープルールを理解することで、微妙なバグを防止できます。

ドルサイン問題

# 間違い:Makeが$(を変数展開として解釈する
bad:
	echo $(HOME)

# 正しい:シェル変数にはダブルドル
good:
	echo $$HOME

# 正しい:シェルコマンド置換
also-good:
	echo $$(date +%Y-%m-%d)

Makeはレシピをシェルに渡す前に$を処理します。単一の$はMake変数展開をトリガーし、$$はシェル用のリテラル$を生成します。つまり$$(command)はシェルでは$(command)になります。

各行は別々のシェル

# 間違い:cdは最初の行のシェルにのみ影響する
bad-cd:
	cd src
	gcc -o main main.c

# 正しい:コマンドをチェーンする
good-cd:
	cd src && gcc -o main main.c

# 正しい:セミコロンを使用
also-good-cd:
	cd src; gcc -o main main.c

デフォルトでは、レシピの各行は別々のシェルプロセスで実行されます。一行でディレクトリを変更しても次の行には影響しません。

複数行コマンド

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

バックスラッシュ継続は行を単一のシェルコマンドに結合します。各継続行はタブで始まる必要があります。

.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はレシピのすべての行を単一のシェル呼び出しで実行させます。これにより、バックスラッシュエスケープなしでif/then/fifor/do/doneのような複数行シェル構造が可能になります。

Make変数のシェルへの受け渡し

export DATABASE_URL
export API_KEY

run:
	./start-server.sh

exportはMake変数をレシピシェルの環境変数として利用可能にします。

クォーティングガイドライン

  • シェルコマンドではMakeとシェルの両方の展開を防ぐためにシングルクォートを使用する
  • シェル変数展開は必要だがMake展開は不要な場合にダブルクォートを使用する
  • シェル変数には$$、Make変数には$()を使用する
  • シェルでリテラル$$が必要な場合は$$$$を使用する(まれ)

ユースケース

シェルエスケープの問題、複数行コマンドの失敗、MakeとShell間の変数展開の競合により予期しない動作をするMakefileレシピをデバッグする場合に使用します。

試してみる — Makefile Generator

フルツールを開く