現場で使えるシェルスクリプト入門13:自作スクリプトをツール化する「getopts」でのオプション解析

シェルスクリプト IT

前回の記事(入門12)では、スクリプトを安全に動かすための「set -euo pipefail」設定と、異常終了時でもゴミを掃除する「trap」コマンドについて解説しました。

今回は、Linuxの標準コマンドのように -f-d などのオプション(引数)を受け取って柔軟に動作が切り替えられる、実用的な「CLIツール」へステップアップさせる方法を解説します。

現場で役立つ汎用的なコマンドツールを作るための標準機能「getopts」の使い方をマスターしましょう!

位置パラメータ(, )の限界と落とし穴

シェルスクリプトに引数を渡す際、最も手軽なのは $1$2 などの「位置パラメータ」を使用する方法です。

#!/bin/bash
# 実行例: ./backup.sh /src/dir /dest/dir
src_dir=$1
dest_dir=$2

しかし、位置パラメータには以下のような問題があります。

1. 引数の順番が厳格に固定される
順番通りに指定しないと変数に間違った値が格納され、思わぬバグに繋がります(上記の例でコピー元とコピー先を逆に指定してしまうなど)。

2. 「引数が省略された時」の制御が複雑になる
一部のオプションパラメータを「省略可能」にしたい場合、条件分岐(if文)が煩雑になり、コードの可読性が著しく低下します。

こうした位置パラメータの問題を解決し、-f-dといった一般的なコマンドオプションを安全に解析するために用意されているのが、シェル標準のビルドインコマンドである getopts です。

getopts コマンドの基本ルールと構文

getopts は、スクリプトに渡された引数を1つずつ読み取り、定義したオプションと一致するかをループ処理で順番にチェックしていくコマンドです。

while getopts "オプション定義" opt; do
    case "$opt" in
        a) # オプション -a が指定された場合の処理 ;;
        b) # オプション -b が指定された場合の処理 ;;
    esac
done

💡 オプション文字列のルール(コロン「:」の意味)

getopts に指定するオプション文字列の定義方法には重要なルールがあります。それは、「引数を必要とするオプションの文字の直後にコロン(`:`)を記述する」という点です。

定義例受け取れるオプション意味
"d"-d単体で指定するフラグ(値は不要)
"f:"-f file.txt直後に値が必要なオプション(ファイルパスなど)
"df:"-d および -f file.txt値が不要な -d と、値が必要な -f の組み合わせ

💡 特別な環境変数:OPTARG と OPTIND

getopts のループ内では、以下の2つのシェル変数が自動的に更新されます。

OPTARG (Option Argument)
値をとるオプション(コロン付きのもの)が指定された際、その値(引数の中身)が一時的に格納されます(例: `-f file.txt` の場合の `file.txt`)。

OPTIND (Option Index)
次に処理される引数のインデックス番号(何番目のパラメータか)を指す数値です。`getopts` のループを抜けた後、オプション以外の通常のパラメータ(ファイルリストなど)を処理するために使われます。

オプション解析スクリプト例

それでは、具体的なスクリプト例を見ていきましょう。

このスクリプトは、値をとるオプション -f [ファイル名]、単体の切り替えフラグ -d(デバッグ表示)、およびヘルプを表示する -h を処理します。

#!/bin/bash
set -euo pipefail

# 使い方(ヘルプメッセージ)を表示する関数
usage() {
    echo "Usage: $0 [-f file_path] [-d] [-h]"
    echo "  -f : 処理対象のファイルパスを指定します(必須)"
    echo "  -d : デバッグモード(ログ出力)を有効にします"
    echo "  -h : 使い方を表示します"
    exit 1
}

# 変数の初期設定(デフォルト値)
file_path=""
debug_mode=false

# オプション解析の実行
# ※ "f:dh" ➔ -f は値を必要とし、-d と -h は値が不要
while getopts "f:dh" opt; do
    case "$opt" in
        f)
            file_path="$OPTARG"
            ;;
        d)
            debug_mode=true
            ;;
        h)
            usage
            ;;
        \?)
            # 定義されていないオプションが指定された場合
            echo "エラー: 無効なオプションです。" >&2
            usage
            ;;
    esac
done

# --- 必須チェック ---
if [ -z "$file_path" ]; then
    echo "エラー: 処理対象のファイル(-f)が指定されていません。" >&2
    usage
fi

# --- メイン処理 ---
if [ "$debug_mode" = true ]; then
    echo "[DEBUG] デバッグ表示が有効です"
    echo "[DEBUG] 処理ファイル: $file_path"
fi

echo "ファイルを処理中: $file_path..."
# 実際の処理がここに続く

実行結果:

# ./test1.sh -d -f ./data.txt 
[DEBUG] デバッグ表示が有効です
[DEBUG] 処理ファイル: ./data.txt
ファイルを処理中: ./data.txt...

次にOPTIND環境変数を確認します。
位置パラメータ$0はスクリプト名になるため、OPTINDは1から始まる点にご注意ください。

#!/bin/bash

while getopts "ab" opt; do
    case "$opt" in
        a) echo "A オプション";;
        b) echo "B オプション";;
    esac
done

# getopts が読み終えた位置から通常引数を取り出す
shift $((OPTIND - 1))

echo "残りの引数: $@"

実行結果:

# ./test1.sh  -a -b foo bar baz
A オプション
B オプション
残りの引数: foo bar baz

💡 ポイント:エラーハンドリング(\? の処理)

getopts の判定処理において、登録されていない無効なオプション(例:定義していない `-z` など)が指定された場合、シェルは自動的に ?(バックスラッシュでエスケープして \?)を渡します。これを使って、不適切な入力を検知した際の処理を記述できます。

getoptsで処理した後に通常パラメータを受け取る方法

getopts を使うと、オプション(`-f file.txt` や `-d`)を取得することができますが、オプションの指定が終わった後に、さらに「通常の引数(ファイルの一覧など)」を受け取りたいケースがあります。

その場合は、getopts のループが終わった直後に shift コマンドを使用して、オプション部分の引数を「ゴミ箱」へ捨てる処理を行います。

#!/bin/bash

while getopts "ab" opt; do
    case "$opt" in
        a) echo "A オプション";;
        b) echo "B オプション";;
        \?) echo "引数に問題があります。";;
    esac
done

# getopts が読み終えた位置から通常引数を取り出す
echo "shift実行前の引数:$@"
shift $((OPTIND - 1))
echo "shift実行後の引数: $@"

実行結果:

# ./test1.sh  -a -b foo bar baz
A オプション
B オプション
shift実行前の引数:-a -b foo bar baz
shift実行後の引数: foo bar baz

この shift $((OPTIND - 1)) は、オプション解析を行うシェルスクリプトの定番の後始末処理ですので、セットで覚えておきましょう。

まとめ

getopts を導入することで、記述順序に縛られないシェルスクリプトを作ることができます。

実務でスクリプトを配布したり、自動化運用で複雑な条件の切り替えが必要になった際は、ぜひこの getopts を活用してください!

コメント

タイトルとURLをコピーしました