パラメータ展開レシピ|既定値・置換・長さ取得

データとパラメータ展開
スポンサーリンク

この記事の狙い

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}DEFDEF空も既定値にする
${var=DEF}var=DEF何もしない代入あり
${var:=DEF}var=DEFvar=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(末尾一致)

patglob パターン* ? [ .. ] など)。正規表現ではない点に注意。


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}patglobgrep -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 を用い、無駄な配列コピーを避ける。

参考リンク

スポンサーリンク
Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする