Bash 入門

Bash case 文

Bash の case 文は、複数の条件分岐をエレガントに処理するための手法です。単一のバリアブル(変数)やエクスプレッション(式)を複数の可能なパターンと照合させる必要がある場合、冗長な if-elif-else 構造よりも簡潔で可読性が高くなります。

この制御フロー文は、エクスプレッションを一度評価し、その値を一連のパターンと比較します。マッチする項目が見つかると、対応するコマンドブロックが実行されます。

1. case 文の構文を理解する

Bash における case 文の基本構文は case キーワードで始まり、評価するバリアブルまたはエクスプレッション、続いて in キーワードが続きます。

次に一連のパターンを列挙し、各パターンの後に右括弧 )コマンドブロック、そしてそのブロックの終了を示す2つのセミコロン ;; を記述します。最後に esac(case を逆読みしたもの)を置いて全体のブロックを閉じます。

case エクスプレッション in
    パターン1)
        パターン1で実行するコマンド
        ;;
    パターン2)
        パターン2で実行するコマンド
        ;;
    パターン3 | パターン4) # | (OR) 記号を使用して複数のパターンを区切ることが可能
        パターン3またはパターン4で実行するコマンド
        ;;
    *) # デフォルトケース (ワイルドカード。if-elif-else における 'else' に相当)
        デフォルトで実行するコマンド
        ;;
esac

1.1 コア構文の解説

  • エクスプレッション (EXPRESSION): 通常はバリアブルであり、その値が各パターンと比較されます。
  • パターン (PATTERN): 文字列またはシェルのワイルドカード(*, ?, [] など)です。最初にマッチしたパターンに関連付けられたコマンドが実行されます。
  • コマンド (COMMANDS): マッチングが成功した際に実行される1つ以上のコマンド。
  • ;; (ダブルセミコロン): 特定のパターンに関連付けられたコマンドブロックの終了をマークします。これがない場合、case 文は「フォールスルー(fall through)」現象を起こし、次のパターンのコマンドまで実行してしまいます。
  • | (パイプ記号): 1つのコマンドブロックに対して複数のパターンを指定できます。いずれかにマッチすれば実行されます。
  • * (アスタリスク): 任意の文字列にマッチするワイルドカードです。通常、最後のパターンとして配置され、それまでのどの条件にも当てはまらなかった場合の「デフォルト」処理を行います。

2. 詳細なコード例

2.1 基本的なメニューセレクター

ユーザーに操作メニューを表示し、選択に応じて異なるアクションを実行するスクリプトを作成してみましょう。

#!/bin/bash

echo "操作を選択してください:"
echo "1. ファイルをリストアップ"
echo "2. カレントディレクトリを表示"
echo "3. ディスク使用状況を表示"
echo "Q. 終了"

read -p "選択してください [1, 2, 3, Q]: " choice

case $choice in
    1)
        echo "カレントディレクトリのファイルをリストアップしています:"
        ls -l
        ;;
    2)
        echo "現在のディレクトリ:"
        pwd
        ;;
    3)
        echo "ディスク使用状況の情報:"
        df -h
        ;;
    Q|q) # 大文字 'Q' または小文字 'q' にマッチ
        echo "スクリプトを終了します。さようなら!"
        exit 0
        ;;
    *) # その他の入力に対するデフォルト処理
        echo "無効な選択です。1, 2, 3 または Q/q を入力してください。"
        ;;
esac

ロジックの解説:
この例では、ユーザー入力である $choice を評価しています。

  • 1 の場合は ls -l を実行。
  • 2 の場合は pwd を実行。
  • 3 の場合は df -h を実行。
  • Q または q の場合は終了メッセージを表示し、スクリプトを停止します。
  • それ以外の場合はエラーメッセージを表示します。

2.2 拡張子によるファイルタイプ別の処理

システム管理において、ファイルの拡張子に応じて異なる処理を行うタスクは非常に一般的です。case 文はこの用途に最適です。

#!/bin/bash

read -p "ファイル名を入力してください: " filename

