【Bash】case文の書き方完全ガイド!複数条件・ワイルドカードとif文の使い分け

スクリプト設計

Bashでシェルスクリプトを書いているとき、条件分岐が増えるにつれて if ... elif ... elif ... else の階段が延々と続き、コードの見通しが悪くなってうんざりした経験はありませんか?

「elifの階段でネストが深くなり、視界不良になった経験はありませんか?『スクリプトはシンプルに』が私のモットー。分岐が増えたらcase文でスッキリ整えましょう!」――こんにちは、Bash玄(ばっしゅげん)です。

Bashの case 文は、単一の変数に対して複数の文字列パターンを照合し、合致した処理をすっきりと実行するための制御構文です。case 文を使いこなせば、コマンドライン引数の解析やサービスの起動・停止スクリプト、ユーザーからの対話型入力の判定を劇的にシンプルかつ堅牢に記述できます。

本記事では、case文の基本構文から複数条件(OR指定)、ワイルドカードや文字クラスを用いた高度なパターンマッチ、Bash 4.0以降で使えるフォールスルー(;& / ;;&)、if文との明確な使い分け基準マトリクス、そして実務でそのままコピペして使えるCLIテンプレートまで、現場目線で余すところなく徹底解説します。


  1. 【結論】Bashのcase文の基本構文とコピペ用テンプレート
    1. case文の1行基本構文まとめ
    2. 即使える引数分岐テンプレート(CLI制御スクリプト)
  2. case文の書き方ルールと各構文の意味
    1. in と esac の基本構造
    2. パターン末尾の ;;(ブレーク)と省略時の挙動
    3. デフォルト処理を表す *)(ワイルドカード)の必須性
  3. 実務で必須のパターンマッチング技法
    1. 複数条件(OR検索): パイプ | でまとめる
    2. ワイルドカードと文字クラス(大文字小文字・数値判定)
      1. 大文字・小文字を両方許容する対話プロンプトパターン
      2. 数値フォーマットのバリデーション
    3. Bash 4.0以降の高度な制御(;& でフォールスルー、;;& で継続判定)
      1. 1. ;& によるフォールスルー(段階的処理の蓄積)
      2. 2. ;;& による複数パターンの継続判定(属性チェック)
  4. 【実務比較】if文とcase文はどちらを使うべきか?(早見表付き)
    1. if-elifとcase文の使い分け基準マトリクス
    2. 判定対象による判断基準:単一変数値 vs 複数条件の論理積
      1. 1. 「変数が3分岐以上」ならcase文一択
      2. 2. 「数値の大小比較・範囲指定」ならif文
      3. 3. 「複数変数の組み合わせ(AND / OR)」ならif文
  5. 実務現場で即使えるcase文の活用シナリオ
    1. シナリオ1: コマンドライン引数()による処理切り替え(自作CLIツール)
    2. シナリオ2: ユーザーの対話型プロンプト入力判定(yes / no)
    3. シナリオ3: OS判定によるパッケージマネージャー自動判別(Debian / RedHat / macOS)
  6. よくあるエラーとデバッグ方法
    1. syntax error near unexpected token ';;' の原因と対処
      1. 主な発生原因
    2. クォートによるパターンの無効化ミス(文字列リテラル化)
    3. bash -x によるcase文の実行トレース
  7. まとめ: case文で可読性の高いエレガントなシェルスクリプトを書こう
    1. あわせて読みたい関連記事
  8. 関連記事

【結論】Bashのcase文の基本構文とコピペ用テンプレート

まずは結論から。Bashにおける case 文の最小構文と、実務現場でそのまま使えるCLIサービス制御テンプレートです。

case文の1行基本構文まとめ

case "$変数" in パターン1) 処理1 ;; パターン2) 処理2 ;; *) デフォルト処理 ;; esac

case "$変数" in で開始し、パターンと閉じ括弧 ) で条件を指定し、各ブロックの末尾を ;; で終え、最後に case を逆から綴った esac で閉じるのが基本骨格です。

即使える引数分岐テンプレート(CLI制御スクリプト)

