2026年6月27日

2026年6月27日

bashのgetoptsで引数(オプション)を処理する方法|OPTARG・OPTIND・エラー処理まで

シェルスクリプトに -v や -o ファイル名 のようなオプションを持たせたいときは、bash の組み込みコマンド getopts を使うのが基本です。while getopts と case を組み合わせるだけで、-vo out.txt のようなまとめ書きや、未知のオプションの検出まで扱えます。この記事では、オプション文字列の書き方、OPTARG と OPTIND の意味、エラー処理、必須オプションの確認、関数の中で使うときの注意、そして getopts が扱えない長いオプション(--help など)への対応方法までを説明します。

getopts は POSIX で定められたシェル組み込みコマンドで、bash 以外の sh でも基本的な書き方は同じです。(2026年10月現在)この記事のコードは bash 5 系で動作を確認できる内容にしています。

基本形:while getopts と case

まずは完成形を見てください。-h(ヘルプ)、-v(詳細表示)、-o ファイル(出力先)を受け付けるスクリプトです。

#!/usr/bin/env bash
set -euo pipefail

usage() {
  cat <<'EOF' >&2
使い方: backup.sh [-h] [-v] [-o 出力ファイル] 対象ディレクトリ...
  -h  このヘルプを表示
  -v  詳細を表示(-vv でさらに詳しく)
  -o  出力ファイル名
EOF
}

verbose=0
output="backup.tar.gz"

while getopts ":hvo:" opt; do
  case "$opt" in
    h) usage; exit 0 ;;
    v) verbose=$(( verbose + 1 )) ;;
    o) output="$OPTARG" ;;
    :) echo "エラー: -$OPTARG には値が必要です" >&2; usage; exit 2 ;;
    \?) echo "エラー: 不明なオプション -$OPTARG" >&2; usage; exit 2 ;;
  esac
done
shift $(( OPTIND - 1 ))

if (( $# == 0 )); then
  echo "エラー: 対象ディレクトリを指定してください" >&2
  usage
  exit 2
fi

(( verbose >= 1 )) && echo "出力先: $output" >&2
tar -czf "$output" -- "$@"

./backup.sh -v -o site.tar.gz wp-content のように実行すると、-v と -o が処理されたあと、残りの wp-content が "$@" に入ります。

オプション文字列の読み方

getopts ":hvo:" opt の ":hvo:" がオプションの定義です。

  • 文字そのもの(h v)は、値を取らないオプション
  • 後ろに : が付いた文字(o:)は、値を1つ取るオプション
  • 先頭の : は「エラーメッセージを自分で出す」モード(後述)

値を取るオプションは -o out.txt と空白を空けても、-oout.txt と続けて書いても受け付けます。値を取らないオプションは -v -h を -vh とまとめられ、-vo out.txt のように「最後だけ値付き」のまとめ書きもできます。これらを自前の case "$1" で解析しようとすると手間がかかるため、短いオプションだけなら getopts に任せるのが手堅い選択です。

OPTARG と OPTIND

getopts は呼ばれるたびにオプションを1つ読み、次の変数を設定します。

  • 第2引数の変数(例では opt): 見つかったオプションの文字
  • OPTARG: 値を取るオプションの値。エラー時は問題のあったオプション文字
  • OPTIND: 次に処理する引数の位置(1から始まる)

オプションが尽きると getopts は失敗を返し、while ループが終わります。このとき OPTIND は「最初のオプションでない引数」の位置を指しているので、shift $(( OPTIND - 1 )) で処理済みのオプションを取り除けば、$1 以降が残りの引数になります。この shift を忘れると、"$@" にオプションが残ったまま後続の処理に渡ってしまいます。

エラー処理:先頭の : の有無で挙動が変わる

オプション文字列の先頭に : を付けるかどうかで、エラー時の動きが変わります。

状況 先頭に : なし 先頭に : あり
未知のオプション -x bash がメッセージを表示し、opt に ? メッセージなし。opt に ?、OPTARG に x
-o の値がない bash がメッセージを表示し、opt に ? メッセージなし。opt に :、OPTARG に o

先頭に : を付けると、未知のオプションと値の不足を区別でき、日本語のメッセージや usage の表示を自分で制御できます。基本形の例のように : と \? の2つの分岐を書いておくのがおすすめです。case で ? をそのまま書くと「任意の1文字」というパターンになり、すべてのオプションに一致してしまうため、\? とエスケープするか '?' と引用符で囲みます。

終了ステータスは、使い方の誤りなら2を返す慣習が多く見られます。cron やほかのスクリプトから呼ぶ場合、通常の失敗(1)と区別できると原因を追いやすくなります。

必須オプションと値の検証

getopts には「このオプションは必須」という指定はありません。ループのあとで変数が空かどうかを確認します。

env=""
dry_run=0

while getopts ":e:n" opt; do
  case "$opt" in
    e) env="$OPTARG" ;;
    n) dry_run=1 ;;
    :) echo "エラー: -$OPTARG には値が必要です" >&2; exit 2 ;;
    \?) echo "エラー: 不明なオプション -$OPTARG" >&2; exit 2 ;;
  esac
done
shift $(( OPTIND - 1 ))

case "$env" in
  staging|production) ;;
  "") echo "エラー: -e で環境を指定してください" >&2; exit 2 ;;
  *)  echo "エラー: -e は staging か production を指定してください" >&2; exit 2 ;;
