「引数を間違えて渡しても何のメッセージも出ないので、どこが問題なのかわからない」
シェルスクリプトにありがちな問題です。
--help オプションと usage 関数を組み込めば、スクリプト自身が使い方を教えてくれる「自己文書化」の設計が実現できます。ドキュメントを別ファイルに書かなくても済みますし、スクリプトを渡した相手が使い方に迷わなくなります。この記事では、RHEL 9系/Ubuntu 24.04 LTSを想定環境として、
usage 関数・--help・--version の設計パターンを体系的に解説します。引数解析の実装(getopts・ロングオプション)は別記事を参照してください。
この記事のポイント
・usage関数はヒアドキュメントで書くとヘルプ本文を一元管理できる
・--helpは標準出力(stdout)に出力して終了コード0で終わる
・引数エラーは標準エラー出力(stderr)に出力して終了コード2で終わる
・--versionは冒頭の変数1つを変更するだけで更新できる設計にする
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜシェルスクリプトにヘルプ表示が必要か
シェルスクリプトは「書いた本人が使う」という前提で作られることが多いですが、実際には時間が経つと作者自身も使い方を忘れます。ましてチームで共有するスクリプトでは、使い方を別ファイルに書いたドキュメントはたいていメンテナンスされなくなります。シェルスクリプトにヘルプ表示を組み込むメリットは2つあります。
・スクリプトとドキュメントを一元管理できる:コードを修正したタイミングでヘルプ本文も同時に更新できます。
・使い方の誤りを即座にフィードバックできる:引数エラーのときに用途をその場で伝えられるので、試行錯誤のループを減らせます。
man コマンドのマニュアルページを書くほどではない規模のスクリプトでも、--help だけ実装すれば十分に「自己文書化」が成立します。シェルスクリプトのusage関数の基本設計
1. ヒアドキュメントでヘルプ本文を書く
usage 関数の中身は、ヒアドキュメント(<<'EOF')で書くのが定石です。ヒアドキュメントにすると、インデントや改行を自由に書けるので、整形されたヘルプテキストをそのまま表現できます。#!/bin/bash usage() { cat <<'EOF' 使い方: backup.sh [オプション] 送信元ディレクトリ 説明: 指定ディレクトリを /backup 配下にコピーします。 オプション: -h, --help このヘルプを表示して終了する -v, --version バージョンを表示して終了する -d, --dry-run 実行内容を表示するだけでコピーしない 引数: 送信元ディレクトリ バックアップ対象のパス(必須) 例: backup.sh /var/www backup.sh --dry-run /var/www EOF }
<<'EOF')をシングルクォートで囲むと、本文中の $ や \ がシェル展開されません。$1 や ${変数} をそのまま文字列として表示したい場合はこの形を使いましょう。変数を展開させたい場合はクォートなしの <<EOF を使います。2. --helpは標準出力に出力して終了コード0で終わる
--help は正常な要求です。エラー扱いにせず、標準出力(stdout)に出力して終了コード0(正常終了)で終わらせるのがGNUコーディング規約の慣習です。# 引数チェックの先頭で --help を処理する case "$1" in -h|--help) usage # 標準出力(stdout)に出力 exit 0 # 正常終了 ;; esac
usage を呼んだだけだと標準出力に出ますが、usage >&2 のように書くと標準エラー出力に流れます。--help の場合はリダイレクトなしで標準出力に出すのが正しい設計です。重要なのは「終了コード0」で終わることです。
exit 1 にすると、スクリプトを呼ぶ側が if script.sh --help; then ... のように処理したとき、ヘルプ表示を「失敗」と判定してしまいます。3. 引数エラーは標準エラー出力に出力して終了コード2で終わる
引数が不足している・不正なオプションが渡されたといった「使い方の誤り」は、エラーメッセージとusage の出力を標準エラー出力(stderr)に流し、終了コード2で終わるのがPOSIXの慣習です。# 引数が1つ以上なければエラー if [ $# -lt 1 ]; then echo "エラー: 送信元ディレクトリを指定してください" >&2 usage >&2 # usage も stderr に出す exit 2 # 使い方の誤りは終了コード2 fi
・0:正常終了(
--help・--version を含む)・1:処理中の一般エラー(コピー失敗・ファイルが見つからないなど)
・2:使い方の誤り(引数不足・未知のオプションなど)
exit 1 と exit 2 を区別しておくと、スクリプトを呼ぶシェルが終了コードで「引数の問題なのか処理の問題なのか」を判別できます。シェルスクリプトの--version設計パターン
--version はバージョン文字列を標準出力に出して終了コード0で終わります。バージョン番号をスクリプト冒頭の変数に切り出しておくと、更新時の変更箇所が1か所に集まります。#!/bin/bash readonly SCRIPT_NAME="backup.sh" readonly VERSION="1.2.0" usage() { cat <<'EOF' 使い方: backup.sh [オプション] 送信元ディレクトリ ... EOF } case "$1" in -h|--help) usage exit 0 ;; -v|--version) echo "${SCRIPT_NAME} version ${VERSION}" exit 0 ;; esac
readonly を付けると変数の上書きを防ぎ、スクリプトの途中で誤って変更されることを防げます。SCRIPT_NAME を "$(basename "$0")" で取得すると、スクリプトをリネームしても自動的に追随できます。--version の出力形式はGNUツールの慣習に倣い、「スクリプト名 version バージョン番号」のワンライン形式が無難です。ライセンス情報を付ける場合は2行目以降に追加します。シェルスクリプトのヘルプとコメントを二重管理しない工夫
よくある失敗は、usage 関数のヘルプ本文とコード内のコメントに同じ内容を重複して書いてしまうことです。どちらかを更新してもう一方を忘れると、ヘルプとコードの記述が食い違います。二重管理を避けるポイントは2つです。
・「何をするか」はusage関数に書く:各オプションの説明・引数の意味・使用例は
usage 関数に集約します。スクリプト冒頭のコメントブロックで同じ内容を繰り返さないようにします。・コード内コメントは「なぜこう書くか」に限定する:「引数なしをチェックする」のような自明な説明ではなく、「busybox sh では
[[ ]] が使えないため [ ] を使う」のような、コードから読み取れない理由だけをコメントに残します。usage 関数にオプションの説明が集約されていれば、新しいオプションを追加したときに「usage関数を更新する」という一つの習慣で文書化が完結します。完成形テンプレート(シェルスクリプトのヘルプ表示)
上記の設計をまとめた完成形テンプレートです。このテンプレートをベースにして各スクリプトに合わせて修正してください。シェルスクリプトをより体系的に学びたい方は、シェルスクリプト入門講座も参考にしてください。#!/bin/bash # backup.sh - ディレクトリのバックアップスクリプト readonly SCRIPT_NAME="$(basename "$0")" readonly VERSION="1.0.0" usage() { cat <<'EOF' 使い方: backup.sh [オプション] 送信元ディレクトリ 説明: 指定ディレクトリを /backup 配下にコピーします。 オプション: -h, --help このヘルプを表示して終了する -v, --version バージョンを表示して終了する -d, --dry-run 実行内容を表示するだけでコピーしない 引数: 送信元ディレクトリ バックアップ対象のパス(必須) 例: backup.sh /var/www backup.sh --dry-run /var/www EOF } # --help / --version は最優先で処理する case "$1" in -h|--help) usage exit 0 ;; -v|--version) echo "${SCRIPT_NAME} version ${VERSION}" exit 0 ;; esac # 引数チェック if [ $# -lt 1 ]; then echo "${SCRIPT_NAME}: エラー: 送信元ディレクトリを指定してください" >&2 usage >&2 exit 2 fi SRC_DIR="$1" if [ ! -d "${SRC_DIR}" ]; then echo "${SCRIPT_NAME}: エラー: ディレクトリが存在しません: ${SRC_DIR}" >&2 exit 1 fi # 以降に実際の処理を書く echo "バックアップ対象: ${SRC_DIR}"
# --help を渡したとき(stdout に出てくる) $ ./backup.sh --help 使い方: backup.sh [オプション] 送信元ディレクトリ 説明: 指定ディレクトリを /backup 配下にコピーします。 オプション: -h, --help このヘルプを表示して終了する -v, --version バージョンを表示して終了する -d, --dry-run 実行内容を表示するだけでコピーしない 引数: 送信元ディレクトリ バックアップ対象のパス(必須) 例: backup.sh /var/www backup.sh --dry-run /var/www # 引数なしで実行したとき(stderr に出てくる) $ ./backup.sh 2>/tmp/err.txt; cat /tmp/err.txt backup.sh: エラー: 送信元ディレクトリを指定してください 使い方: backup.sh [オプション] 送信元ディレクトリ ... # 終了コードの確認 $ ./backup.sh --help; echo $? (ヘルプが出る) 0 $ ./backup.sh; echo $? (エラーが stderr に出る) 2
よくある誤りとシェルスクリプトのヘルプ表示の対処法
1. --helpをstderrに出してしまう
# NG: usage を stderr に流している -h|--help) usage >&2 exit 0 ;;
--help は正常な要求なので、出力先は標準出力(リダイレクトなし)にします。スクリプトの出力を grep や less にパイプしてヘルプを検索したいとき、stderr に出ていると受け取れません。2. 引数エラーで終了コード1を返してしまう
# NG: 使い方の誤りなのに exit 1 にしている if [ $# -lt 1 ]; then usage >&2 exit 1 # 処理エラーと区別がつかない fi
exit 2 に統一すると、呼び出し元が終了コードで原因を絞り込めます。Bashの組み込みコマンドも引数の誤りに exit 2 を返す慣習があります。3. usage関数の中でexitを呼んでしまう
# NG: usage 関数の中に exit を書いている usage() { cat <<'EOF' ... EOF exit 1 # usage を呼ぶだけしたいときに必ず終了してしまう }
usage 関数はテキストを表示するだけに留め、exit は呼び出し元で行いましょう。--help では exit 0、引数エラーでは exit 2 と、終了コードを使い分けられるようになります。4. ヘルプの内容が引数仕様と食い違う
コードを修正したときにusage 関数の更新を忘れると、ヘルプと実際の動作が食い違います。引数の追加・削除・名称変更をしたときは必ず usage 関数も同時に更新する習慣をつけましょう。シェルスクリプトのヘルプ表示設計まとめ
| 設計ポイント | 正しい設計 | 理由 |
|---|---|---|
| --helpの出力先 | 標準出力(stdout) | パイプやリダイレクトで受け取れる |
| --helpの終了コード | 0(正常終了) | ヘルプ表示は正常な要求 |
| 引数エラーの出力先 | 標準エラー出力(stderr) | エラーはstderrに流すPOSIX慣習 |
| 引数エラーの終了コード | 2 | 使い方の誤りと処理エラーを区別できる |
| --versionの出力先 | 標準出力(stdout) | --helpと同じ正常な要求 |
| --versionの終了コード | 0(正常終了) | バージョン確認は正常な要求 |
| ヘルプ本文の書き方 | ヒアドキュメント(<<'EOF') | 整形が自由・変数展開を制御できる |
| usageでexit | 呼び出し元でexitする | 終了コードを柔軟に使い分けられる |
>> シェルスクリプト入門講座の詳細はこちら
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 次のページへ:シェルスクリプトのコーディング規約|命名・インデント・コメント・関数分割をチームで統一するスタイルガイドの作り方
- 前のページへ:シェルスクリプトのロケール設計|LC_ALL=Cでsort順・日付・数値の出力が環境ごとに変わる問題を防ぐ方法
- この記事の属するカテゴリ:シェルスクリプトへ戻る

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