実務で頻出する、サービス制御(start / stop / restart / status / その他)を行うコマンドライン引数分岐スクリプトです。そのままコピー&ペーストして処理内容を書き換えるだけで利用できます。

#!/usr/bin/env bash
set -euo pipefail

# サービス名定義
SERVICE_NAME="my-daemon"

# 使い方(Usage)を表示する関数
usage() {
    echo "使用方法: $0 {start|stop|restart|status}" >&2
    exit 1
}

# 引数が指定されていない場合はUsageを表示
if [ $# -lt 1 ]; then
    echo "エラー: 引数が指定されていません。" >&2
    usage
fi

# 第1引数($1)による処理の分岐
case "$1" in
    start)
        echo "[INFO] ${SERVICE_NAME} を起動しています..."
        # 起動コマンド(例: systemctl start my-daemon)
        ;;
    stop)
        echo "[INFO] ${SERVICE_NAME} を停止しています..."
        # 停止コマンド
        ;;
    restart)
        echo "[INFO] ${SERVICE_NAME} を再起動しています..."
        # 再起動処理
        ;;
    status)
        echo "[INFO] ${SERVICE_NAME} のステータスを確認しています..."
        # ステータス確認処理
        ;;
    *)
        echo "エラー: 不正な引数です -> '$1'" >&2
        usage
        ;;
esac

echo "[SUCCESS] コマンド処理が完了しました。"

実行結果例:

$ ./service-control.sh start
[INFO] my-daemon を起動しています...
[SUCCESS] コマンド処理が完了しました。

$ ./service-control.sh reload
エラー: 不正な引数です -> 'reload'
使用方法: ./service-control.sh {start|stop|restart|status}

このように、想定外の引数が渡された場合でも最後の *) パターンで捕捉し、標準エラー出力にUsageを表示して安全に異常終了(終了ステータス1)させることができます。


case文の書き方ルールと各構文の意味

Bashの case 文は、C言語やJava、JavaScriptなどの switch-case 構文に相当する制御文ですが、シェルスクリプト特有の記述ルールがいくつか存在します。それぞれの構成要素の意味と挙動を正確に把握しておきましょう。

in と esac の基本構造

case 文の全体構造は、評価対象を指定する case "$変数" in から始まり、末尾の esac で閉じます。

case "$TARGET" in
    pattern1)
        # 処理内容
        ;;
    pattern2)
        # 処理内容
        ;;
esac

ここで押さえておくべきポイントは以下の通りです:

  • esac の語源: case を逆順に並べた文字列です。Bashの if ... fi と同じ設計思想で、ブロックの終了を明示します。
  • パターンの指定書式: パターン文字列の後ろに半角閉じ括弧 ) を配置します(例: start))。なお、POSIX規格およびBashでは、対応する開き括弧を書いて (start) と記述することも文法上認められていますが、実務のシェルスクリプトでは開き括弧を省略して start) と書くスタイルが一般的です。
  • 変数のクォート("$TARGET"): case "$TARGET" in のように変数を必ずダブルクォートで囲むことが推奨されます。もしクォートを忘れて変数が空文字だった場合、Bashは構文エラーにならず空文字としてマッチ判定を行いますが、意図しない空白割れや特殊文字による誤作動を防ぐため、常にダブルクォートで保護する習慣をつけましょう。

パターン末尾の ;;(ブレーク)と省略時の挙動

各パターンの処理ブロックの末尾には、必ず連続した2つのセミコロン ;; を記述します。

この ;; は、他言語における break 文 と全く同じ役割を果たします。合致したパターンの処理を実行した後、後続のパターンを一切評価せずに直ちに esac の外側へ制御を脱出させます。

case "$action" in
    build)
        echo "ビルドを実行します"
        ;;  # ここでcase文を抜ける
    test)
        echo "テストを実行します"
        ;;
esac

【注意】;; を省略した際のエラー

もし末尾の ;; を書き忘れて次のパターンを記述してしまうと、Bashは構文解析に失敗し、実行時に以下のような致命的な構文エラー(syntax error)を引き起こします。

# NG例: ;; の書き忘れ
case "$action" in
    build)
        echo "ビルドを実行します"
        # ;; が抜けている
    test)
        echo "テストを実行します"
        ;;