esac

値は許可する候補と照合しておくと、タイプミスで想定外の環境に対して処理が走る事故を防げます。数値を受け取るなら [[ $OPTARG =~ ^[0-9]+$ ]] のように形式も確認してください。WordPress の運用スクリプトなら、-n のような「実際には変更しないで内容だけ表示する」オプションを用意しておくと、本番での実行前に確認できて安全です。データベースを書き換える処理を含む場合は、先に wp db export などでバックアップを取る流れを組み込んでおきます。

値の位置に別のオプションが来る問題

getopts は、値を取るオプションの直後にある引数を、たとえ - で始まっていても値として扱います。./script.sh -o -v と実行すると、output に -v が入り、-v はオプションとして処理されません。値が - で始まるのは不自然だと判断できる場合は、次のように弾いておくと誤操作に気付けます。

o)
  if [[ $OPTARG == -* ]]; then
    echo "エラー: -o の値がありません" >&2; exit 2
  fi
  output="$OPTARG" ;;

同じオプションを複数回受け取る

-v を重ねて詳細度を上げる処理は、基本形のようにカウンタを増やすだけで実現できます。-e a -e b のように値を複数受け取りたい場合は、配列に追加します。

excludes=()
while getopts ":x:" opt; do
  case "$opt" in
    x) excludes+=( "--exclude=$OPTARG" ) ;;
    *) exit 2 ;;
  esac
done
shift $(( OPTIND - 1 ))

tar -czf backup.tar.gz "${excludes[@]}" -- "$@"

配列を "${excludes[@]}" と引用符付きで展開すれば、空白を含む値も1つの引数として渡せます。

オプションの後ろに引数を置く必要がある

getopts は、オプションでない引数が現れた時点で解析を終えます。./backup.sh wp-content -v と実行すると、-v はオプションとして扱われず、wp-content と -v の両方が残りの引数になります。オプションは引数より前に書く、という前提で usage を書いておくと利用者が迷いません。

また、-- を書くとそこでオプションの解析が終わります。- で始まるファイル名を引数として渡したいときに使えます。

関数の中で getopts を使うときは OPTIND を local にする

OPTIND はシェル全体で共有される変数です。関数の中で getopts を使い、その関数を2回呼ぶと、2回目は前回の OPTIND の続きから読もうとしてオプションを取りこぼします。

log() {
  local OPTIND opt level="INFO"
  while getopts "l:" opt; do
    case "$opt" in
      l) level="$OPTARG" ;;
      *) return 2 ;;
    esac
  done
  shift $(( OPTIND - 1 ))
  printf '[%s] %s\n' "$level" "$*" >&2
}

log -l WARN "ディスク残量が少なくなっています"
log -l ERROR "バックアップに失敗しました"

local OPTIND と書くと、関数に入るたびに OPTIND が初期化され、呼び出し元の値も壊しません。

長いオプション(--help)を使いたい場合

getopts が扱えるのは1文字の短いオプションだけです。--help や --output=file を受け付けたい場合は、次の2つの方法があります。

方法1:自前のループで解析する

依存がなく、どの環境でも動く方法です。

output="backup.tar.gz"
while (( $# > 0 )); do
  case "$1" in
    -h|--help)  usage; exit 0 ;;
    -o|--output)
      [[ $# -ge 2 ]] || { echo "エラー: $1 には値が必要です" >&2; exit 2; }
      output="$2"; shift 2 ;;
    --output=*) output="${1#*=}"; shift ;;
    --) shift; break ;;
    -*) echo "エラー: 不明なオプション $1" >&2; exit 2 ;;
    *)  break ;;
  esac
done

この書き方では -vo のようなまとめ書きは扱えませんが、オプションの種類が少ないスクリプトなら十分です。

方法2:GNU の getopt コマンドを使う

getopts(末尾に s がある組み込みコマンド)とは別に、getopt(s なし)という外部コマンドがあります。Linux では util-linux に含まれる拡張版が使え、長いオプションと引数の並べ替えに対応しています。

parsed=$(getopt -o hvo: --long help,verbose,output: -n "$0" -- "$@") || exit 2
eval set -- "$parsed"

while true; do
  case "$1" in
    -h|--help)    usage; exit 0 ;;
    -v|--verbose) verbose=$(( verbose + 1 )); shift ;;
    -o|--output)  output="$2"; shift 2 ;;
    --) shift; break ;;
  esac
done

getopt は引数を安全に引用符付きで並べ直した文字列を出力し、eval set -- でそれを位置パラメータに戻します。この組み合わせは定型として崩さずに使ってください。拡張版かどうかは getopt -T を実行し、終了ステータスが4なら拡張版と判断できます。macOS 標準の getopt は古い BSD 版で長いオプションに対応していないため、移植性が必要なら方法1を選びます。

まとめ

  • 短いオプションは while getopts ":hvo:" opt; do case ... done で処理し、値を取るオプションには : を付ける
  • ループの後は shift $(( OPTIND - 1 )) で残りの引数を "$@" に残す
  • 先頭に : を付けると、未知のオプション(\?)と値の不足(:)を自分で区別して処理できる
  • 必須オプションと値の妥当性は、ループの後で明示的に確認する
  • 関数の中で使うときは local OPTIND を宣言する
  • 長いオプションは自前のループか、Linux なら util-linux の getopt で対応する

お気軽にご相談ください

お見積りへ お問い合わせへ
▲