はじめに
シェルスクリプトを書いていると、「このスクリプトは直接実行してほしくない」「`source`で読み込んで使ってほしい」という場面に遭遇することがあります。例えば、DockerのPostgreSQLコンテナで使う`initdb.d`内のスクリプトで、直接実行されると意図しない動作をする場合などです。
今回は、シェバン(`#!`)を工夫して、スクリプトの直接実行を防ぎつつ、実行時にメッセージを表示するシンプルな方法を紹介します。使うのは、身近なコマンド`/bin/echo`です。このトリックを知れば、スクリプトの制御がちょっと楽しくなるかもしれません!
問題: スクリプトの直接実行を防ぎたい
例えば、以下のようなスクリプトを考えます。これは`initdb.d`で共通関数を定義するモジュールです:
function log() {
echo "[INFO] $1"
}
このスクリプトは、他のスクリプトから`source`で読み込んで使う想定です。しかし、誰かがうっかり`./common.sh`と実行してしまうと、特に何も起こらないものの、意図しない動作(例えば環境変数の汚染)を引き起こすリスクがあります。さらに、`initdb.d`内で直接実行されると、期待した初期化が動かないことも。
ここで欲しいのは:
- 直接実行(`./common.sh`)されたら「これは`source`で使ってね」とメッセージを表示。
- `source`で読み込んだときはエラーなく関数を利用可能。
- `initdb.d`で余計な実行を防ぐ。
さて、どうしましょうか?
解決策: `#!/bin/echo` を使う
シンプルな解決策として、シェバンに`/bin/echo`を使います。以下がその実装です:
#!/bin/echo This script is meant to be sourced, not executed directly.
function log() {
echo "[INFO] $1"
}
どう動くのか?
-
直接実行時(`./common.sh`)
シェバンが`/bin/echo`を呼び出し、続く文字列を引数として出力します:
```
This script is meant to be sourced, not executed directly.
```
その後、`echo`が終了し、スクリプトの残りの部分(`function log`)は実行されません。 -
`source`時(`source ./common.sh`)
シェバンは無視され、スクリプト全体が現在のシェルで解釈されます。2行目以降がBashコードとして処理され、`log`関数が正しく読み込まれます:$ source ./common.sh $ log "test" [INFO] test
-
`initdb.d`での動作
Dockerの`initdb.d`で実行されると、シェバンにより`/bin/echo`が呼ばれ、メッセージが出力されて終了。関数定義などの余計な処理はスキップされます。
なぜこれでうまくいくのか?
- `/bin/echo`は引数を出力して即終了する軽量コマンド。
- シェバンの後ろに書いた文字列がそのまま`echo`の引数になる。
- Bashではシェバンが無視されるため、`source`時の動作に影響なし。
他の方法との比較
似た目的でよく使われる方法と比べてみましょう:
`/bin/true` + 条件分岐
#!/bin/true
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
echo "This script is meant to be sourced, not executed directly."
exit 1
fi
function log() {
echo "[INFO] $1"
}
- メリット: メッセージをスクリプト内で柔軟に編集可能。
- デメリット: 少し冗長で、シェバンだけで完結しない。
`/bin/echo` の勝ちポイント
- 1行で完結するシンプルさ。
- 外部依存性がほぼゼロ(`/bin/echo`はどこにでもある)。
- 直感的で「なるほど!」と思えるトリック感。
注意点
この方法を使う際に気をつけるべき点を挙げます:
- メッセージの編集: シェバンにハードコードされるので、長いメッセージや動的な内容には不向き。
- 改行: シェバン内で`\n`を入れてもリテラルとして扱われるので、改行は自然に出ません。必要なら`printf`を検討してもいいかも。
- 可読性: チーム開発では「なぜ`echo`がシェバンに?」と驚かれる可能性があるので、コメントで意図を補足すると親切です。
応用例: Dockerでの利用
`initdb.d`で使う場合、こんな感じで実装できます:
#!/bin/echo This script is meant to be sourced, not executed directly.
function log() {
echo "[INFO] $1"
}
#!/bin/bash
source /docker-entrypoint-initdb.d/common.sh
log "Starting database initialization"
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "CREATE TABLE test (id SERIAL PRIMARY KEY);"
直接`common.sh`が実行されてもメッセージで警告しつつ、`01-init.sh`では関数を活用できます。
おわりに
`#!/bin/echo`を使ったこのトリックは、シェルスクリプトの小さな工夫として意外と便利です。シンプルながら特定の場面(特にDocker環境)で役立つ場面があるので、ぜひ試してみてください。もし他にも面白いシェバンの使い方を見つけたら、コメントで教えてくださいね!