esac

# 実行結果:
# sample.sh: 行 6: 予期しないトークン `test)' 付近に構文エラーがあります

なお、esac の直前にある最後のパターンの処理ブロックに限っては ;; を省略しても文法上エラーにはなりませんが、将来新しいパターンを追加した際のバグを防止するため、すべてのパターン末尾に必ず ;; を明記するのが現場の鉄則です。

デフォルト処理を表す *)(ワイルドカード)の必須性

case 文の最後のパターンとして、必ず *) を用意しましょう。

case "$input" in
    yes)
        echo "処理を続行します"
        ;;
    no)
        echo "処理を中断します"
        ;;
    *)
        echo "エラー: 'yes' または 'no' を入力してください" >&2
        exit 1
        ;;
esac

アスタリスク * は、シェルにおける「0文字以上の任意の文字列」を表すワイルドカードです。それより前のいずれのパターンにも合致しなかったすべての値が、この *) にマッチします。これは他言語の default 句 や if 文の else に相当します。

もし *) を定義せず、かついずれのパターンにも合致しなかった場合、case 文は何の処理も行わずに素通りし、終了ステータス 0 で正常終了します。しかし実務スクリプトでは、「想定外の値が渡されたときに黙って無視される」ことは深刻な潜在バグや障害の元です。必ず *) で予期せぬ入力を捕捉し、エラーメッセージを標準エラー出力(>&2)に出力してスクリプトを安全に中断させましょう。


実務で必須のパターンマッチング技法

Bashの case 文が強力である最大の理由は、単なる文字列の完全一致だけでなく、シェルの強力なパターンマッチング(ワイルドカード・文字クラス)やOR条件をシンプルに組み合わせられる点にあります。

複数条件(OR検索): パイプ | でまとめる

1つの処理に対して複数のパターンを合致させたい場合は、縦棒(パイプ記号) | で区切って並べます。

case "$command" in
    start|run|up)
        echo "コンテナ・サービスを開始します"
        ;;
    stop|down|kill)
        echo "コンテナ・サービスを停止します"
        ;;
    status|ps|info)
        echo "稼働状況を表示します"
        ;;
    *)
        echo "不明なコマンドです: $command" >&2
        exit 1
        ;;
esac

if 文でこれを書こうとすると、if [ "$command" = "start" ] || [ "$command" = "run" ] || [ "$command" = "up" ]; then のように比較式をいくつも論理結合する必要があり、記述が冗長になります。case 文であれば start|run|up) と書くだけで直感的なOR条件を構成でき、コードの可読性が格段に向上します。

ワイルドカードと文字クラス(大文字小文字・数値判定)

case 文のパターンには、ファイル名展開(Glob)と同じワイルドカード構文や文字クラスが利用できます。

パターン記法 マッチする対象 実用例
* 0文字以上の任意の文字列 *.log)(末尾が .log で終わる全ファイル)
? 任意の1文字 image-??.png)(2桁連番の画像ファイル名)
[...] 括弧内のいずれか1文字 [yY])(小文字のyまたは大文字のY)
[!...] 括弧内に含まれない任意の1文字 [!0-9]*)(先頭が数字以外で始まる文字列)
[0-9] 任意の1文字の数字 [0-9]*)(先頭が数字で始まる文字列)
[a-zA-Z] 任意の英字1文字 [a-zA-Z]*)(英字で始まる識別子)

大文字・小文字を両方許容する対話プロンプトパターン

ユーザーのキーボード入力(Yes / No)を判定する際、[yY]|[yY][eE][sS] を活用すると、y、Y、yes、YES、Yes などを1行で漏れなくカバーできます。

read -r -p "デプロイを実行しますか? (y/N): " answer

case "$answer" in
    [yY]|[yY][eE][sS])
        echo ">> デプロイ処理を実行します..."
        ;;
    [nN]|[nN][oO]|"")
        echo ">> 処理をキャンセルしました。"
        exit 0
        ;;
    *)
        echo "無効な入力です。処理を中止します。" >&2
        exit 1
        ;;
esac

空エンター(未入力)も "" でOR結合しておくことで、デフォルト動作(No扱い)をスマートに実装できます。

数値フォーマットのバリデーション

引数が整数値かどうか、ポート番号やIDの形式を満たしているかもパターンマッチで素早く確認できます。

port="$1"

case "$port" in
    ""|*[!0-9]*)
        echo "エラー: ポート番号には正の整数のみを指定してください -> '$port'" >&2
        exit 1
        ;;
    *)
        echo "ポート番号 $port でサーバーを起動します"
        ;;
esac

*[!0-9]* は、「数字以外の文字が1文字でも含まれている文字列」にマッチします。これにより、空文字("")または英字・記号混じりの入力を確実に弾くことができます。

Bash 4.0以降の高度な制御(;& でフォールスルー、;;& で継続判定)

Bash 4.0以降では、従来の ;;(ブレーク)に加えて、;& と ;;& という2つの新しい終端演算子が追加されました。これらを活用すると、より高度な制御フローを構築できます。

演算子 動作の名称 挙動の詳細
;; 通常ブレーク(標準) ブロックを実行後、直ちに case 文を脱出する。
;& フォールスルー(無条件継続) 次のパターンの合致判定を無視して無条件に実行する(C言語の break なしの挙動)。
;;& 継続判定(条件再評価) case 文を脱出せず、後続のパターンが合致するかどうかを続けて評価する。

1. ;& によるフォールスルー(段階的処理の蓄積)

ログレベルの制御など、「DEBUGを指定した場合はINFOやWARNの処理も合わせて実行したい」という場面では ;& が最適です。

#!/usr/bin/env bash
# ログレベルに応じた処理蓄積
LOG_LEVEL="${1:-INFO}"

case "$LOG_LEVEL" in
    DEBUG)
        echo "[DEBUG] 詳細デバッグトレースを有効化しました"
        ;&  # 次のブロックへ無条件フォールスルー
    INFO)
        echo "[INFO] 標準メッセージを出力します"
        ;&  # 次のブロックへ無条件フォールスルー
    WARN)
        echo "[WARN] 警告レベルの監視を行います"
        ;;  # ここで脱出
    *)
        echo "無効なログレベル: $LOG_LEVEL" >&2
        exit 1
        ;;
esac

実行結果(DEBUG指定時):

$ ./log-level.sh DEBUG
[DEBUG] 詳細デバッグトレースを有効化しました
[INFO] 標準メッセージを出力します
[WARN] 警告レベルの監視を行います

2. ;;& による複数パターンの継続判定(属性チェック)

1つの文字列に対して複数の属性を独立して検査したい場合、;;& を使うとすべてのパターンを順次チェックできます。

#!/usr/bin/env bash
file="backup-archive.tar.gz"

case "$file" in
    *backup*)
        echo "[MATCH 1] バックアップ関連ファイルです"
        ;;&  # 次のパターンも続けて条件判定する
    *.tar.gz|*.tgz)
        echo "[MATCH 2] gzip圧縮されたtarアーカイブです"
        ;;&  # 次のパターンも続けて条件判定する
    *.log)
        echo "[MATCH 3] ログファイルです"
        ;;
    *)
        echo "[FINISH] ファイル属性の判定が完了しました"
        ;;
esac

実行結果:

$ ./check-file.sh
[MATCH 1] バックアップ関連ファイルです
[MATCH 2] gzip圧縮されたtarアーカイブです
[FINISH] ファイル属性の判定が完了しました

※ ;& や ;;& は Bash 4.0以降の専用拡張機能 です。古いUnix環境や /bin/sh(POSIX互換モード、Ubuntuの dash など)では構文エラーとなるため、移植性が求められる共通スクリプトでは従来の ;; のみを使用してください。


【実務比較】if文とcase文はどちらを使うべきか?(早見表付き)

シェルスクリプトで条件分岐を設計する際、「if-elif を使うべきか、case 文を使うべきか」で迷う場面は少なくありません。どちらも分岐を表現できますが、処理の性質によって適した構文が明確に分かれます。

if文の基本記法や演算子一覧については、Bashのif文の書き方と条件分岐の基本 で詳しく解説していますが、ここでは両者の設計思想の違いと使い分け基準をマトリクスで整理します。

if-elifとcase文の使い分け基準マトリクス

比較項目 case 文 if-elif 文
得意な判定対象 単一の変数に対する文字列・パターン一致 数値の大小・複数変数の論理式・コマンド終了ステータス
分岐の規模 3分岐以上で圧倒的に見やすい 1〜2分岐(単純な真偽・有無チェック)向き
ワイルドカード・OR pattern1|pattern2) で極めて簡潔 [[ ... ]] || [[ ... ]] で記述が肥大化しやすい
数値の範囲判定 不向き(文字列表現としてのマッチのみ) 最適(-gt, -lt や (( a > b )) を使用)
ファイル・コマンド判定 直接判定不可(変数を介する必要あり) 直接判定可能([ -f file ] や if grep -q)
ネストの深さ インデントが一定でフラットに保たれる 分岐が増えるほど階段状に深くなりやすい

判定対象による判断基準:単一変数値 vs 複数条件の論理積

選択の最も根本的な基準は、「何を基準に分岐させたいか」です。

1. 「変数が3分岐以上」ならcase文一択

1つの変数が取り得る値(コマンド引数、設定名、ユーザーの応答など)が3つ以上存在する場合は、迷わず case 文を選択してください。

# × 読みにくい elif の階段
if [ "$action" = "create" ]; then
    do_create
elif [ "$action" = "update" ]; then
    do_update
elif [ "$action" = "delete" ]; then
    do_delete
else
    echo "不明なアクションです" >&2
fi

# 〇 すっきりして読みやすい case 文
case "$action" in
    create) do_create ;;
    update) do_update ;;
    delete) do_delete ;;
    *) echo "不明なアクションです" >&2 ;;
esac

case 文はインデントの深さが一定に保たれ、新しい分岐を追加する場合も1行書き足すだけで済むため、保守性が非常に高くなります。

2. 「数値の大小比較・範囲指定」ならif文

「変数が100以上かつ200未満」「ファイルの更新日時が新しい」といった、数値の不等号比較やファイルテストを行う場合は、if 文が最適です。

# 数値の大小・範囲比較は if 文が最適
if (( count >= 100 && count < 200 )); then
    echo "適正範囲内です"
fi

case 文でも [1-9][0-9]) のように文字パターンとして数値を判定することは可能ですが、桁数が増えるとパターンが複雑怪奇になり、バグの温床となります。数値の計算や比較は素直に if 文または算術構文 (( )) に任せましょう。

3. 「複数変数の組み合わせ(AND / OR)」ならif文

「ユーザーが root で、かつ環境が本番環境(production)の場合」のように、複数の変数を論理積(&&)で判定したい場合も if 文の独壇場です。

if [[ "$USER" == "root" && "$ENV" == "production" ]]; then
    echo "本番環境での管理者作業を開始します"
fi

実務現場で即使えるcase文の活用シナリオ

実務のLinuxシステム運用やCI/CDパイプラインにおいて、case 文が真価を発揮する3大シナリオを具体的なスクリプトコードとともに紹介します。

シナリオ1: コマンドライン引数()による処理切り替え(自作CLIツール)

社内ツールやデプロイスクリプトなど、サブコマンドを受け取って処理を分岐させるCLIユーティリティの典型パターンです。コマンドライン引数の基本や $@ による安全な展開については、シェルスクリプトでコマンドライン引数を安全に処理する方法 でも詳しく解説しています。

#!/usr/bin/env bash
set -euo pipefail

# サブコマンドの判定
subcommand="${1:-}"

