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/fiやfor/do/doneのような複数行シェル構造が可能になります。
Make変数のシェルへの受け渡し
export DATABASE_URL
export API_KEY
run:
./start-server.sh
exportはMake変数をレシピシェルの環境変数として利用可能にします。
クォーティングガイドライン
- シェルコマンドではMakeとシェルの両方の展開を防ぐためにシングルクォートを使用する
- シェル変数展開は必要だがMake展開は不要な場合にダブルクォートを使用する
- シェル変数には
$$、Make変数には$()を使用する - シェルでリテラル
$$が必要な場合は$$$$を使用する(まれ)
ユースケース
シェルエスケープの問題、複数行コマンドの失敗、MakeとShell間の変数展開の競合により予期しない動作をするMakefileレシピをデバッグする場合に使用します。