シェルスクリプトの設定を外部ファイル化する設計|sourceと.envで環境別に切り替える方法

運営:リナックスマスター.JP(代表 宮崎智広)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > シェルスクリプト > シェルスクリプトの設定を外部ファイル化する設計|sourceと.envで環境別に切り替える方法
「本番サーバーのDBにテストデータを流し込んでしまった」「認証情報をGitリポジトリにそのままコミットしてしまった」。こういった事故の原因を辿ると、シェルスクリプトへの設定値の直書きが起点になっていることが多い。
規模が小さいスクリプトならハードコードでも何とかなる。しかし、環境が開発・検証・本番と増え、複数人で管理するようになった瞬間、その設計の甘さが噴き出す。

この記事では、sourceコマンドを使ってシェルスクリプトの設定を外部ファイルに切り出す方法と、環境別(開発・検証・本番)に設定を切り替える実践的な設計を解説する。RHEL 9 / Rocky Linux 9 / Ubuntu 24.04 LTS を想定したコード例を交えて紹介する。

この記事のポイント

・source コマンドで外部設定ファイルを読み込み、設定とロジックを分離できる
・「デフォルト値 → config.sh → 環境変数」の3層設計で dev・stg・prod を安全に切り替えられる
・読み込み前の存在確認・権限確認・必須値バリデーションがフェイルセーフ設計の要点
・source はサブシェルを作らないため変数が呼び出し元に引き継がれる(bash 直接実行との違い)


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

なぜ設定をスクリプト本体に直書きしてはいけないのか

シェルスクリプトに設定値を直書きする「ハードコード」が招くリスクは、主に3つある。

本番環境への誤操作リスク
開発用スクリプトをコピーして本番で使い回すと、DB接続先やAPIエンドポイントがそのままになっている。「本番のつもりで実行したら開発DBを操作していた」という逆パターンも起きる。設定値がスクリプト本体に埋め込まれていると、この種のミスを防ぐ仕組みが作れない。

認証情報の漏洩リスク
パスワードやAPIキーをスクリプト本体に書いた状態でGitにコミットすると、履歴に永久に残る。.gitignoreで除外できる設定ファイルに切り出すことが、最低限の対策だ。

変更コストの問題
接続先ホスト名が変わるたびにスクリプト本体を開いて編集しなければならない。設定ファイルが分離されていれば、設定ファイルだけ更新すればよく、スクリプト本体に触れるリスクがない。

sourceコマンドで外部設定ファイルを読み込む基本パターン

sourceコマンド(ドットコマンド . とも書く)は、指定したシェルスクリプトを現在のシェルプロセスで読み込む組み込みコマンドだ。読み込んだファイル内で定義した変数は、呼び出し元のスクリプトでそのまま使える。

bash でスクリプトを直接実行した場合(bash script.sh や ./script.sh)はサブシェルが作られるため、ファイル内の変数は呼び出し元には返らない。source はサブシェルを作らないため、変数が呼び出し元のシェルにそのまま引き継がれる。この点が source を設定ファイルの読み込みに使う根本的な理由だ。

sourceとbash実行の違い

実行方法 新規プロセス 変数の引き継ぎ 主な用途
source file.sh(または . file.sh) なし(同一プロセス) される 設定ファイル読み込み・関数の定義
bash file.sh あり(サブプロセス) されない 独立したスクリプト実行
./file.sh あり(サブプロセス) されない 独立したスクリプト実行

1. 基本的な読み込み方

まず、設定ファイル(config.sh)を用意する。

# config.sh — 設定ファイル DB_HOST="192.168.1.100" DB_NAME="myapp_db" DB_USER="dbuser" DB_PASS="P@ssw0rd" APP_LOG_DIR="/var/log/myapp"

次に、メインスクリプトからsourceで読み込む。

#!/bin/bash # main.sh — メインスクリプト # 設定ファイルの読み込み source /etc/myapp/config.sh # または . /etc/myapp/config.sh でも同じ echo "DB接続先: ${DB_HOST}" echo "ログ出力先: ${APP_LOG_DIR}"

sourceとドット(.)は等価だ。bash では両方使えるが、sh(POSIXシェル)では source が使えない環境もあるため、移植性を重視する場合は . を使う。

2. 相対パスの罠と絶対パス指定の鉄則

source config.sh のように相対パスで指定すると、スクリプトの置き場所ではなくカレントディレクトリから探しにいく。cronや他のスクリプトから呼ばれた場合に「ファイルが見つからない」エラーが発生する典型的な落とし穴だ。

設定ファイルのパスは必ず絶対パスで指定するか、スクリプト自身のディレクトリを基準にする。

#!/bin/bash # スクリプト自身のディレクトリを取得 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # スクリプトと同じディレクトリにある設定ファイルを読み込む source "${SCRIPT_DIR}/config.sh"

${BASH_SOURCE[0]} はスクリプト自身のパスを返す変数だ。$0 と異なり、sourceで読み込まれた場合でも正しくファイルパスを返す。

環境別(開発・検証・本番)に設定ファイルを切り替える設計

複数の環境を管理する実務では、設定ファイルをひとつにまとめるのではなく、環境ごとに設定ファイルを用意してスクリプトから自動選択する設計が安全だ。

1. 環境変数 APP_ENV で切り替える

#!/bin/bash SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # APP_ENV が未設定の場合は development を使う APP_ENV="${APP_ENV:-development}" CONFIG_FILE="${SCRIPT_DIR}/config/${APP_ENV}.sh" # 設定ファイルの存在確認 if [ ! -f "${CONFIG_FILE}" ]; then echo "[ERROR] 設定ファイルが見つかりません: ${CONFIG_FILE}" >&2 exit 1 fi source "${CONFIG_FILE}" echo "環境: ${APP_ENV} / DB接続先: ${DB_HOST}"

呼び出し側は以下のように環境を指定する。

# 開発環境で実行(デフォルト) bash main.sh # 検証環境で実行 APP_ENV=staging bash main.sh # 本番環境で実行 APP_ENV=production bash main.sh # または環境変数をexportしてから実行 export APP_ENV=production bash main.sh

cronで実行する場合は、crontabの先頭に環境変数を記述するのが安全だ。

# crontab -e の記述例 APP_ENV=production 0 2 * * * /opt/myapp/bin/main.sh >> /var/log/myapp/cron.log 2>&1

2. 設定ファイルのディレクトリ構成例

myapp/ ├── main.sh └── config/ ├── development.sh # 開発環境用 ├── staging.sh # 検証環境用 └── production.sh # 本番環境用(.gitignoreで除外)

本番の設定ファイルには認証情報が含まれるため、必ず .gitignore に追加して管理するのが鉄則だ。

3. デフォルト値・config.sh・環境変数の3層設計

さらに柔軟性を高めるには、「デフォルト値 → config.sh → 環境変数」の3層で優先順位を持たせる設計が有効だ。開発環境ではconfig.shなしでも動かせ、本番では環境変数で一部の値を上書きできる。

#!/bin/bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # 第1層: デフォルト値(config.shがなくても動作する最低限の設定) BACKUP_SRC="${BACKUP_SRC:-/var/www/html}" BACKUP_DEST="${BACKUP_DEST:-/tmp/backup_test}" BACKUP_KEEP_DAYS="${BACKUP_KEEP_DAYS:-3}" LOG_FILE="${LOG_FILE:-/tmp/backup.log}" # 第2層: config.shで上書き(存在する場合のみ読み込む) CONFIG_FILE="${SCRIPT_DIR}/config.sh" if [ -f "${CONFIG_FILE}" ]; then source "${CONFIG_FILE}" fi # 第3層: 環境変数による最終上書き(CI/CDや緊急対応での優先設定) BACKUP_DEST="${OVERRIDE_BACKUP_DEST:-${BACKUP_DEST}}" echo "設定確認: BACKUP_SRC=${BACKUP_SRC}, BACKUP_DEST=${BACKUP_DEST}"