case "$filename" in
    *.txt) # .txt で終わるすべてのファイルにマッチ
        echo "テキストファイルを処理中: $filename"
        cat "$filename" | head -n 5 # 最初の 5 行を表示
        ;;
    *.log) # .log で終わるすべてのファイルにマッチ
        echo "ログファイルを分析中: $filename"
        grep -i "error" "$filename" # エラー情報を検索 (ケースインセンティブ)
        ;;
    *.sh) # .sh で終わるすべてのファイルにマッチ
        echo "Bash スクリプトを実行中: $filename"
        bash "$filename"
        ;;
    *.zip|*.tar.gz|*.tgz) # 複数のアーカイブ拡張子にマッチ
        echo "アーカイブファイルを解凍中: $filename"
        tar -xf "$filename" # 例:tar 互換アーカイブと仮定
        ;;
    *) # 未知の拡張子に対するデフォルト処理
        echo "このスクリプトは '$filename' のファイルタイプを認識できないか、サポートしていません。"
        echo "手動で開くことを検討してください。"
        ;;
esac

ロジックの解説:
ここでは、ワイルドカードを使用したマッチングを行っています。

  • *.txt.txt サフィックスに一致。
  • *.zip|*.tar.gz|*.tgz は複数のアーカイブ形式を一括で処理。
  • case "$filename" in のようにバリアブルをダブルクォートで囲むのは、ファイル名にスペースが含まれている際などの予期せぬ挙動を防ぐためのベストプラクティスです。

3. システム管理の実戦:サービス制御スクリプト

オートメーションタスクの実践的なシナリオとして、ユーザー入力に基づいてサービスの状態を管理したり、システムイベントを処理したりする場合に case 文は非常に強力です。

Linux システム上のサービスを管理するスクリプトを想定してみましょう。管理者はサービスの起動、停止、再起動、状態確認を行いたいと考えます。

#!/bin/bash

SERVICE_NAME="apache2" # 例:管理対象のサービス名

echo "$SERVICE_NAME サービス管理ツール"
echo "-----------------------------------"
echo "使用可能なアクション:"
echo "start   - サービスの起動"
echo "stop    - サービスの停止"
echo "restart - サービスの再起動"
echo "status  - サービス状態の確認"
echo "exit    - 終了"

read -p "$SERVICE_NAME に対して実行するアクションを入力してください: " action

case "$action" in
    start)
        echo "$SERVICE_NAME を起動しようとしています..."
        sudo systemctl start "$SERVICE_NAME"
        if [ $? -eq 0 ]; then # 直前のコマンドの終了コードを確認
            echo "$SERVICE_NAME の起動に成功しました。"
        else
            echo "$SERVICE_NAME の起動に失敗しました。ログを確認してください。"
        fi
        ;;
    stop)
        echo "$SERVICE_NAME を停止しようとしています..."
        sudo systemctl stop "$SERVICE_NAME"
        if [ $? -eq 0 ]; then
            echo "$SERVICE_NAME の停止に成功しました。"
        else
            echo "$SERVICE_NAME の停止に失敗しました。ログを確認してください。"
        fi
        ;;
    restart)
        echo "$SERVICE_NAME を再起動しようとしています..."
        sudo systemctl restart "$SERVICE_NAME"
        if [ $? -eq 0 ]; then
            echo "$SERVICE_NAME の再起動に成功しました。"
        else
            echo "$SERVICE_NAME の再起動に失敗しました。ログを確認してください。"
        fi
        ;;
    status)
        echo "$SERVICE_NAME の状態を確認中:"
        systemctl status "$SERVICE_NAME"
        ;;
    exit)
        echo "サービス管理スクリプトを終了します。"
        exit 0
        ;;
    *)
        echo "無効なアクション: '$action'。"
        echo "'start', 'stop', 'restart', 'status', 'exit' のいずれかを使用してください。"
        ;;
esac

ロジックの解説:

このスクリプトapache2 サービスを管理します。ユーザーが入力した action に基づき、case 文が適切な systemctl コマンドへと誘導します。このように、受け付ける入力の選択肢が限定されている場合に case 文を使うと、ロジックを非常に簡潔に保つことができます。