そういう悩みを抱えているサーバー管理者は多いです。
bashスクリプトを書き始めると、最初は位置パラメータ($1、$2…)で引数を受け取ります。しかしスクリプトが育ってきて「-v(詳細表示)」「-o 出力先」「-n(ドライラン)」といったオプションが増えてくると、位置パラメータでの管理はすぐに破綻します。定義していないオプションが渡されてもエラーを出さず、サイレントに誤動作するケースも出てきます。
この記事では、bashの組み込みコマンド
getopts を使ったオプション解析の方法を解説します。基本的な1文字オプションの受け取り方から、引数付きオプション、エラー処理、デフォルト値の設計、長いオプション(--verbose)への対応まで、RHEL 9.4 / Rocky Linux 9.4 / Ubuntu 24.04 LTS で動作確認した手順でお伝えします。
この記事のポイント
・getopts は bash 組み込みのオプション解析コマンドで外部コマンド不要
・while getopts "ab:c" opt; do case $opt in で1文字オプションを処理できる
・コロン付き(b:)は引数ありオプション、$OPTARG で値を取得する
・先頭コロン(":ab:")でサイレントモードにしてエラーを自前処理できる
・shift $((OPTIND - 1)) でオプション処理後の位置引数を詰め直すのが必須
・長いオプション(--verbose)が必要なときは外部コマンド getopt に切り替える
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
getoptsとは何か、getoptとの違い
シェルスクリプト入門でよく書くのが、次のような位置パラメータ依存の書き方です。#!/bin/bash SRC=$1 DST=$2 LOG=$3 rsync -av "$SRC" "$DST" >> "$LOG"
この問題を解決するのが
getopts です。オプション名(-v や -o)で引数を識別するため、渡す順番が変わっても正しく動作します。getopts は bash の組み込みコマンドです。command -v getopts を実行しても外部ファイルのパスは返らず、getopts is a shell builtin と表示されます。似た名前の
getopt(末尾に s がない)は外部コマンドで、長いオプション(--verbose など)を扱える一方、OS によって挙動が異なり移植性に課題があります。| 比較 | getopts(bash組み込み) | getopt(外部コマンド) |
|---|---|---|
| 長いオプション | 非対応 | 対応(--verbose 等) |
| 移植性 | 高い(POSIX準拠) | OS 差異あり |
| インストール不要 | はい | util-linux パッケージ必要 |
| 実務での用途 | 社内スクリプト・自動化 | 汎用 CLI ツール作成 |
getopts で十分です。getoptsが得意なこと:
・フラグ型オプション(-v・-d)の処理
・引数ありオプション(-o output.log)の処理
・未定義オプションの検出とエラーメッセージ出力
・オプション終端(--)以降の引数を位置引数として扱う
getoptsが苦手なこと(外部コマンド getopt との違い):
・長いオプション(--output)への対応(外部コマンド
getopt が必要)・macOS と Linux 両方で動く長いオプション処理(互換性の問題がある)
運用サーバーで動かす社内スクリプトには、POSIX準拠で安全に動く
getopts(ショートオプション専用)が最適です。getoptsの基本的な書き方
1. オプション文字列の読み方
getopts の第1引数は「オプション文字列」です。# オプション文字列の例 # "abc" → -a -b -c を受け取る(引数なし) # "ab:c" → -a -b 値 -c を受け取る(b は引数あり) # ":ab:c" → 先頭のコロンはサイレントモード(エラー処理を自前で行う)
: が文字の直後に付いている場合、そのオプションは引数を必要とします。先頭に
: が付いている場合は「サイレントモード」となり、エラーメッセージを getopts 自身が出力せず、スクリプト側で制御できます。2. while ループと case 文の組み合わせ
getopts は1回の呼び出しで1つのオプションを処理します。while ループで繰り返し呼び出すのが定石です。#!/bin/bash # 基本的な getopts の書き方 while getopts "vn:o:" opt; do case $opt in v) VERBOSE=true ;; n) NAME="$OPTARG" ;; o) OUTPUT="$OPTARG" ;; \?) echo "不明なオプション: -$OPTARG" >&2 exit 1 ;; esac done # オプション以外の残り引数は $@ に残る shift $((OPTIND - 1)) echo "NAME=$NAME, OUTPUT=$OUTPUT, VERBOSE=$VERBOSE" echo "残り引数: $@"
・$OPTARG:引数ありオプション(コロン付き)で、その引数の値が格納されます
・$OPTIND:次に処理するべき引数のインデックス。
shift $((OPTIND - 1)) で残り引数を $@ に整理できます・\?:未定義のオプションが指定された場合に一致するパターンです
3. サイレントモードで不明オプションを制御する
定義していないオプションが渡されたとき、getopts の動作はオプション文字列の先頭に : があるかどうかで変わります。| 定義文字列 | 不明オプション時の動作 | $opt に入る値 |
|---|---|---|
"vn:o:"(先頭に:なし) |
getopts がエラーメッセージを自動出力 | ? |
":vn:o:"(先頭に:あり) |
エラーメッセージを抑制(サイレントモード) | ? または : |
: を付けるサイレントモードが推奨されます。エラーメッセージを自分でコントロールできるため、ヘルプへの誘導も自由に書けます。サイレントモードでは case 文に
:) パターンを追加します。これは「引数が必要なオプションに引数が与えられなかった」場合に一致します。#!/bin/bash # サイレントモード(先頭に : を付ける) while getopts ":vn:o:" opt; do case $opt in v) VERBOSE=true ;; n) NAME="$OPTARG" ;; o) OUTPUT="$OPTARG" ;; :) # 引数が必要なオプションに引数が与えられなかった場合 echo "エラー: -$OPTARG には引数が必要です" >&2 exit 1 ;; \?) echo "エラー: 不明なオプション -$OPTARG" >&2 exit 1 ;; esac done shift $((OPTIND - 1))
$ ./myscript.sh -x エラー: 不明なオプション -x $ ./myscript.sh -o エラー: -o には引数が必要です
4. 実際に動かす
上記スクリプトをtest-opts.sh として保存して実行します。# 実行権限を付与 $ chmod +x test-opts.sh # オプションを渡して実行 $ ./test-opts.sh -v -n "webserver01" -o /tmp/result.log NAME=webserver01, OUTPUT=/tmp/result.log, VERBOSE=true 残り引数: # 残り引数(ファイル名など)も渡す場合 $ ./test-opts.sh -n "db01" /etc/hosts /etc/hostname NAME=db01, OUTPUT=, VERBOSE= 残り引数: /etc/hosts /etc/hostname
引数ありオプションのエラー処理
1. オプションの引数が省略された場合
コロン付きオプションに引数を与えずに実行すると、getopts はエラーを報告します。# -n オプションの引数を省略した場合(通常モード) $ ./test-opts.sh -n ./test-opts.sh: option requires an argument -- 'n' 不明なオプション: -
2. usageメッセージを関数化する
実務では usage 表示を関数として定義しておくと、-h オプションや複数のエラー箇所からまとめて呼び出せて便利です。usage メッセージは stdout ではなく stderr(>&2)に出力するのがポイントです。パイプで受け取る側に余分な行が混入しません。#!/bin/bash usage() { cat <
&2; usage ;; \?) echo "エラー: 不明なオプション -$OPTARG" >&2; usage ;; esac done shift $((OPTIND - 1)) # 必須オプションのチェック if [ -z "$NAME" ]; then echo "エラー: -n オプション(ホスト名)は必須です" >&2 usage fi echo "ホスト: $NAME, 出力: $OUTPUT, 詳細: ${VERBOSE:-false}"
$ ./myscript.sh -h 使い方: ./myscript.sh [OPTIONS] [ファイル名...] OPTIONS: -v 詳細表示モード -n ホスト名 対象ホスト(必須) -o 出力先 ログの保存先(デフォルト: /tmp/result.log) -h このヘルプを表示する $ ./myscript.sh -z エラー: 不明なオプション -z 使い方: ./myscript.sh [OPTIONS] [ファイル名...] ...
exit 1 を入れることです。エラー処理と -h オプションの両方から1行で呼び出せて、コードの重複をなくせます。実務で使えるスクリプト例
1. デフォルト値と環境変数を組み合わせた設計
引数のデフォルト値はgetopts ループの前に変数に代入しておきます。環境変数の設定と組み合わせると、環境ごとに異なる値を外から上書きできます。#!/bin/bash # デフォルト値と環境変数の組み合わせ # 環境変数で上書き可能なデフォルト値 OUTPUT="${APP_OUTPUT:-/tmp/app.log}" COUNT="${APP_COUNT:-100}" TIMEOUT="${APP_TIMEOUT:-30}" while getopts ":o:n:t:h" opt; do case $opt in o) OUTPUT="$OPTARG" ;; n) COUNT="$OPTARG" ;; t) TIMEOUT="$OPTARG" ;; h) usage; exit 0 ;; :) echo "エラー: -$OPTARG には引数が必要です" >&2; exit 1 ;; \?) echo "エラー: 不明なオプション -$OPTARG" >&2; exit 1 ;; esac done shift $((OPTIND - 1)) echo "OUTPUT : $OUTPUT" echo "COUNT : $COUNT" echo "TIMEOUT: $TIMEOUT"
# デフォルト値で実行 $ bash myscript.sh OUTPUT : /tmp/app.log COUNT : 100 TIMEOUT: 30 # 環境変数で上書き $ APP_COUNT=500 bash myscript.sh OUTPUT : /tmp/app.log COUNT : 500 TIMEOUT: 30 # オプションで最優先指定(環境変数より優先される) $ APP_COUNT=500 bash myscript.sh -n 10 OUTPUT : /tmp/app.log COUNT : 10 TIMEOUT: 30
2. サーバー死活確認スクリプト
複数ホストへの ping チェックを行うスクリプトです。オプションでタイムアウトや繰り返し回数を変更できます。#!/bin/bash # check-hosts.sh: サーバー死活確認スクリプト # 使い方: ./check-hosts.sh [-v] [-c 回数] [-t タイムアウト秒] ホスト名 [ホスト名...] COUNT=3 TIMEOUT=5 VERBOSE=false usage() { echo "使い方: $0 [-v] [-c 回数] [-t タイムアウト秒] ホスト名..." >&2 exit 1 } while getopts ":vc:t:" opt; do case $opt in v) VERBOSE=true ;; c) COUNT="$OPTARG" ;; t) TIMEOUT="$OPTARG" ;; :) echo "エラー: -$OPTARG には引数が必要です" >&2; usage ;; \?) echo "エラー: 不明なオプション -$OPTARG" >&2; usage ;; esac done shift $((OPTIND - 1)) # ホスト名がなければエラー if [ $# -eq 0 ]; then echo "エラー: チェック対象のホスト名を指定してください" >&2 usage fi # 各ホストに ping for host in "$@"; do if ping -c "$COUNT" -W "$TIMEOUT" "$host" > /dev/null 2>&1; then echo "[OK] $host は応答しています" if "$VERBOSE"; then ping -c 1 "$host" | grep "time=" fi else echo "[FAIL] $host は応答しません" fi done
# 基本実行 $ ./check-hosts.sh web01.example.local db01.example.local [OK] web01.example.local は応答しています [OK] db01.example.local は応答しています # -v オプションで詳細表示 $ ./check-hosts.sh -v -c 1 web01.example.local [OK] web01.example.local は応答しています 64 bytes from 192.168.10.11: icmp_seq=1 ttl=64 time=0.312 ms # 存在しないホスト $ ./check-hosts.sh -t 2 backup01.example.local [FAIL] backup01.example.local は応答しません
3. ディレクトリ使用量レポートスクリプト
指定ディレクトリ配下の使用量を調べて、しきい値を超えたものを警告します。Linux 基本コマンドの解説で触れているように、du や ls といった基本コマンドをスクリプト化する際に getopts が役立ちます。
#!/bin/bash # disk-report.sh: ディレクトリ使用量レポート # 使い方: ./disk-report.sh [-d 検索ディレクトリ] [-s しきい値MB] [-v] TARGET_DIR="/var" THRESHOLD=500 VERBOSE=false while getopts ":d:s:v" opt; do case $opt in d) TARGET_DIR="$OPTARG" ;; s) THRESHOLD="$OPTARG" ;; v) VERBOSE=true ;; :) echo "エラー: -$OPTARG には引数が必要です" >&2; exit 1 ;; \?) echo "エラー: 不明なオプション -$OPTARG" >&2; exit 1 ;; esac done echo "=== ディスク使用量レポート ===" echo "対象: $TARGET_DIR / しきい値: ${THRESHOLD}MB" echo "" # しきい値(MB)を超えるディレクトリを表示 du -mx "$TARGET_DIR" --max-depth=2 2>/dev/null \ | awk -v threshold="$THRESHOLD" '$1 > threshold {print $0}' \ | sort -rn \ | while read size path; do echo "[警告] ${size}MB $path" done if "$VERBOSE"; then echo "" echo "--- 上位10件(全体) ---" du -mx "$TARGET_DIR" --max-depth=2 2>/dev/null | sort -rn | head -10 fi
4. ログアーカイブスクリプト(完成形)
ここまでの要素(usage 関数・デフォルト値・サイレントモード・必須チェック)をまとめた実務想定のスクリプトです。Rocky Linux 9.4 の実機で動作確認しています。#!/bin/bash # log_archive.sh - ログアーカイブの引数設計サンプル set -eu SCRIPT=$(basename "$0") usage() { cat <
ログディレクトリ内の古いファイルをアーカイブします。 OPTIONS: -o アーカイブ先ディレクトリ(必須) -d 何日より古いファイルを対象にするか(デフォルト: 30) -v 詳細出力 -n ドライラン(実際には移動しない) -h このヘルプを表示する Example: $SCRIPT -o /backup/archive -d 14 -v /var/log/app EOF } ARCHIVE_DIR="" DAYS=30 VERBOSE=0 DRY_RUN=0 while getopts ":o:d:vnh" opt; do case "$opt" in o) ARCHIVE_DIR="$OPTARG" ;; d) DAYS="$OPTARG" ;; v) VERBOSE=1 ;; n) DRY_RUN=1 ;; h) usage; exit 0 ;; :) echo "Error: -$OPTARG には引数が必要です" >&2; usage >&2; exit 1 ;; \?) echo "Error: 不明なオプション -$OPTARG" >&2; usage >&2; exit 1 ;; esac done shift $((OPTIND - 1)) # 必須チェック [ -z "$ARCHIVE_DIR" ] && { echo "Error: -o は必須です" >&2; usage >&2; exit 1; } [ $# -lt 1 ] && { echo "Error: log_dir を指定してください" >&2; usage >&2; exit 1; } LOG_DIR="$1" [ ! -d "$LOG_DIR" ] && { echo "Error: $LOG_DIR が見つかりません" >&2; exit 1; } [ "$DRY_RUN" -eq 0 ] && mkdir -p "$ARCHIVE_DIR" [ "$VERBOSE" -eq 1 ] && echo "[INFO] ログ対象: $LOG_DIR / 保存先: $ARCHIVE_DIR / 対象: ${DAYS}日以前" find "$LOG_DIR" -maxdepth 1 -type f -mtime +"$DAYS" | while read -r f; do if [ "$DRY_RUN" -eq 1 ]; then echo "[DRY-RUN] mv \"$f\" \"$ARCHIVE_DIR/\"" else mv "$f" "$ARCHIVE_DIR/" [ "$VERBOSE" -eq 1 ] && echo "[MOVED] $f" fi done echo "完了"
# ドライランで動作確認(実際には移動しない) $ bash log_archive.sh -o /backup/archive -d 14 -v -n /var/log/app [INFO] ログ対象: /var/log/app / 保存先: /backup/archive / 対象: 14日以前 [DRY-RUN] mv "/var/log/app/access-20260724.log" "/backup/archive/" [DRY-RUN] mv "/var/log/app/error-20260718.log" "/backup/archive/" 完了 # 必須引数なしでエラーと usage を同時に表示 $ bash log_archive.sh /var/log/app Error: -o は必須です Usage: log_archive.sh [OPTIONS]
...
長いオプション(--verbose)に対応するには(getopt コマンド)
bash 組み込みのgetopts は1文字の短いオプション(-v、-f)のみに対応しています。--verbose や --file=xxx のような長いオプションが必要なときは、外部コマンドの getopt(util-linux パッケージ)を使います。注意:macOS のデフォルト
getopt(BSD 版)は GNU 版と挙動が異なり、long option に対応していません。macOS 環境で長いオプションを使いたい場合は brew install gnu-getopt で GNU 版を別途インストールしてください。スクリプトをチームで共有する場合は、動作環境を README に明記しておくことを推奨します。#!/bin/bash set -euo pipefail usage() { echo "使い方: $0 [--verbose] [--file FILE] [--output DIR] 引数..." >&2 exit 1 } # GNU getopt でパース(Linux / bash 環境前提) OPTS=$(getopt -o vf:o: --long verbose,file:,output: -n "$0" -- "$@") || usage eval set -- "$OPTS" VERBOSE=0 INPUT_FILE="" OUTPUT_DIR="./output" while true; do case "$1" in -v | --verbose) VERBOSE=1; shift ;; -f | --file) INPUT_FILE="$2"; shift 2 ;; -o | --output) OUTPUT_DIR="$2"; shift 2 ;; --) shift; break ;; *) echo "不正なオプション: $1" >&2; usage ;; esac done echo "VERBOSE=$VERBOSE, INPUT_FILE=$INPUT_FILE, OUTPUT_DIR=$OUTPUT_DIR" echo "残り引数: $*"
$ bash long-opts.sh --verbose --file data.csv --output /tmp /path/to/src VERBOSE=1, INPUT_FILE=data.csv, OUTPUT_DIR=/tmp 残り引数: /path/to/src # short option と long option を混在させても動作する $ bash long-opts.sh -v --output /tmp /path/to/src VERBOSE=1, INPUT_FILE=, OUTPUT_DIR=/tmp 残り引数: /path/to/src
getopt を使うポイントは、解析後に eval set -- "$OPTS" で位置パラメータを再設定し、通常の while+case ループで処理する点です。短いオプションと長いオプションを混在させても同じ変数に集約できます。getoptsで陥りやすいトラブルと対処法
1. shift $((OPTIND - 1)) を忘れた場合
getopts はオプションを処理しても $1 以降の引数を自動でシフトしません。shift $((OPTIND - 1)) を忘れると、while ループ後でも $@ にオプション文字が残ったままになります。# OPTIND を確認する $ cat debug-opts.sh #!/bin/bash while getopts "vn:" opt; do case $opt in v) echo "OPTIND=$OPTIND, opt=$opt" ;; n) echo "OPTIND=$OPTIND, opt=$opt, OPTARG=$OPTARG" ;; esac done echo "shift 前: $@" shift $((OPTIND - 1)) echo "shift 後: $@" $ ./debug-opts.sh -v -n server01 file1.txt file2.txt OPTIND=2, opt=v OPTIND=4, opt=n, OPTARG=server01 shift 前: -v -n server01 file1.txt file2.txt shift 後: file1.txt file2.txt
2. オプション引数にハイフンから始まる値を渡してしまう
-n -verbose のように渡すと、getopts は -verbose をオプションとして解釈しようとしてエラーになります。引数の値がハイフンから始まる可能性がある場合は、
-n="-verbose" や --(オプション終了マーカー)を使う回避策を検討してください。3. 関数の中で getopts を使う場合の注意
関数内でgetopts を呼ぶ場合、$OPTIND はグローバル変数のため、関数入口で local OPTIND=1 と宣言することで意図しない動作を防げます。同じ関数を複数回呼ぶときも OPTIND が前回の値のまま残らず、正しく動作します。# 関数内で getopts を使う正しい書き方 parse_options() { local OPTIND=1 # 必須:関数内でリセット local opt while getopts "vn:" opt; do case $opt in v) echo "verbose mode" ;; n) echo "name=$OPTARG" ;; esac done } parse_options -v -n test01 parse_options -n server02
4. 数値オプションの入力値を検証していない
getopts は型チェックをしません。-n abc のように数値を期待するオプションに文字列が渡されると、後続の計算処理でエラーになります。受け取った後に自前でバリデーションしてください。Linux ポート確認の全コマンドのように、スクリプトでポート番号をオプション引数で受け取る場合は、数値バリデーションも合わせて実装しておくと安全です。
# ポート番号のバリデーション例 while getopts ":p:n:" opt; do case $opt in p) if ! echo "$OPTARG" | grep -qE '^[0-9]+$' || [ "$OPTARG" -lt 1 ] || [ "$OPTARG" -gt 65535 ]; then echo "エラー: ポート番号は 1~65535 の整数で指定してください" >&2 exit 1 fi PORT="$OPTARG" ;; n) if ! echo "$OPTARG" | grep -qE '^[0-9]+$'; then echo "エラー: -n には正の整数を指定してください" >&2 exit 1 fi COUNT="$OPTARG" ;; :) echo "エラー: -$OPTARG には引数が必要です" >&2; exit 1 ;; \?) echo "エラー: 不明なオプション -$OPTARG" >&2; exit 1 ;; esac done echo "指定ポート: $PORT"
本記事のまとめ
| やりたいこと | 書き方 |
|---|---|
| 引数なしオプション(-v)を受け取る | while getopts "v" opt; do case $opt in v) … |
| 引数ありオプション(-n 値)を受け取る | while getopts "n:" opt、値は $OPTARG |
| 不明なオプションをサイレントに捕捉する | 先頭コロン ":n:" + case の \?) パターンで処理 |
| 引数省略エラーを自前処理する | 先頭コロン ":n:" + case の :) パターン |
| ヘルプを表示する | usage 関数に集約し -h と \? と : ケースから呼び出す |
| オプション処理後に残り引数を取り出す | shift $((OPTIND - 1)) |
| デフォルト値を設定する | getopts ループの前に変数代入。環境変数との組み合わせも有効 |
| 関数内で getopts を使う | 関数の冒頭で local OPTIND=1 を宣言 |
| 数値オプションの値を検証する | 受け取り後に grep -qE '^[0-9]+$' でバリデーション |
| 長いオプション(--verbose)に対応する | 外部コマンド getopt --long(GNU 版・util-linux)を使う |
getopts を覚えると、「とりあえず動く一回きりのスクリプト」から「オプションで柔軟に動作を変えられる再利用可能なスクリプト」へ一段階昇格できます。コロンの付け方と
$OPTARG・$OPTIND の役割さえ把握すれば、使いこなすのは難しくありません。まずは手元のスクリプトで
$1・$2 による引数受け取りを getopts に書き換えてみてください。シェルスクリプトのメンテナビリティが一気に上がります。
ls コマンドの基本オプションやcsh 環境変数設定の解説のような基本コマンドを組み合わせて、本格的なシェルスクリプトを書いていきましょう。
「スクリプトが書けるLinuxエンジニア」になりたいなら
getopts を使いこなせると、メンテナブルな自動化スクリプトが書けるようになります。
ネットの古い情報をコピペするだけでなく、現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。
「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 次のページへ:trapコマンドでbashスクリプトのシグナルを捕捉・処理する方法|一時ファイル削除やエラー終了処理の実践例も
- 前のページへ:sarコマンドでシステムパフォーマンスを時系列で確認する方法|CPU・メモリ・ディスク統計の読み方と活用も
- この記事の属するカテゴリ:Linuxtips・シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます