シェルスクリプトのmain関数パターン|関数定義と実処理を分けて読み込み時の暴発を防ぐ構成

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)シェルスクリプト > シェルスクリプトのmain関数パターン|関数定義と実処理を分けて読み込み時の暴発を防ぐ構成
「シェルスクリプトをsourceで読み込んだら、意図せずバックアップが走りだして焦った」
「共通関数だけ取り込みたいのに、sourceした途端にスクリプト全体が実行されてしまう」

Pythonには if __name__ == '__main__': という定番パターンがあります。bashにも同じ考え方を導入できます。すべての処理を関数に閉じ込め、エントリポイントを main() に集約し、スクリプトが直接実行されたときだけ main を呼び出す設計です。これを「main関数パターン」と呼びます。

この記事では、bash 関数定義の配置ルールと実行ガードの仕組みを解説します。関数の再利用・ライブラリ化・テスタビリティを高める実践パターンを、RHEL 9.4 / Ubuntu 24.04 LTSで動作確認した手順でお伝えします。

この記事のポイント

・BASH_SOURCE[0] と $0 を比較する1行で実行ガードを実装できる
・すべての処理を関数化し main() に集約するのが設計の核心
・source で読み込んだ場合は関数定義のみ行われ実行は走らない
・共通処理をライブラリ化して複数スクリプトから再利用できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜ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] バックアップ完了

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
シェルスクリプト講座を見る >>

ライブラリとして別スクリプトから読み込む実践例

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 宣言でカレントシェルへの変数汚染を防ぐ
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
シェルスクリプト講座を見る >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。