「共通関数だけ取り込みたいのに、sourceした途端にスクリプト全体が実行されてしまう」
Pythonには
if __name__ == '__main__': という定番パターンがあります。bashにも同じ考え方を導入できます。すべての処理を関数に閉じ込め、エントリポイントを main() に集約し、スクリプトが直接実行されたときだけ main を呼び出す設計です。これを「main関数パターン」と呼びます。この記事では、bash 関数定義の配置ルールと実行ガードの仕組みを解説します。関数の再利用・ライブラリ化・テスタビリティを高める実践パターンを、RHEL 9.4 / Ubuntu 24.04 LTSで動作確認した手順でお伝えします。
この記事のポイント
・BASH_SOURCE[0] と $0 を比較する1行で実行ガードを実装できる
・すべての処理を関数化し main() に集約するのが設計の核心
・source で読み込んだ場合は関数定義のみ行われ実行は走らない
・共通処理をライブラリ化して複数スクリプトから再利用できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜmain関数パターンが必要なのか
1. sourceとexecの違い:スクリプトが「走る」タイミング
Linuxでシェルスクリプトを読み込む方法は2種類あります。
・直接実行(bash script.sh や ./script.sh): 新しいサブシェルを起動してスクリプトを実行する
・source(source script.sh や . script.sh): 現在のシェルの文脈でスクリプトを読み込む(サブシェルは起動しない)
通常のスクリプトは、関数定義と実行処理が混在しています。これを source すると、関数が定義されるだけでなく、実行処理まで走ってしまいます。
#!/bin/bash # backup.sh(main関数パターンを使っていない例) BACKUP_SRC="/var/data" BACKUP_DEST="/backup" backup() { echo "バックアップ開始: $BACKUP_SRC → $BACKUP_DEST" rsync -a "$BACKUP_SRC/" "$BACKUP_DEST/" } # この行がsourceしただけで即座に実行される backup
# NG: sourceするとbackup()が即座に実行されてしまう $ source backup.sh バックアップ開始: /var/data → /backup
2. 関数だけが定義されて実行部が走らない設計の価値
main関数パターンを使うと、source したときは関数が定義されるだけで実行処理は走りません。これにより次のメリットが得られます。
・ライブラリ化: 共通関数を別スクリプトから source して再利用できる
・テスタビリティ: テストスクリプトから関数を個別に呼び出してユニットテストができる
・誤実行防止: source と直接実行を混同しても意図しない副作用が起きない
main関数パターンの基本構成
1. 全処理を関数に閉じ込める
まず「実行時に走らせたい処理」をすべて関数にまとめます。グローバルスコープに処理を直接書かず、必ず何らかの関数の中に入れます。
#!/bin/bash # backup.sh(関数化したバージョン) BACKUP_SRC="/var/data" BACKUP_DEST="/backup" # ユーティリティ関数(ライブラリとして再利用可能) log() { local msg="$1" echo "[$(date '+%Y-%m-%d %H:%M:%S')] $msg" } validate_dirs() { if [[ ! -d "$BACKUP_SRC" ]]; then echo "ERROR: バックアップ元が存在しません: $BACKUP_SRC" >&2 return 1 fi mkdir -p "$BACKUP_DEST" } run_backup() { log "バックアップ開始: $BACKUP_SRC → $BACKUP_DEST" rsync -a --checksum "$BACKUP_SRC/" "$BACKUP_DEST/" log "バックアップ完了" } # エントリポイント関数 main() { validate_dirs || exit 1 run_backup }
2. BASH_SOURCEで実行ガードを張る
全関数定義の最後に、実行ガードを1行追加します。これがmain関数パターンの核心です。
# ファイルの末尾に追加する実行ガード(全関数定義の後に置く) if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi
${BASH_SOURCE[0]} はbashの組み込み変数で、現在処理中のスクリプトファイルのパスを保持します。${0} は直接実行されているスクリプトのパスです。
・直接実行時: ${BASH_SOURCE[0]} と ${0} は同じパスを示す → 条件が真 → main が実行される
・sourceされた場合: ${BASH_SOURCE[0]} はsourceされたファイルのパス、${0} は呼び出し元シェルのパス → 両者が異なる → 条件が偽 → main は実行されない
実際に動作を確認します。
# 直接実行する場合(mainが走る) $ bash backup.sh [2026-09-10 10:00:00] バックアップ開始: /var/data → /backup [2026-09-10 10:00:05] バックアップ完了 # sourceする場合(関数だけが定義され、mainは走らない) $ source backup.sh $ # プロンプトが返るだけ。バックアップは実行されていない # sourceした後、関数を個別に呼び出せる $ log "テストメッセージ" [2026-09-10 10:00:10] テストメッセージ
3. 引数を main に渡す書き方
スクリプトに引数を渡す場合、実行ガードの main "$@" で渡します。"$@" はスクリプトに渡されたすべての引数を個別に展開する書き方で、スペースを含む引数も正しく扱えます。
main() { local src="${1:-$BACKUP_SRC}" # 引数があれば使い、なければデフォルト値 local dest="${2:-$BACKUP_DEST}" log "バックアップ開始: $src → $dest" rsync -a --checksum "$src/" "$dest/" log "バックアップ完了" } if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi
# 引数を渡して直接実行する $ bash backup.sh /var/www /backup/www [2026-09-10 10:00:00] バックアップ開始: /var/www → /backup/www [2026-09-10 10:00:08] バックアップ完了
シェルスクリプト講座を見る >>
ライブラリとして別スクリプトから読み込む実践例
1. 共通関数ライブラリの設計
複数のスクリプトで共通する処理(ログ出力・エラー通知・バリデーション)を1つのライブラリファイルにまとめます。main関数パターンを適用することで、このファイルを安全に source できます。
#!/bin/bash # /usr/local/lib/shell/common.sh — 共通ライブラリ # ログ出力関数 log_info() { echo "[INFO][$(date '+%Y-%m-%d %H:%M:%S')] $*" } log_error() { echo "[ERROR][$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2 } # ディレクトリ存在確認(なければ作成する) ensure_dir() { local dir="$1" if [[ ! -d "$dir" ]]; then mkdir -p "$dir" || { log_error "ディレクトリ作成失敗: $dir" return 1 } fi } # 誤って直接実行された場合に使い方を示す if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then echo "このファイルはライブラリです。sourceして使用してください。" >&2 exit 1 fi
2. 本体スクリプトからsourceして呼び出す
本体スクリプトはライブラリを source してから各関数を呼び出します。BASH_SOURCE[0] を使ってライブラリの絶対パスを求めると、スクリプトの配置場所に依存しない書き方になります。
#!/bin/bash # /usr/local/bin/backup.sh — バックアップスクリプト本体 # ライブラリを絶対パスで読み込む source "/usr/local/lib/shell/common.sh" BACKUP_SRC="/var/data" BACKUP_DEST="/backup/data" run_backup() { ensure_dir "$BACKUP_DEST" || exit 1 log_info "バックアップ開始: $BACKUP_SRC → $BACKUP_DEST" rsync -a --checksum "$BACKUP_SRC/" "$BACKUP_DEST/" log_info "バックアップ完了" } main() { run_backup } if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi
RHEL 9.4の検証サーバーで実行した出力例を示します。
$ bash /usr/local/bin/backup.sh [INFO][2026-09-10 10:30:00] バックアップ開始: /var/data → /backup/data [INFO][2026-09-10 10:30:12] バックアップ完了
トラブルシュート:よくある落とし穴と対処
BASH_SOURCEが使えないシェルで動かない場合
BASH_SOURCE はbash固有の変数です。#!/bin/sh で書かれたPOSIX互換スクリプトでは使えません。sh環境で同様のガードを実装するには $0 と実際のファイル名を比較する方法がありますが、環境依存が大きくなります。設計層のスクリプトには #!/bin/bash を使うことを推奨します。
# bash推奨: BASH_SOURCEが使える #!/bin/bash # NG: #!/bin/sh ではBASH_SOURCEが未定義になる # Ubuntu等のdash環境ではBASH_SOURCE[0]が空文字になりガードが機能しない
実行ガードを関数定義の前に書いてしまった場合
bashは関数を定義順に読み込みます。実行ガードを関数定義の前に書いてしまうと、まだ関数が定義されていない状態で main が呼ばれてエラーになります。必ずファイルの末尾に配置してください。
# NG: 関数定義より前に実行ガードがある if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" # この時点でmain()はまだ定義されていない → エラー fi main() { echo "hello" }
# OK: 全関数定義の後に実行ガードを配置する main() { echo "hello" } if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi
sourceしたスクリプトの変数がカレントシェルを汚染する場合
source はカレントシェルの文脈でスクリプトを読み込むため、スクリプト内で定義した変数がそのままカレントシェルに残ります。ライブラリが意図せず環境変数を上書きしないよう、関数内の変数は local で宣言し、スコープを制限しましょう。
# localで変数スコープを制限する(関数内の変数がシェルに残らない) log_info() { local timestamp timestamp="$(date '+%Y-%m-%d %H:%M:%S')" echo "[INFO][$timestamp] $*" }
本記事のまとめ
bash 関数定義のmain関数パターンは、BASH_SOURCE[0] と $0 の比較という1行のガードで、スクリプトを「実行ファイル」と「ライブラリ」の両方として使える設計を実現します。
| やりたいこと | 設計・コマンド |
|---|---|
| sourceしても実行されないようにする | if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@"; fi |
| 全処理を関数に閉じ込める | main() にエントリポイントを集約し、処理を各関数に分割する |
| スクリプトに引数を渡す | main "$@" で全引数をそのまま渡す |
| 共通関数をライブラリ化する | 共通処理を別ファイルに切り出し source で読み込む |
| 関数内の変数スコープを制限する | local 宣言でカレントシェルへの変数汚染を防ぐ |
シェルスクリプト講座を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:シェルスクリプトの関数に引数を渡す設計|位置パラメータの一括展開の違いと可変長引数・配列の受け渡し
- この記事の属するカテゴリ:シェルスクリプトへ戻る

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