Bash 入門

Bash エラーハンドリングと制御フロー

スクリプトを作成する際、特にシステム管理(Sysadmin)に関連するスクリプトにおいて、エラーハンドリング(Error handling)は、ロバスト(堅牢)で信頼性の高いプログラムを構築するための重要なプロセスです。適切なエラーチェックのメカニズムがないと、スクリプトはエラーが発生した際に「サイレント失敗」を起こし、データの破損やシステムの一貫性が失われる危険な状態を招く可能性があります。

制御フロー(Control Flow) 文を導入することで、潜在的な問題をアクティブに検知し、エラーログの記録、ヒント情報の表示、あるいはスクリプトの安全な終了といった適切な措置を講じることができます。本章では、ifelseelifcase 文を使用して、エラーハンドリングのメカニズムをシステム管理の実践的なケースにシームレスに統合する方法を詳しく解説します。

1. if、then、else、elif を使用したエラーチェック

if 文は、Bashにおけるエラーチェックの基石です。これは、条件が truefalse かに基づいて、異なる コードブロック を実行することを可能にします。これを利用して、コマンドエグジットステータス の確認、ユーザーインプットバリデーション、あるいは ファイル の存在確認などを行います。

1.1 コマンドのエグジットステータスを確認

Bashでは、実行が完了したすべての コマンドエグジットステータス(exit status) を返します。これは コマンド が成功したかどうかを示す数値コードです。通常、ステータスコード 0 は成功を意味し、それ以外の non-zero 値は失敗を意味します。特殊な バリアブル である $? は、直前に実行された コマンドエグジットステータス を保持するために使用されます。

コード例:

#!/bin/bash

# 新しいディレクトリの作成を試行
mkdir /path/to/new/directory 

# mkdir コマンドのエグジットステータスをチェック
if [ $? -eq 0 ]; then
  echo "ディレクトリの作成に成功しました。"
else
  echo "ディレクトリの作成に失敗しました。"
fi

この例では、mkdir コマンド がディレクトリの作成を試みます。直後に if 文が $? の値をチェックし、それが 0 と等しい(-eq 0)場合は成功メッセージを、そうでない場合はエラーメッセージを表示します。

1.2 ユーザーインプットのバリデーション

ユーザーインプット は、スクリプト がエラーを起こす一般的な原因です。入力されたデータを処理する前に、それが スクリプト の要件に適合しているか バリデーション(Validation) を行うことが極めて重要です。

コード例:

#!/bin/bash

# ユーザーに数字の入力を促す
read -p "正の整数を入力してください: " num 

# 入力が正の整数であるかチェック
if ! [[ "$num" =~ ^[0-9]+$ ]] || [ "$num" -le 0 ]; then
  echo "入力が無効です。正の整数を入力してください。"
  exit 1
else
  echo "入力された値: $num"
fi

この スクリプト はユーザーに正の整数の入力を求めます。if 文はまず レジェックス(Regular Expression) (^[0-9]+$) を使用して入力が数字のみで構成されているかを確認し、続いてその整数が 0 より大きいかをチェックします。いずれかの条件が満たされない(false)場合、エラーメッセージを表示し、exit 1 によって強制終了します。

1.3 ファイルの存在確認とパーミッション

多くの スクリプト の実行は、ファイル やディレクトリに依存しています。それらを読み取ったり修正したりする前に、ファイル が存在するか、また現在のユーザーが適切な パーミッション(Permission) を持っているかを確認する必要があります。

コード例:

#!/bin/bash

# チェックするファイルパスを指定
file="/path/to/my/file.txt" 

# ファイルが存在し、かつ読み取り権限があるかチェック
if [ ! -f "$file" ]; then
  echo "エラー: ファイル '$file' が存在しません。"
  exit 1
elif [ ! -r "$file" ]; then
  echo "エラー: ファイル '$file' は読み取り不可能です。"
  exit 1
else
  echo "ファイル '$file' は存在し、読み取り権限があります。"
fi

この スクリプト は、指定された ファイル が存在するか(-f)、および読み取り可能か(-r)を個別にチェックします。条件が満たされない場合は、対応するエラーを表示して終了します。elif 文を使用することで、複数の条件を順番に連鎖させてチェックすることが可能です。

2. case 文を使用したエラーチェック

一つの バリアブルエクスプレッション が複数の異なる値を取る可能性がある場合、case 文は非常にエレガントな処理方法を提供します。if 文は単純な二元的な条件には適していますが、複数の排他的な可能性(例えば、異なるエラーコードの処理)に対応する場合、case 文の方が可読性とメンテナンス性に優れています。