この設計では、テスト実行時は環境変数だけで設定を変えられるため、config.shを書き換えずに動作確認できる。

シェルスクリプトを体系的に学びたい方は、シェルスクリプト実践講座(無料)をチェックしてほしい。設定管理・ログ出力・エラーハンドリングなど、現場で使える設計パターンを網羅している。

.envファイル形式を使った設定管理

近年は12-Factor Appの影響で、.env ファイル形式による設定管理が広く使われている。シェルスクリプトでも同じ考え方を取り入れることができる。

1. .envファイルの書き方

# .env.production(本番環境用設定) export DB_HOST="db.example.com" export DB_NAME="myapp_production" export DB_USER="app_user" export DB_PASS="P@ssw0rd_prod" export APP_LOG_DIR="/var/log/myapp" export MAIL_FROM="noreply@example.com"

exportを付ける理由:sourceで読み込んだ変数を子プロセス(外部コマンドや別スクリプト)にも引き継ぐためだ。exportなしだと、現在のシェルのスコープ内でしか参照できない。

cshシェルを使う環境では setenv コマンドが対応するが(csh 環境変数設定の解説参照)、現代のサーバー運用では bash/sh が標準のため export 記法で統一してよい。

2. 値にスペースを含む場合の注意

# OK: ダブルクォートでスペースを含む値を保護 export APP_NAME="My Application" export MAIL_SUBJECT="Backup completed" # NG: スペースがあると想定外の解釈になる # export APP_NAME=My Application ← シェルエラーになる

設定ファイルのバリデーションとフェイルセーフ設計

設定ファイルを外部化しても、必須の変数が未定義のまま実行されると深刻な問題を引き起こす。設定読み込み後にバリデーション処理を入れることが実務の鉄則だ。

1. 設定ファイルを安全に読み込む関数

sourceは「ファイルに書かれたコマンドを現在のシェルで実行する」操作だ。不正なファイルや権限設定の甘いファイルを読み込むと、意図しない変数の上書きが起きる。読み込み前に存在・権限・所有者を確認する関数を設けるとよい。

# 設定ファイルを安全に読み込む関数 load_config() { local config_file="$1" # 1. ファイルの存在確認 if [ ! -f "${config_file}" ]; then echo "[ERROR] 設定ファイルが見つかりません: ${config_file}" >&2 return 1 fi # 2. 権限確認(他ユーザーが書き込み可能な場合に警告) local perms perms=$(stat -c '%a' "${config_file}") if [ "${perms: -1}" -ge 2 ]; then echo "[WARNING] 他ユーザーが書き込める設定ファイルです(${perms}): ${config_file}" >&2 fi # 3. 所有者確認 local owner owner=$(stat -c '%U' "${config_file}") if [ "${owner}" != "$(id -un)" ] && [ "${owner}" != "root" ]; then echo "[WARNING] 所有者が不審です(owner=${owner}): ${config_file}" >&2 fi source "${config_file}" echo "[INFO] 設定ファイルを読み込みました: ${config_file}" }

本番環境の設定ファイルのパーミッションは 600(所有者のみ読み書き可)が推奨だ。

# 推奨パーミッション設定 chmod 600 config/production.sh # load_config 関数を使った安全な読み込み SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" load_config "${SCRIPT_DIR}/config/${APP_ENV:-development}.sh" || exit 1

2. 必須変数の存在チェック

#!/bin/bash SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" APP_ENV="${APP_ENV:-development}" source "${SCRIPT_DIR}/config/${APP_ENV}.sh" # 必須変数チェック関数 check_required_vars() { local missing=0 for var in DB_HOST DB_NAME DB_USER DB_PASS; do if [ -z "${!var}" ]; then echo "[ERROR] 必須変数 ${var} が設定されていません" >&2 missing=1 fi done if [ "${missing}" -eq 1 ]; then exit 1 fi } check_required_vars echo "バリデーション OK — 処理を開始します"