case "$subcommand" in
    init)
        echo "環境を初期化しています..."
        mkdir -p ./config ./data ./logs
        ;;
    deploy)
        env_target="${2:-dev}"
        echo "ターゲット環境 [${env_target}] へデプロイを実行します..."
        ;;
    clean)
        echo "一時キャッシュを削除しています..."
        rm -rf ./logs/*.tmp
        ;;
    -h|--help|help)
        cat << 'HELP_MSG'
使用法: mytool  [arguments]

利用可能なサブコマンド:
  init            ディレクトリ構成の初期セットアップ
  deploy     指定環境へのデプロイ(デフォルト: dev)
  clean           一時ファイルのクリーンアップ
  help, -h        ヘルプメッセージの表示
HELP_MSG
        exit 0
        ;;
    "")
        echo "エラー: サブコマンドを指定してください。" >&2
        echo "詳細な使い方は '$0 help' を実行してください。" >&2
        exit 1
        ;;
    *)
        echo "エラー: 未知のサブコマンドです -> '$subcommand'" >&2
        exit 1
        ;;
esac

引数が複数ある場合は、shift で第1引数を追い出し、残りの引数を for 文で走査する組み合わせが鉄板です。引数のループ処理については、Bashのforループを使った繰り返し処理の基本 を参考にしてください。

シナリオ2: ユーザーの対話型プロンプト入力判定(yes / no)

サーバー構築やデータ削除など、取り消しのつかない危険なコマンドを実行する前に、オペレーターに対して安全確認を求める対話型プロンプトです。

#!/usr/bin/env bash
set -euo pipefail

# 削除前の確認プロンプト
confirm_action() {
    local prompt_msg="$1"
    local response

    while true; do
        read -r -p "${prompt_msg} [y/N]: " response
        case "$response" in
            [yY]|[yY][eE][sS])
                return 0
                ;;
            [nN]|[nN][oO]|"")
                return 1
                ;;
            *)
                echo "不正な入力です。'y' または 'n' を入力してください。"
                ;;
        esac
    done
}

# 使用例
if confirm_action "古いログアーカイブを全削除してもよろしいですか?"; then
    echo "[EXECUTE] 削除処理を実行しました。"
else
    echo "[CANCEL] ユーザーにより処理が中断されました。"
    exit 0
fi

while true ループの中に case 文を配置することで、ユーザーが想定外のタイポ(誤入力)をした場合でも、正しいキーが入力されるまで再質問を繰り返す堅牢な設計になっています。

シナリオ3: OS判定によるパッケージマネージャー自動判別(Debian / RedHat / macOS)

環境構築用スクリプト(dotfilesやサーバープロビジョニング)において、実行マシンのOS種別を自動検出し、適切なパッケージマネージャー(apt / dnf / brew)を呼び分ける実務テクニックです。

#!/usr/bin/env bash
set -euo pipefail

# OSカーネル名の取得
os_type="$(uname -s)"

case "$os_type" in
    Linux)
        # Linuxディストリビューションの判別(/etc/os-release)
        if [ -f /etc/os-release ]; then
            # 変数 ID(ubuntu, debian, rhel, almalinux など)を読み込み
            distro_id="$(. /etc/os-release && echo "$ID")"
        else
            distro_id="unknown"
        fi

        case "$distro_id" in
            ubuntu|debian)
                echo "[Debian系] apt-get を使用してパッケージを更新します"
                PKG_MGR="apt-get"
                ;;
            rhel|centos|fedora|rocky|almalinux)
                echo "[RedHat系] dnf を使用してパッケージを更新します"
                PKG_MGR="dnf"
                ;;
            arch)
                echo "[Arch系] pacman を使用してパッケージを更新します"
                PKG_MGR="pacman"
                ;;
            *)
                echo "警告: 未知のLinuxディストリビューションです: $distro_id" >&2
                exit 1
                ;;
        esac
        ;;
    Darwin)
        echo "[macOS] Homebrew を使用します"
        PKG_MGR="brew"
        ;;
    *)
        echo "エラー: サポート対象外のOSです: $os_type" >&2
        exit 1
        ;;
esac

echo "選択されたパッケージマネージャー: $PKG_MGR"

このように case 文を階層的にネストさせることで、「大枠のOS種別(Linux / macOS)」を判定した上で、Linux内の「ディストリビューションファミリー(Debian系 / RedHat系)」を整然と切り分けることができます。


よくあるエラーとデバッグ方法

case 文の作成時につまずきやすい構文エラーや予期せぬマッチング失敗、および実行時のトレースデバッグ手法をまとめました。

syntax error near unexpected token ';;' の原因と対処

case 文を記述していて最も目にするエラーメッセージが、この syntax error near unexpected token ';;' です。

sample.sh: 行 8: 予期しないトークン `;;' 付近に構文エラーがあります
sample.sh: 行 8: `        ;;'

主な発生原因

  1. パターンの閉じ括弧 ) の書き忘れ: パターン名の末尾に ) を付け忘れると、シェルは ;; の直前で構文を認識できなくなります。
  2. ;; の重複記述: コピペミスなどで ;; の行を連続して2回書いてしまった場合にも発生します。
  3. パターン指定より前にコマンドを記述した: case "$var" in の直後、最初の pattern) を書く前に echo などのコマンドを記述すると、構文エラーになります。
# × 誤り例: パターンの ) 忘れ
case "$env" in
    prod  # ) が抜けている
        echo "本番です"
        ;;
esac

# 〇 正しい記述
case "$env" in
    prod)
        echo "本番です"
        ;;
esac

クォートによるパターンの無効化ミス(文字列リテラル化)

初心者が特にハマりやすいのが、「パターン側をダブルクォートで囲んでしまうミス」です。

Bashの変数展開では「変数はダブルクォートで囲む」のが鉄則ですが、case 文のパターン側にクォートを付けると、ワイルドカード(* や ?、[...])の意味が打ち消され、単なる文字通りの記号(リテラル)として扱われてしまいます。

filename="report.txt"

# × 誤り例: パターンをダブルクォートで囲んでいる
case "$filename" in
    "*.txt")
        echo "テキストファイルです"
        ;;
    *)
        echo "マッチしませんでした"
        ;;
esac

# 実行結果:
# マッチしませんでした("*.txt" という完全一致の文字を探してしまうため)

ワイルドカード展開を正しく機能させるには、クォートを外して記述してください。

# 〇 正しい記述: クォートを外す
case "$filename" in
    *.txt)
        echo "テキストファイルです"
        ;;
    *)
        echo "その他"
        ;;
esac

なお、空白を含む固定文字列を1つのパターンとしてマッチさせたい場合(例: "web server"))に限っては、ダブルクォートで囲むことが有効です。

bash -x によるcase文の実行トレース

「どのパターンに合致しているのか」「なぜ *) に流れてしまうのか」が分からないときは、bash -x オプションを使って実行トレース(実行されたコマンドの逐次表示)を有効化するのが最も確実なデバッグ手法です。

$ bash -x ./service-control.sh start
+ SERVICE_NAME=my-daemon
+ '[' 1 -lt 1 ']'
+ case "$1" in
+ echo '[INFO] my-daemon を起動しています...'
[INFO] my-daemon を起動しています...
+ echo '[SUCCESS] コマンド処理が完了しました。'
[SUCCESS] コマンド処理が完了しました。

+ case "$1" in の行で実際にどの変数値が評価され、どのパターンのブロックにジャンプしたかがひと目で確認できます。スクリプト全体ではなく特定の case 文周辺だけを調査したい場合は、コード内に set -x と set +x を挟んで局所的にデバッグログを出力させましょう。

set -x  # デバッグトレース開始
case "$arg" in
    ...
esac
set +x  # デバッグトレース終了

まとめ: case文で可読性の高いエレガントなシェルスクリプトを書こう

Bashの case 文を実務で安全・エレガントに使いこなすための重要ポイントを振り返りましょう。

  1. 3分岐以上の文字列判定はcase文一択: if-elif の深いネストを解消し、フラットで保守性の高いコードを実現できる。
  2. パイプ | とワイルドカードを使いこなす: 複数条件のOR検索や、[yY]|[yY][eE][sS] などの文字クラスによる柔軟なパターンマッチを1行で記述可能。
  3. 記述ルールを厳守する: ブロック末尾の ;; を忘れず明記し、想定外の入力からスクリプトを守るデフォルト句 *) を必ず設置する。

「スクリプトはシンプルに」――分岐が増えてコードが乱雑になりそうなときは、ぜひ case 文を活用して美しく整理されたシェルスクリプトを構築してください!

あわせて読みたい関連記事

シェルスクリプトの制御構文や実務設計スキルをさらに高めたい方は、以下の解説記事もぜひ参考にしてください。

Bash玄

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

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

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

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

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

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

Bash玄をフォローする

コメント