2.1 異なるエラーコードの処理

ある コマンド が複数の異なるエラーコードを返し、それぞれのコードが特定の失敗タイプを表すと仮定します。case 文を使用して、各エラーを個別に処理できます。

コード例:

#!/bin/bash

# 異なるエラーコードを返すコマンドをシミュレート
simulate_command() {
  # ランダムに 0、1、または 2 を返す
  exit $((RANDOM % 3))
} 

simulate_command 

# 直前のコマンドのエグジットステータスをチェック
case $? in
  0)
    echo "コマンド実行に成功しました。"
    ;;
  1)
    echo "エラー: 無効な引数(Parameter)です。"
    ;;
  2)
    echo "エラー: ファイルが見つかりません。"
    ;;
  *)
    echo "エラー: 未知のエラーが発生しました。"
    ;;
esac

この例では、simulate_command ファンクション がランダムに 0, 1, 2 を エグジットステータス として返します。case 文が $? の値をキャプチャし、それぞれのステータスに応じたメッセージを表示します。最後の *) パターン は、想定外のコードをキャプチャする デフォルト(Default) ブランチとして機能します。

2.2 ユーザーの選択(チョイス)の処理

case 文は、メニューやプロンプトでユーザーが行った異なる選択を処理するのにも最適です。

コード例:

#!/bin/bash

read -p "オプションを選択してください (a, b, または c): " choice 

case "$choice" in
  a)
    echo "オプション a が選択されました。"
    ;;
  b)
    echo "オプション b が選択されました。"
    ;;
  c)
    echo "オプション c が選択されました。"
    ;;
  *)
    echo "無効な選択です。a, b, または c から選択してください。"
    exit 1
    ;;
esac

この スクリプト はユーザーに選択を促し、case 文が入力に応じて対応する コードブロック を実行します。入力が期待される範囲外の場合はエラーを表示して終了します。

3. システム管理スクリプトへのエラーチェックの適用

以前のレッスンで作成したシステム管理 スクリプト にエラーチェックを組み込み、より堅牢なものにアップグレードしましょう。ここでは、バックアップ機能にエラーハンドリングを追加することにフォーカスします。

オリジナルの(簡易版)バックアップファンクション:

backup_files() {
  # ソースディレクトリとバックアップ先ディレクトリを定義
  source_dir="/path/to/source/directory"
  backup_dir="/path/to/backup/directory"
    
  # バックアップディレクトリが存在しない場合は作成
  mkdir -p "$backup_dir"
    
  # ファイルをバックアップ先へコピー
  cp -r "$source_dir"/* "$backup_dir"
}

エラーチェックを追加した強化版バックアップファンクション:

backup_files() {
  # ソースディレクトリとバックアップ先ディレクトリを定義
  source_dir="/path/to/source/directory"
  backup_dir="/path/to/backup/directory"
    
  # 1. ソースディレクトリが存在するかチェック
  if [ ! -d "$source_dir" ]; then
    echo "エラー: ソースディレクトリ '$source_dir' が存在しません。"
    return 1
  fi
    
  # 2. バックアップディレクトリが存在しない場合は作成し、成功したかチェック
  mkdir -p "$backup_dir"
  if [ $? -ne 0 ]; then
    echo "エラー: バックアップディレクトリ '$backup_dir' を作成できませんでした。"
    return 1
  fi
    
  # 3. ファイルをコピーし、成功したかチェック
  cp -r "$source_dir"/* "$backup_dir"
  if [ $? -ne 0 ]; then
    echo "エラー: ファイルのコピーに失敗しました。"
    return 1
  fi
    
  echo "ファイルは正常に '$backup_dir' へバックアップされました。"
  return 0
} 

# 実行例:
backup_files 
if [ $? -eq 0 ]; then
  echo "バックアッププロセスが正常に完了しました。"
else
  echo "バックアッププロセスが失敗しました。"
fi

この強化版では、以下の重要な防御的チェックが追加されています:

  • ソースディレクトリの存在確認: [ ! -d "$source_dir" ] を使用して、バックアップ元が実際に存在するか確認します。
  • バックアップディレクトリの作成: mkdir -p コマンド の エグジットステータス をチェックします。権限不足などで作成に失敗した場合にエラーを返します。
  • ファイルコピーのプロセス: cp -r コマンド の エグジットステータス をチェックします。コピー中に中断や失敗があった場合にそれを検知します。

ファンクション 内部で return 1 を使用することで、その ファンクション 自体が失敗を示す non-zero ステータスで終了するようにしています。メインプロセス側では、このステータスをチェックすることで最終的な成功・失敗を判断します。