シェルスクリプトの設計まとめ

今年は自身を取り巻く環境の変化もあり、 Windows 10 が EOL となるなど企業環境でも変化があるが、 Debian 13 リリースにあわせて主要な環境を Debian で統一することで、過去の遺産を整理して運用環境の現代化を図ることができた。ついでにウェブサイトブログのデザインも最新化した。そんな中、環境整備のためにここ数か月はかなりの数のシェルスクリプトをはじめとしたコードを更新してきたわけだが、その中で一貫して意識してきた「設計の基準」を、POSIX と FHS の根拠とともに整理しておく。単発のテクニックではなく、なぜその方針にしているのかを標準の規定で裏づける。これはシェルスクリプトだけでなく、システム管理用の Python などのプログラム言語でのコードも同様である。


POSIX 準拠とは何か(自分の適用範囲)

本記事での「POSIX 準拠」は、The Open Group Base Specifications Issue 7(いわゆる POSIX.1-2017)で規定されたシェル言語とユーティリティの範囲に収めて書くことを指す。/bin/sh で動作し、環境依存の拡張(配列、[[ … ]], プロセス置換など)に頼らない。

  • 引数やオプションの扱いは Utility Conventions に合わせる(単一文字オプション、オペランドより前に並べる、– で終端など)。
  • 出力は echo より printf を優先(挙動差異の回避)。
  • コマンド検出は command -v を使う(規定あり)。

目的は「移植性・可読性・一貫性」。/bin/sh 実行で壊れにくく、別環境へ持っていっても読み替えが少なくて済む。


126/127 と終了ステータスの意味

終了コードは運用の会話言語だ。POSIX はいくつかの番号に意味を与えているので、それに合わせる。

  • 0 … 正常終了。
  • 1〜125 … 一般エラー(文法やリダイレクトの失敗を含む)。
  • 126 … 見つかったが実行不能(権限なし、バイナリ形式不正など)。
  • 127 … コマンド未検出(PATH にない等)。
  • 128+N … シグナル N による終了。

したがって、自作スクリプトでも外部コマンドが見つからない場合は 127、権限で実行不能なら 126 を踏襲する。監視や上位のジョブ管理が意味を解釈しやすくなり、原因切り分けの時間が短縮される。


exit と return の使い分け

exit は「シェル実体を終了」し、return は「関数(または source されたスクリプト)から戻る」。トップレベルの致命的な失敗は exit、関数内部の成否は return で返す。return を関数外で乱用しない。返す値は 0/非 0 に限定し、詳細は出力や変数で伝える。


FHS 準拠のファイル配置

置き場は FHS 3.0 に合わせる。これにより、OS 標準のログローテーションや権限モデル、監視と自然に噛み合う。

  • 実行ファイル … /usr/local/bin(一般)または /usr/local/sbin(管理系)。
  • 設定 … /etc(ホスト固有、実行不可)。ローカル配布物の場合は /usr/local/etc も検討。
  • ログ … /var/log。サービスごとにファイルを分ける。
  • PID・一時的ランタイム … /run(ブートでクリアされる前提)。

自作ツールでもこのルールに乗せると、他の仕組みと衝突しにくい。たとえばログ監視やバックアップの対象選定が容易になる。


ヘッダードキュメントの構成

スクリプト冒頭のヘッダーは「使う人・直す人・監視する人」の共通言語。次の順で揃える。

  • Name / Synopsis … 一行要約と書式。utility [-a] [-b arg] [operand…] の流儀に寄せる。
  • Description … 目的、対象範囲、前提(依存コマンドや前提サービス)。
  • Files / Directories … FHS 準拠の配置を明記(実行・設定・ログ・PID)。
  • Exit Status … 0/1–125/126/127/128+N の意味を一覧化。
  • Diagnostics … 出力ポリシー。stdout は通常情報、stderr は警告・エラー。プレフィックスは [INFO]/[WARN]/[ERROR]。
  • Options … 単一文字中心、– で終端、オプションはオペランドより前。
  • Security / Permissions … 必要権限、取り扱う機微情報、想定する拒否時の挙動。
  • Examples … 代表的な利用例を少数。
  • Version History … ヘッダーの履歴と -v/–version の出力を同期。
  • Test Cases … 重要処理の再現手順や想定結果(必要に応じて)。

Usage はこのヘッダーから機械的に抜粋できる形にしておくと、重複記述が減り、更新漏れも起きにくい。


Usage 出力の統一

-h/–help で必ず Usage を出し、ヘッダーの項目と順序を合わせる。書式は POSIX の流儀に寄せて、短いオプションとオペランドの位置関係を崩さない。-v/–version でバージョン表示を統一し、ヘッダーの Version History と一致させる。


ログ出力の標準化([INFO]/[ERROR] など)

  • 標準出力は通常情報([INFO])。標準エラーは警告・障害([WARN]/[ERROR])。
  • grep しやすいプレフィックスを付ける。数値や識別子は後段集計を意識したフォーマットに。
  • ファイルに落とす場合は /var/log/ 配下に置き、ローテーション方針を別途定義。

なぜ check_* で徹底的に環境を検証するのか

「失敗は早く、理由は明確に」を徹底するため。遅延して本処理で落ちるより、開始時に意味のあるメッセージと妥当な終了コードで止まるほうが安い。

  • 依存コマンドの存在確認 … command -v で検出でき、未検出は 127 と解釈できる。
  • 書き込み先・権限の確認 … FHS に基づく置き場(/run, /var/log, /etc)を前提に、ディレクトリ有無・権限を事前に点検。
  • 終了コードの一貫性 … 126/127 を含む POSIX の区分に合わせると、上位の監視やバッチ制御が正確に判断できる。

チェックは「少数・高精度」。すべてを網羅しようとして複雑化させない。致命点だけを確実に見つけて落とす。


コメント規約

  • 英語のみ、動詞始まり、簡潔。例:Validate inputs / Initialize environment。
  • 意図を書く。コードの逐語説明は不要。可読性を下げる長文は避ける。
  • 将来の公開・外部共有を前提に、曖昧な表現や私的略語を避ける。

まとめ

POSIX に寄せ、FHS に沿って配置し、終了コードとログの表現を標準に合わせる。これだけでスクリプトは別環境へ持ち出しても壊れにくくなり、運用・監視と自然に統合される。チェックは開始時に短く鋭く。ヘッダーと Usage を共通化して、更新漏れを作らない。これが当面の基盤であり、今後の変更もこの基盤の上で積み重ねる。