この記事の狙い
Bash のパラメータ展開を“暗記”ではなくレシピとして使い回せるようにします。
既定値・代入・エラー・置換・切り出し・長さ・前後マッチ除去・大文字小文字変換を、安全なクォート方針とともに整理します。
前提と対象
- Bash 4+(一部は 4.4+ で便利オプションあり)
set -Eeuo pipefailを前提に、未定義と空文字の違いを明確に扱います。
TL;DR(最小実装・コピペ可)
#!/usr/bin/env bash
set -Eeuo pipefail
# 既定値(未定義/空 → default)
echo "${USER_NAME:-guest}"
# 未定義/空なら代入(副作用あり)
: "${TMPDIR:=/tmp}"
# 未定義/空ならエラー
: "${API_TOKEN:?set API_TOKEN first}"
# 置換(最初のみ / 全部)
s="a_b_c"; echo "${s/_/-}" # a-b_c
echo "${s//_/-}" # a-b-c
# 前後のパターン除去(最短/最長)
p="/var/log/app.log"
echo "${p#*/}" # 1回目の '*/' を前から除去 → var/log/app.log
echo "${p##*/}" # 最後の '*/' を前から除去 → app.log
echo "${p%/*}" # 末尾から最短 '/*' → /var/log
echo "${p%%/*}" # 末尾から最長 '/*' → (空:先頭から一番長くマッチ)
# 部分文字列(offset[:len])
t="abcdef"; echo "${t:2:3}" # cde
# 長さ(文字数 / 要素数)
echo "${#t}" # 6
arr=(a "" "b c"); echo "${#arr[@]}" # 3
# 大文字/小文字(Bash 4+)
name="BaSh"; echo "${name^^}" # BASH
echo "${name,,}" # bash
設計の要点(原則)
- クォートは基本、常に
"${…}":単語分割・グロブ暴発を防ぐ。 - 未定義 vs 空文字を区別:
-と:-、=と:=、?と:?を使い分ける。 - “最短/最長”の前後マッチ除去でパスや拡張子処理を
sedなしで書く。 - 配列は
"${arr[@]}"、長さは${#arr[@]}。[*]は結合用途のみ。
レシピ集
1) 既定値・代入・エラー
| 形 | 対象が未定義 | 対象が空文字 | 効果 |
|---|---|---|---|
${var-DEF} | DEF を展開 | 空文字のまま | 読み取りのみ |
${var:-DEF} | DEF | DEF | 空も既定値にする |
${var=DEF} | var=DEF | 何もしない | 代入あり |
${var:=DEF} | var=DEF | var=DEF | 空にも代入 |
${var?MSG} | エラー(MSG) | そのまま | 強制チェック |
${var:?MSG} | エラー(MSG) | エラー(MSG) | 空もエラー |
: "${OUT_DIR:=/tmp/out}" # 未定義/空なら /tmp/out を設定
: "${REQUIRED:?missing REQUIRED}" # 未定義/空なら即エラー終了
set -u有効時は 未定義参照で即エラーになるため、"${var-}"や上表の形を使って安全に扱います。
2) 置換(文字列中)
- 最初のみ:
${var/pat/repl} - すべて:
${var//pat/repl} - 先頭のみ:
${var/#pat/repl} - 末尾のみ:
${var/%pat/repl}
f="report_2025_final.txt"
echo "${f/_final/}" # report_2025.txt
echo "${f//_/ }" # report 2025 final.txt
echo "${f/#report_/}" # 2025_final.txt(先頭一致)
echo "${f/%.txt/.csv}" # report_2025_final.csv(末尾一致)
patは glob パターン(* ? [ .. ]など)。正規表現ではない点に注意。
3) 前後のパターン除去(パス処理の定番)
- 先頭側(左)から:
${var#pat}(最短) /${var##pat}(最長) - 末尾側(右)から:
${var%pat}(最短) /${var%%pat}(最長)
path="/opt/app/releases/2025-09-25/app.log"
# ファイル名
echo "${path##*/}" # app.log
# ディレクトリ名
echo "${path%/*}" # /opt/app/releases/2025-09-25
# 拡張子除去
file="a.tar.gz"; echo "${file%%.*}" # a
# 末尾のサフィックス削り
ver="v1.2.3-snapshot"; echo "${ver%-snapshot}" # v1.2.3
4) 部分文字列・長さ
s="abcdef"
echo "${s:2}" # cdef
echo "${s:2:2}" # cd
echo "${#s}" # 6
arr=(x "" "y z")
echo "${#arr[@]}" # 3 要素
echo "${#arr[2]}" # "y z" の文字数 → 3
5) 事前/事後に文字を足す(フォーマット)
n=7
printf -v padded '%03d' "$n" # 007
echo "$padded"
msg='path with spaces'
printf -v quoted '%q' "$msg" # シェル安全な表現(Bash 4.4+には ${var@Q} も)
echo "$quoted"
Bash 4.4+ なら ${var@Q} でクォート済み表現が得られます。
echo "${msg@Q}" # 'path with spaces'
6) 大文字・小文字(Bash 4+)
name="baSh"
echo "${name^}" # 先頭だけ大文字 → BaSh
echo "${name^^}" # 全部大文字 → BASH
echo "${name,}" # 先頭だけ小文字 → baSh
echo "${name,,}" # 全部小文字 → bash
7) 名前の列挙(補助)
# プレフィックスで変数名を列挙
DB_HOST=1 DB_PORT=2 APP_LOG=3
printf '%s\n' ${!DB_*} # DB_HOST DB_PORT
# 配列/連想配列のキー
declare -A m=([a]=1 [b]=2)
printf '%s\n' "${!m[@]}" # a b
安全なクォート戦略(必読)
- 文字列は常に
printf '%s\n' "${var}"/cmd "${arr[@]}"。 - 置換・前後除去の結果もクォートする(グロブ・空白で壊れない)。
- 結合したいときだけ
[*]を明示的に使い、IFSをローカルに設定して副作用を閉じ込める。
( IFS=,; printf '%s\n' "${arr[*]}" )
“コピペ可”テストブロック(最小)
#!/usr/bin/env bash
set -Eeuo pipefail
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
# 未定義/空の判定
unset X || true; Y=""
[[ "${X:-def}" == "def" ]] || { echo "X default"; exit 1; }
[[ "${Y:-def}" == "def" ]] || { echo "Y default"; exit 1; }
: "${Z:=zzz}"; [[ "$Z" == "zzz" ]] || { echo "assign"; exit 1; }
# 置換
s="a_b_c"
[[ "${s/_/-}" == "a-b_c" ]] || { echo "sub1"; exit 1; }
[[ "${s//_/-}" == "a-b-c" ]] || { echo "sub2"; exit 1; }
# 前後マッチ
p="/a/b/c.txt"
[[ "${p##*/}" == "c.txt" ]] || { echo "base"; exit 1; }
[[ "${p%/*}" == "/a/b" ]] || { echo "dir"; exit 1; }
# 部分文字列/長さ
t="abcdef"
[[ "${t:2:3}" == "cde" ]] || { echo "slice"; exit 1; }
[[ "${#t}" -eq 6 ]] || { echo "len"; exit 1; }
# 配列長と要素保持
arr=(a "" "b c")
[[ "${#arr[@]}" -eq 3 ]] || { echo "alen"; exit 1; }
out="$(for x in "${arr[@]}"; do printf "<%s>" "$x"; done)"
[[ "$out" == "<a><><b c>" ]] || { echo "aexp"; exit 1; }
echo "PASS"
失敗しやすい点(アンチパターン)
- 未クォートの展開:
$var/${arr[@]}を裸で使って分裂・グロブ暴発。 set -u下の未定義アクセス:"${var-}"か:-系で防御。- 正規表現と勘違い:
${var/pat/repl}のpatは glob。grep -Eや[[ =~ ]]と使い分ける。 [*]常用:要素が結合される。通常は[@]を使う。
運用メモ(実務)
- パス処理は前後除去で完結させ、外部コマンドを減らす。
- 既定値の採用/代入は冒頭でまとめて宣言(設定セクション化)。
- API キーなど必須値は
: "${TOKEN:?…}"で早期失敗に。
互換性と移植性
${var@Q}は Bash 4.4+。古い環境ではprintf -v q '%q' "$var"; echo "$q"。- 文字種変換(
^^,,,)は Bash 4+。Bash 3.x(古い macOS)では非対応。 - 置換・前後除去・部分文字列は Bash 3 でも概ね可(ただし配列は 4+ で充実)。
セキュリティと安全設計
- コマンド注入防止のため、値は値として扱い、
evalは使わない。 - 生成文字列を再評価しない(
$( … )の入れ子生成に注意)。 - 外部に渡すときはクォート済み表示(
${var@Q}/printf '%q')でログに残す。
パフォーマンスの勘所(短く)
- 外部コマンドの
sed/awk/cutをパラメータ展開に置き換えると、プロセス生成が減り速くなる。 - 巨大文字列の連結は
printfを用い、無駄な配列コピーを避ける。