${!var} は変数名を動的に展開するbashの機能だ。var="DB_HOST" のとき、${!var} は ${DB_HOST} と同じ値を返す。

3. デフォルト値の設定(:= 演算子)

必須ではないが省略可能な設定値には、フォールバックを設定しておく。

# 変数が未設定または空の場合にデフォルト値を返す(変数は変更しない) LOG_LEVEL="${LOG_LEVEL:-INFO}" # 変数が未設定または空の場合にデフォルト値を設定する(変数も更新する) : "${RETRY_COUNT:=3}" : "${TIMEOUT_SEC:=30}" echo "ログレベル: ${LOG_LEVEL}" echo "リトライ回数: ${RETRY_COUNT}" echo "タイムアウト: ${TIMEOUT_SEC}秒"

${変数名:-デフォルト値} は変数を変更せずデフォルト値を返す。${変数名:=デフォルト値} は変数自体も更新する。コロン(:)コマンドはシェルの組み込みで、引数を評価して何もしない。副作用として := による変数初期化に活用できる。

4. set -u による未定義変数の検出

スクリプト冒頭に set -u(または set -o nounset)を記述すると、未定義変数を参照した時点でエラー終了する。誤字による変数名ミスを早期に検出できる。

#!/bin/bash set -euo pipefail # -e: エラー発生時に即終了 # -u: 未定義変数を参照したらエラー # -o pipefail: パイプライン途中のエラーも捕捉 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/config/${APP_ENV:-development}.sh"

set -euo pipefail は堅牢なシェルスクリプトの定番設定だ。ただし、sourceで読み込む設定ファイル側で :- などのデフォルト値処理を適切に行わないと、未定義変数でスクリプトが止まることがある点は注意が必要だ。

「設定ファイルが読み込まれない」よくあるエラーと対処法

1. No such file or directory:パス指定の落とし穴

source実行時に「bash: /path/to/config.sh: No such file or directory」が出る場合、多くはパスの問題だ。

# NG: カレントディレクトリから探すため、cronなどで失敗する source config.sh # OK: BASH_SOURCE[0]でスクリプト自身の場所を基準にする SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/config.sh"

cronで実行するスクリプトは特に注意が必要だ。cronのデフォルトカレントディレクトリは実行ユーザーのホームディレクトリになるため、相対パスが通らないケースが多い。

2. 変数が空のまま:exportし忘れに気づく方法

設定ファイルをsourceしたはずなのに変数が空になる場合、export忘れが原因の場合がある。sourceで読み込んだ変数でも、子プロセスに渡すにはexportが必要だ。

# 実際に変数が読み込まれているか確認する手順 source "${SCRIPT_DIR}/config/${APP_ENV}.sh" # 現在のシェルで定義されているか確認 echo "DB_HOST=${DB_HOST}" # 子プロセスに渡っているか確認(exportがなければ空になる) bash -c 'echo "子プロセス DB_HOST=${DB_HOST}"'

3. set -u 導入後に「unbound variable」が大量発生する場合

既存のスクリプトに set -u を追加すると、これまで暗黙的に空として扱われていた未定義変数が全てエラーになる。段階的に対応するには、まず set -u を有効にしてエラーを洗い出し、各変数に :- でデフォルト値を設定していくのが現実的だ。

4. パイプ経由でsourceするとサブシェル問題が発生する

# NG: パイプの右辺はサブシェルで動くため変数が親プロセスに返らない cat config.sh | while read line; do eval "$line"; done # OK: sourceをそのまま直呼びする source config.sh

パイプを使った読み込みは、変数が親プロセスに引き継がれないだけでなく、evalによるコマンドインジェクションのリスクもある。sourceを直接呼ぶのが唯一の正解だ。

5. source先でexitを呼ぶと現在のシェルが終了する

config.sh の中で exit 1 を呼ぶと、現在のシェルプロセスそのものが終了する。バリデーションエラーを通知したい場合は return 1 を使う。

