シェルスクリプトで業務を自動化する際、処理対象のファイルパスや環境名、実行フラグをスクリプト外部から受け取る「コマンドライン引数」のハンドリングは必須のスキルです。実行するたびにスクリプト本体の変数書き換えを行っているようでは、運用の自動化とは言えません。「手作業は負け」――引数を柔軟かつ安全に受け取れるスクリプトを設計しましょう。
しかし、現場の実務では 「引数が10個以上になると意図しない値が入る」 「引数に空白が含まれているとループで単語がバラバラに割れてしまう」 「ハイフン付きオプションの解析が複雑で挫折した」 といったトラブルが頻発します。
本記事では、基本の位置パラメータ( $1 〜 $9 、 ${10} )の取り扱いから、現場で最重要となる "$@" と "$*" の決定的な違い 、引数の存在チェック・デフォルト値代入、そして本格的な getopts によるオプション引数の解析テンプレート まで、Bash玄が徹底解説します。
【アンサーファースト】コピペで即動く!引数チェック&オプション解析付きスクリプトテンプレート
実務でそのままコピー&ペーストして使える「引数バリデーション+Usage表示+オプション解析付きスクリプト」の完成形テンプレートです。必要なオプションと必須引数の両方を安全に処理できます。
#!/usr/bin/env bash
set -euo pipefail
# 使い方(Usage)を表示する関数
usage() {
cat << 'USAGE_END' >&2
Usage: ./deploy.sh [OPTIONS] <ENVIRONMENT> <APP_NAME>
Arguments:
ENVIRONMENT デプロイ先環境(dev | stg | prd)
APP_NAME 対象アプリケーション名
Options:
-f 強制実行フラグ(Force)
-v 詳細ログ出力(Verbose)
-p PORT 指定ポート番号(デフォルト: 8080)
-h このヘルプを表示
USAGE_END
exit 1
}
# デフォルト値の設定
FORCE=0
VERBOSE=0
PORT=8080
# 1. getoptsによるオプション解析
while getopts "fvp:h" opt; do
case "${opt}" in
f) FORCE=1 ;;
v) VERBOSE=1 ;;
p) PORT="${OPTARG}" ;;
h) usage ;;
*) usage ;;
esac
done
shift "$((OPTIND - 1))"
# 2. 必須引数の個数チェック($# で判定)
if [ $# -lt 2 ]; then
echo "エラー: 必要な引数(ENVIRONMENT, APP_NAME)が不足しています。" >&2
usage
fi
# 3. 位置パラメータの取得とデフォルト値展開
ENV="$1"
APP_NAME="$2"
TAG="${3:-latest}" # 第3引数は任意(未指定時は "latest")
# 実行内容の確認表示
echo "=== デプロイ処理開始 ==="
echo "環境: ${ENV}"
echo "アプリ名: ${APP_NAME}"
echo "タグ: ${TAG}"
echo "ポート: ${PORT}"
echo "強制フラグ: ${FORCE}"
echo "詳細ログ: ${VERBOSE}"
コマンドライン引数の基本変数早見表
スクリプト実行時に渡されたコマンドライン引数は、Bashの「位置パラメータ(Positional Parameters)」や引数関連の特殊変数に自動的に格納されます。まずは全体像を早見表で整理しましょう。
$0, $1〜$9, ${10}〜(2桁以上の波括弧ルール)
, 〜, 〜(2桁以上の波括弧ルール)
スクリプト名自身や各引数は、番号付きの変数として参照します。
| 変数名 | 格納される内容 | 実務での具体例・出力イメージ |
|---|---|---|
$0 |
実行されたスクリプト自身の名前(実行パス) | ./deploy.sh や /usr/local/bin/backup |
$1 〜 $9 |
第1引数 〜 第9引数 | $1 は1番目の引数、 $2 は2番目の引数 |
${10} 〜 |
第10引数以降の引数(波括弧 {} が必須) |
${10} は10番目の引数、 ${11} は11番目 |
【要注意】2桁以上の引数には波括弧 {} が絶対に必要!
Bash初心者が現場で必ず遭遇する罠が、第10引数以降の参照ミスです。
# NGな書き方: 波括弧なし
echo "$10"
上記のように $10 と書くと、Bashは 「 $1 の値」の後ろに文字列の「 0 」を連結したもの として解釈してしまいます。たとえば第1引数 $1 が apple だった場合、 $10 は apple0 に展開されます。第10引数以降を正しく取得するには、必ず ${10} や ${11} のように波括弧で囲んでください。普段から "${1}" 、 "${2}" と波括弧とダブルクォートを記述する習慣をつけておくのがベストプラクティスです。
引数関連の特殊変数一覧表($#, $@, $*, $?)
引数の総数や全引数の展開、直前コマンドの成否を扱う特殊変数の一覧です。
| 特殊変数 | 意味・役割 | 主な利用シーン・実務テクニック |
|---|---|---|
$# |
渡された引数の総数(個数) | if [ $# -ne 2 ] による引数不足チェック |
"$@" |
全引数を個別にクォートして展開 | for arg in "$@" によるループ処理(実務の絶対推奨) |
"$*" |
全引数を1つの文字列として連結 | 全引数をまとめて1行でログファイルに記録する際 |
$? |
直前に実行したコマンドの終了ステータス | 0 は成功、 1 以上はエラー発生 |
$$ |
現在実行中のシェルのプロセスID(PID) | 一時ファイル名の一意化(/tmp/work_$$.tmp) |
これで完全理解!「”$@”」と「”$*”」の決定的な違い
すべての引数をまとめて処理したい場合、 "$@" と "$*" という2つの記法が存在します。この2つの違い、そしてダブルクォートで囲むか否かによる挙動の差異は、シェルスクリプト最大の落とし穴の一つです。
以下のように、第1引数にスペースを含む文字列を渡してスクリプトを実行した場合を考えます。
実行例: ./test.sh "hello world" foo bar (計3個の引数)
| 記法 | クォート | 展開後の認識状態 | forループ時の処理回数 |
|---|---|---|---|
"$@" |
あり(推奨) | "hello world" "foo" "bar" (元の引数の区切りを完全保持) |
3回 (正確に3要素として処理) |
"$*" |
あり | "hello world foo bar" (全引数を1個の文字列に連結) |
1回 (巨大な1要素として処理) |
$@ |
なし(非推奨) | hello world foo bar (空白で再分割される) |
4回 (空白割れ事故発生) |
$* |
なし(非推奨) | hello world foo bar (空白で再分割される) |
4回 (空白割れ事故発生) |
クォートなしの場合の空白割れ事故
ダブルクォートを付けずに $@ や $* を使用すると、引数内に含まれるスペースや改行文字がシェルの単語分割(Word Splitting)によってバラバラに分解されてしまいます。
# 危険なスクリプト: word_split.sh
for file in $@; do
echo "処理中: ${file}"
ls -l "${file}"
done
このスクリプトに "My Documents" という空白入りのフォルダ名を渡すと、シェルの単語分割によって My と Documents という2つの別々の引数としてループが回ってしまいます。その結果、 ls: 'My' にアクセスできません: そのようなファイルやディレクトリはありません という致命的なエラーが発生します。クォートなしの $@ や $* は、実務スクリプトでは原則として使用禁止です。
“$@” は「各引数を個別の要素として保持する」(配列ループの鉄板)
渡された引数を1つずつ順番にループ処理したい場合は、必ず "$@"(ダブルクォート付き) を使用します。
#!/usr/bin/env bash
set -euo pipefail
echo "受け取った引数の総数: $#"
# 各引数を個別の要素として安全にループ処理
for arg in "$@"; do
echo "Argument: [${arg}]"
done
実行結果:
$ ./loop_args.sh "Monthly Report.pdf" "data-backup.csv" test.txt
受け取った引数の総数: 3
Argument: [Monthly Report.pdf]
Argument: [data-backup.csv]
Argument: [test.txt]
スペースを含んだファイル名であっても、元の引数の境界が完全に保持されて1要素として安全に処理されていることがわかります。
“$*” は「全引数を1つの文字列に連結する」
一方、 "$*" はすべての引数を 環境変数 IFS(Internal Field Separator、デフォルトは半角スペース)の先頭文字で連結した単一の文字列 として展開します。
#!/usr/bin/env bash
set -euo pipefail
# 全引数を1つの文字列としてログに出力
echo "実行コマンド引数ログ: $*"
# ループ処理すると1回しか回らない
for arg in "$*"; do
echo "Item: [${arg}]"
done
実行結果:
$ ./test_star.sh apple "banana orange" grape
実行コマンド引数ログ: apple banana orange grape
Item: [apple banana orange grape]
全引数をまとめて1行のログファイルに書き出したい場合や、別の外部コマンドにひとまとめの文字列として渡したい場合には "$*" が適していますが、個々の引数を反復処理する場合は必ず "$@" を選択してください。
実務で必須の引数チェック・バリデーションパターン
引数を前提とするスクリプトでは、引数が渡されなかったり想定より少なかったりした場合に、即座に安全に異常終了させるバリデーション処理が不可欠です。
引数の数が足りない場合にエラー終了する(if [ $# -lt 2 ])
実務で最も頻出する引数チェックは、 $# を使った個数チェックです。
# 引数が2個未満の場合はエラー終了
if [ $# -lt 2 ]; then
echo "エラー: 引数は最低2個指定してください。(指定された数: $#)" >&2
echo "使用方法: $0 <INPUT_FILE> <OUTPUT_DIR>" >&2
exit 1
fi
数値比較演算子の使い分け:
-lt: 未満(Less Than)-gt: 超過(Greater Than)-ne: 一致しない(Not Equal)-eq: 等しい(Equal)
引数がちょうど2個でなければならない場合は、 if [ $# -ne 2 ] を使用します。また、エラーメッセージは必ず標準エラー出力( >&2 )へリダイレクトするのが実務の鉄則です。
デフォルト値を設定するパラメータ展開(${1:-default_value})
引数が省略された場合にデフォルトの初期値を使用したい場合、if文を書く必要はありません。Bashの「パラメータ展開(Parameter Expansion)」を使うと、わずか1行でスマートに記述できます。
# 第1引数が未指定・空文字なら "staging" を設定
TARGET_ENV="${1:-staging}"
# 第2引数が未指定・空文字なら "8080" を設定
TARGET_PORT="${2:-8080}"
echo "デプロイ環境: ${TARGET_ENV}, ポート: ${TARGET_PORT}"
| 構文 | 意味・挙動 |
|---|---|
${1:-default} |
$1 が未設定または空文字の場合、 default を返す(変数 $1 自体は変更しない) |
${1:=default} |
$1 が未設定または空文字の場合、 default を $1 に代入して返す(※位置パラメータには直接代入不可のため通常変数で使用) |
${1:?エラーメッセージ} |
$1 が未設定または空文字の場合、メッセージを表示してスクリプトを即時異常終了させる |
特に ${1:?エラー: 第1引数は必須です} は、簡易的な必須チェックとしてワンライナーで重宝します。
shiftコマンドで引数を順番に処理する
shift は、位置パラメータを左に1つずらす(シフトする)コマンドです。 shift を実行すると、 $2 が $1 に、 $3 が $2 に繰り上がり、元の $1 は破棄され、引数の総数 $# も1つ減ります。
#!/usr/bin/env bash
set -euo pipefail
# 先頭2つの必須引数を取得
SRC_DIR="$1"
DEST_DIR="$2"
shift 2 # 引数を2つシフト($3以降が $1 に繰り上がる)
echo "コピー元: ${SRC_DIR}"
echo "コピー先: ${DEST_DIR}"
echo "残りの引数数: $#"
# 残りの引数を順次処理
while [ $# -gt 0 ]; do
echo "追加オプションまたは個別ファイル: $1"
shift
done
固定の引数を受け取った後、残りの可変長引数を処理したい場合に非常に強力なパターンです。
なお、受け取った引数に応じた処理の分岐にはcase文を活用します。サブコマンド(start / stop など)の判定やワイルドカードによる柔軟な引数処理については、「【Bash】case文の書き方完全ガイド!複数条件・ワイルドカードとif文の使い分け」で詳しく解説しています。
本格的なオプション引数を解析する(getopts)
-h や -f 、 -p 8080 といったハイフン付きのコマンドラインオプションを解析するには、Bash組み込みの getopts コマンドを使用します。
getoptsを使った標準的なオプション解析テンプレート(-h, -f, -v)
実務でそのまま使える、標準的な getopts テンプレートです。
#!/usr/bin/env bash
set -euo pipefail
# ヘルプメッセージ表示
show_help() {
cat << 'EOF'
Usage: $0 [OPTIONS] [FILE...]
Options:
-v 詳細ログを有効化(Verbose)
-f 確認をスキップして強制実行(Force)
-o OUTPUT 出力先ファイルを指定
-h このヘルプを表示
EOF
}
VERBOSE=0
FORCE=0
OUTPUT_FILE="output.txt"
# "vfo:h" の意味:
# v, f, h は引数を取らないフラグ
# o: は後ろに引数を取るオプション(コロンが付く)
while getopts "vfo:h" opt; do
case "${opt}" in
v)
VERBOSE=1
;;
f)
FORCE=1
;;
o)
OUTPUT_FILE="${OPTARG}"
;;
h)
show_help
exit 0
;;
?)
echo "エラー: 不正なオプションです: -${OPTARG}" >&2
show_help >&2
exit 1
;;
:)
echo "エラー: オプション -${OPTARG} には引数が必要です。" >&2
show_help >&2
exit 1
;;
esac
done
# 解析済みのオプションを位置パラメータから取り除く
shift "$((OPTIND - 1))"
# ここから先は、通常の引数(残りの位置パラメータ)を処理できる
echo "--- 解析結果 ---"
echo "VERBOSE : ${VERBOSE}"
echo "FORCE : ${FORCE}"
echo "OUTPUT_FILE : ${OUTPUT_FILE}"
echo "残りの引数数 : $#"
for remaining_arg in "$@"; do
echo "処理対象ファイル: ${remaining_arg}"
done
引数を取るオプションと取らないオプションの指定方法
getopts の第1引数に渡す「オプトストリング(オプション定義文字列)」の書き方には、明確なルールがあります。
- 文字単体(例:
v,f):
引数を取らないフラグです。コマンドラインで-vや-fと指定された場合、その文字が変数に格納されます。 - 文字+コロン(例:
o:):
後ろに引数(値)を必要とするオプションです。-o result.txtまたは-oresult.txtのように指定された値が、自動的に環境変数${OPTARG}に格納されます。 - 先頭のコロン(例:
:vfo:h):
先頭に:を付けると「サイレントモード」になり、未定義のオプションや引数不足時にgetoptsが独自の標準エラーを出力するのを抑制し、スクリプト側のcase文(?や:)で独自のエラーハンドリングができるようになります。 OPTINDとshiftの連携:
OPTINDには次に処理すべき引数のインデックスが格納されています。オプション解析が終わった直後にshift "$((OPTIND - 1))"を実行することで、オプション部分がすべて位置パラメータから取り除かれ、$1以降に残りの一般引数(ファイル名など)が綺麗に残ります。
まとめ & 関連記事
Bashスクリプトで引数を安全・確実に受け取るための要点をまとめます。
- 位置パラメータは波括弧を意識する: 第10引数以降は
${10}が必須。普段から"${1}"、"${2}"とダブルクォート&波括弧で囲む習慣をつける。 - 全引数は必ず
"$@"で受ける: ダブルクォートなしの$@や$*は空白割れ事故の元凶。ループ処理には必ず"$@"を採用する。 - getoptsと引数チェックで堅牢なCLIを作る: オプションは
getoptsで解析し、残りの必須引数は$#で個数チェック。デフォルト値は${1:-default}でスマートに設定する。
あわせて読みたい関連記事
スクリプトの制御構造をさらに強化するために、以下の記事もぜひ参考にしてください。

コメント