Bash引数解析をマスターして強力なスクリプトを作成する
コマンドライン引数は、柔軟で再利用可能なBashスクリプトの要です。効果的な引数解析がなければ、スクリプトは単純な自動化ツールに留まり、定義済みの操作しか実行できません。引数解析をマスターすることで、単純な自動化タスクをプロフェッショナルで堅牢、かつユーザーフレンドリーなコマンドラインツールへと変貌させることができます。
本ガイドでは、位置引数、短縮オプション(フラグ)、および長オプションをBashで処理するための必須テクニックを、標準のgetoptsユーティリティとカスタム解析ループに焦点を当てて解説し、スクリプトが複雑な設定にも適切に対応できるようにします。
1. 基本:位置引数
フラグや名前付きオプションに取り組む前に、Bashが単純な順序引数をどのように処理するかを理解することが不可欠です。
| 変数 | 説明 | 例(スクリプトが./script.sh foo barで呼び出された場合) |
|---|---|---|
$0 |
スクリプト自体の名前 | ./script.sh |
$1, $2, ... |
1番目、2番目などの位置引数 | foo, bar |
$# |
位置引数の数 | 2 |
$@ |
すべての位置引数(個別の文字列として扱われる) | foo bar |
$* |
すべての位置引数(単一の文字列として扱われる) | foo bar |
例:単純な位置引数のチェック
#!/bin/bash
if [ "$#" -ne 2 ]; then
echo "使用法: $0 <ソースファイル> <宛先ディレクトリ>"
exit 1
fi
SOURCE="$1"
DESTINATION="$2"
echo "$SOURCE を $DESTINATION にコピー中..."
# cp "$SOURCE" "$DESTINATION"
2. getoptsによる標準解析(短縮オプション)
-v(詳細)や-f <ファイル>のようなオプションを必要とするプロフェッショナルなスクリプトには、組み込みのgetoptsユーティリティが、純粋なBashにおいて最も正統的で信頼性の高い方法です。これは特に短縮(一文字)オプションのために設計されています。
getoptsの仕組み
getoptsはオプションを順次読み取り、指定された変数(通常はOPT)に検出されたオプションを設定し、引数を必要とする場合はその引数をOPTARG変数に格納します。
- オプション文字列 (OPTSTRING): 有効なオプションを定義します。オプションが引数を必要とする場合(例:
-f filename)、文字の後にコロン(:)を付けます。例:vho:は-v、-h、および値が必要な-oを許可します。 OPTIND: 処理すべき次の引数を追跡するBashの内部インデックスです。1に初期化する必要があります(通常はデフォルトですが、複雑なスクリプトではリセットされることがあります)。
実用的なgetoptsテンプレート
このテンプレートは、-v(フラグ)、-h(フラグ)、および値が必要なオプションである-fの3つのオプションを処理します。
#!/bin/bash
# --- デフォルト値 ---
VERBOSE=0
FILENAME="default.txt"
# --- 使用法関数 ---
usage() {
echo "使用法: $0 [-v] [-h] [-f <ファイル>] <入力>"
exit 1
}
# --- 引数解析ループ ---
while getopts "v:hf:" OPT; do
case "$OPT" in
v)
VERBOSE=1
echo "詳細モードが有効になりました。"
;;
h)
usage
;;
f)
# OPTARG は -f に提供された引数を保持します
FILENAME="$OPTARG"
echo "出力ファイル名が: $FILENAME に設定されました"
;;
\?)
# 未認識のオプションまたは引数不足を処理します
echo "エラー: 無効なオプション -$OPTARG" >&2
usage
;;
:)
# 引数を必要とするオプション(例:引数なしの-f)の引数不足を処理します
echo "エラー: オプション -$OPTARG には引数が必要です。" >&2
usage
;;
esac
done
# --- 位置引数のシフト ---
# getoptsが終了した後、OPTIND は最初の非オプション引数のインデックスを保持します。
# shiftを使用して解析されたすべてのオプションを破棄し、位置引数($1、$2など)のみを残します。
shift $((OPTIND - 1))
# 必須の位置引数(INPUT)が存在するか確認
INPUT_DATA="$1"
if [ -z "$INPUT_DATA" ]; then
echo "エラー: 入力データが必要です。"
usage
fi
echo "---"
echo "入力処理中: $INPUT_DATA"
echo "詳細ステータス: $VERBOSE"
ヒント:
while getoptsループの直後にshift $((OPTIND - 1))を常に使用すると、オプションと残りの位置引数をきれいに分離できます。
3. 長いオプション(--option)の処理
純粋なBashの組み込みgetoptsは短縮オプションしかサポートしていません。モダンな長いオプション(例:--verbose、--output-file=data.log)を処理するには、shiftコマンドを使用してカスタムのwhileおよびcaseループを実装する必要があります。
この方法は、より明示的な引数管理を必要とします。
カスタム長オプションパーサー
#!/bin/bash
# --- デフォルト値 ---
VERBOSE=0
OUTPUT_FILE=""
# カスタム使用法表示関数(簡潔にするため省略)
# usage() { ... }
while [ "$#" -gt 0 ]; do
case "$1" in
--verbose)
VERBOSE=1
shift
;;
--output-file)
# フラグ用に1回、値用に1回の合計2回のシフトが必要
if [ -z "$2" ]; then
echo "エラー: --output-file には値が必要です。"
exit 1
fi
OUTPUT_FILE="$2"
shift 2
;;
--help)
usage
;;
-*)
echo "エラー: 不明なオプション $1" >&2
exit 1
;;
*)
# 最初の位置引数が見つかったため、オプション解析を停止
break
;;
esac
done
# 残りの引数は $1、$2 などとして利用可能になります。
# ... スクリプトのロジックが続行 ...
if [ "$OUTPUT_FILE" ]; then
echo "データは $OUTPUT_FILE にリダイレクトされます"
fi
高度な設定:キー=値形式の長オプション(--key=value)の処理
引数を等号(イコール)を使って渡す標準的なUNIXスタイルを好む場合は、パラメータ展開を使用して引数を分割する必要があります。
while [ "$#" -gt 0 ]; do
case "$1" in
--limit=*)
LIMIT_VAL="${1#*=}" # '='以降のすべてを削除
echo "Limit は: $LIMIT_VAL に設定されました"
shift
;;
# ... その他のオプション ...
esac
done
4. 堅牢なスクリプティングのためのベストプラクティス
A. 短縮オプションと長オプションの組み合わせ
最大限の柔軟性を得るために、経験豊富なスクリプターは、短縮オプションにはgetoptsの信頼性を、長オプションにはカスタムのwhile/caseループの柔軟性を組み合わせることがよくあります。まず長オプションのループを実行して長フラグを消費し、その後、残りの引数(短縮オプションを含む)をgetoptsで処理します。
しかし、より一般的でクリーンなパターンは、-oと--optionの両方を検索し、それに応じてshiftを行う、単一の堅牢なカスタムループですべての引数を処理することです。
B. エラー処理と使用法の表示
スクリプトが必要とする引数とオプションを説明する明確なusage関数を必ず用意してください。この関数は、以下のいずれかの状況で呼び出されるべきです。
- ユーザーがヘルプを要求した場合(例:
-hまたは--help)。 - 必須の位置引数が不足している場合。
- 無効または不明なオプションが指定された場合。
エラーが発生した場合は、スクリプトがゼロ以外のステータスで終了(exit 1)し、エラーメッセージを標準エラー出力(>&2)に出力するようにしてください。
C. デフォルト値
すべての設定変数をスクリプトの先頭で、妥当なデフォルト値で初期化します。これにより、オプション引数が渡されなくても、スクリプトの動作が予測可能になります。
# 解析前に変数を必ず初期化する
LOG_LEVEL="info"
FORCE=0
結論
引数解析を習得することで、単なるBashスクリプトを多目的なコマンドラインユーティリティへと格上げできます。最も単純なタスクには位置引数($1、$2)で十分ですが、短縮オプションにはgetoptsを使用することでPOSIX標準への準拠と堅牢な解析が保証されます。長オプションについては、専用のwhileループとshiftを使用することで、モダンなCLIツールに求められる柔軟性が得られます。堅牢な解析、妥当なデフォルト値、および明確な使用法メッセージを統合することで、自動化スクリプトは大幅に強力かつ管理しやすくなります。