# config.sh 内の書き方 # NG: 現在のシェルが終了してしまう if [ -z "${DB_NAME}" ]; then exit 1; fi # OK: 呼び出し元に失敗を返す(シェルは終了しない) if [ -z "${DB_NAME}" ]; then echo "[ERROR] DB_NAME が未設定です" >&2 return 1 fi

実践的なディレクトリ構成とベストプラクティス

1. 推奨ディレクトリ構成

myapp/ ├── bin/ │ └── main.sh # メインスクリプト ├── config/ │ ├── common.sh # 全環境共通設定 │ ├── development.sh # 開発環境固有設定 │ ├── staging.sh # 検証環境固有設定 │ └── production.sh # 本番環境固有設定(gitignore対象) ├── lib/ │ └── functions.sh # 共通関数定義 └── .gitignore

2. 共通設定と環境固有設定を分ける

全環境で共通の設定(ログディレクトリ、スクリプト名等)と、環境ごとに異なる設定(DB接続情報、APIキー等)を分離するとさらに管理しやすくなる。

#!/bin/bash SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_DIR="${SCRIPT_DIR}/../config" APP_ENV="${APP_ENV:-development}" # 共通設定を先に読む source "${CONFIG_DIR}/common.sh" # 環境固有設定で上書き(共通設定より後に読む点が重要) source "${CONFIG_DIR}/${APP_ENV}.sh"

3. .gitignoreの設定

# .gitignore config/production.sh config/staging.sh .env .env.* !.env.example

.env.example(または config/production.sh.example)だけgitに含めることで、チームメンバーが設定ファイルのひな形を把握できる。

本記事のまとめ

やりたいこと 方法
外部設定ファイルを読み込む source /path/to/config.sh
スクリプト自身のディレクトリ基準でsource source "${SCRIPT_DIR}/config.sh"(BASH_SOURCE[0]でSCRIPT_DIR取得)
環境別に設定ファイルを切り替える source "config/${APP_ENV:-development}.sh"
3層設計で柔軟に設定を管理する デフォルト値 → config.sh → 環境変数の順で優先度を持たせる
未設定変数にデフォルト値を使う ${変数名:-デフォルト値}
未定義変数でスクリプトを即終了させる set -u(または set -euo pipefail)
設定ファイルの安全な読み込み 存在確認・パーミッション600・所有者確認を読み込み前に実施
config.sh内のエラー通知 exitではなくreturn 1を使う(exitはシェルごと終了する)
シェルスクリプトの設定を外部ファイルに切り出すことは、開発・検証・本番の環境を安全に管理するための基本設計だ。sourceコマンドの特性(現在のシェルへの読み込み)と相対パスの罠を理解した上で設計すれば、誤操作や認証情報漏洩のリスクを大幅に下げられる。

また、「デフォルト値 → config.sh → 環境変数」の3層設計と必須変数バリデーションを組み合わせることで、設定ミスを早期に検知できるフェイルセーフな構造が完成する。set -euo pipefail の導入と合わせて、実務で安心して使えるスクリプト設計の土台を固めてほしい。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアがシェルスクリプト設計の基礎から実践まで教えます。
シェルスクリプト実践講座を見る >>

<PR>手元に置いて学びを深める1冊

[改訂第3版]シェルスクリプト基本リファレンス ──#!/bin/shで、ここまでできる

source や .env を使った設定管理を含む、現場で使えるシェルスクリプト設計を体系的に押さえたい方に手元に置いてほしい一冊。まず無料メルマガで基礎を固め、さらに深めるなら本書。

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

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

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

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

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

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

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

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

運営

リナックスマスター.JP(代表 宮崎智広)

株式会社イーネットマーキュリーが運営するLinux教育サイトです。代表の宮崎智広は現役のLinuxサーバー管理者として20年以上の実務経験を持ち、累計3,100名以上のエンジニアを指導してきました。実践型のLinuxセミナーや無料マニュアルの配布を通じてLinux人材の育成に取り組んでいます。