ソース化されている場合は呼び出したくないスクリプトがありますexit
。
をチェックすることも考えましたが、$0 == bash
スクリプトが別のスクリプトからソース化されている場合、またはユーザーが などの別のシェルからソース化されている場合は問題が発生しますksh
。
スクリプトがソース化されているかどうかを検出する信頼できる方法はありますか?
ベストアンサー1
bash
、、に対する堅牢なソリューション(ksh
クロスzsh
シェルのソリューションを含む)と、かなり堅牢な POSIX 準拠のソリューション:
指定されたバージョン番号は、機能が検証されたものです。おそらく、これらのソリューションはそれよりずっと前のバージョンでも動作します。フィードバックをお待ちしています。
POSIX 機能のみを使用する場合( Ubuntu では
dash
として動作する など/bin/sh
)、スクリプトがソース化されているかどうかを判断する堅牢な方法はありません。最適な近似値については以下を参照してください。
重要:
ソリューションは、スクリプトが呼び出し元によってソース化されているかどうかを判断します。呼び出し元はシェル自体である場合もあれば、別のスクリプト(それ自体がソース化されている場合とそうでない場合があります)である場合もあります。
また、後者のケースを検出すると複雑さが増します。スクリプトが別のスクリプトによってソース化されているときにそのケースを検出する必要がない場合は、次の比較的単純な POSIX 準拠のソリューションを使用できます。
# Helper function is_sourced() { if [ -n "$ZSH_VERSION" ]; then case $ZSH_EVAL_CONTEXT in *:file:*) return 0;; esac else # Add additional POSIX-compatible shell names here, if needed. case ${0##*/} in dash|-dash|bash|-bash|ksh|-ksh|sh|-sh) return 0;; esac fi return 1 # NOT sourced. } # Sample call. is_sourced && sourced=1 || sourced=0
以下のすべてのソリューションは、関数内ではなく、スクリプトの最上位スコープ内で実行する必要があります。
ワンライナーは次の通りです - 説明は下記です。クロスシェル バージョンは複雑ですが、堅牢に動作するはずです。
- bash (3.57、4.4.19、5.1.16 で検証済み)
(return 0 2>/dev/null) && sourced=1 || sourced=0
- ksh (93u+ で検証済み)
[[ "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ]] && sourced=1 || sourced=0
- zsh (5.0.5 で検証済み)
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
- クロスシェル (bash、ksh、zsh)
(
[[ -n $ZSH_VERSION && $ZSH_EVAL_CONTEXT =~ :file$ ]] ||
[[ -n $KSH_VERSION && "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ]] ||
[[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)
) && sourced=1 || sourced=0
- POSIX 準拠。技術的な理由によりワンライナー(単一パイプライン)ではなく、完全に堅牢ではありません(下部を参照)。
sourced=0
if [ -n "$ZSH_VERSION" ]; then
case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
[ "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ] && sourced=1
elif [ -n "$BASH_VERSION" ]; then
(return 0 2>/dev/null) && sourced=1
else # All other shells: examine $0 for known shell binary filenames.
# Detects `sh` and `dash`; add additional shell filenames as needed.
case ${0##*/} in sh|-sh|dash|-dash) sourced=1;; esac
fi
説明
バッシュ
(return 0 2>/dev/null) && sourced=1 || sourced=0
注: この手法は、user5754163 の回答元の解決策よりも堅牢であることが判明したため、[[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0
[1]
Bash では、
return
関数からのステートメントのみが許可され、スクリプトの最上位スコープでは、スクリプトがソース化されている場合にのみ許可されます。- がソース化されていない
return
スクリプトの最上位スコープ内で使用される場合、エラー メッセージが生成され、終了コードが に設定されます。1
- がソース化されていない
(return 0 2>/dev/null)
サブシェルreturn
で実行され、エラー メッセージが抑制されます。その後、終了コードはスクリプトがソースされたかどうか ( ) を示し、それに応じて変数を設定するためにand演算子と共に使用されます。0
1
&&
||
sourced
return
ソース スクリプトの最上位スコープで実行するとスクリプトが終了するため、サブシェルの使用が必要です。- 敬意を表してハオジュン
0
は、 をオペランドとして明示的に使用することでコマンドをより堅牢にしましたreturn
。彼は、 の bash ヘルプで次のように記しています。return [N]
「N を省略すると、戻りステータスは最後のコマンドのステータスになります。」その結果、以前のバージョン (return
オペランドなしで のみを使用) では、ユーザーのシェルの最後のコマンドの戻り値がゼロ以外の場合、誤った結果が生成されます。
ksh
[[ "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ]] && sourced=1 || sourced=0
特殊変数${.sh.file}
は に多少似ていますが$BASH_SOURCE
、bash、zsh、dash では構文エラー${.sh.file}
が発生するため、マルチシェル スクリプトでは条件付きで実行するようにしてください。
bash とは異なり、$0
と は${.sh.file}
同じであることが保証されません。異なる時点では、どちらかが相対パスまたは単なるファイル名である可能性があり、もう一方が完全なパスである可能性があります。したがって、比較する前に、$0
と の両方${.sh.file}
を完全なパスに解決する必要があります。完全なパスが異なる場合は、ソースが暗黙的に指定されます。
翻訳
[[ $ZSH_EVAL_CONTEXT =~ :file$) ]] && sourced=1 || sourced=0
$ZSH_EVAL_CONTEXT
評価コンテキストに関する情報が含まれます。file
で区切られたサブ文字列:
は、スクリプトがソース化されている場合にのみ存在します。
ソース スクリプトの最上位スコープでは、は で$ZSH_EVAL_CONTEXT
終了し:file
、このテストは に限定されます。関数内では:shfunc
が に追加され:file
、コマンド置換内では:cmdsubst
が追加されます。
POSIX機能のみを使用する
特定の仮定を立てるつもりであれば、スクリプトを実行している可能性のあるシェルのバイナリ ファイル名を知ることで、スクリプトがソース化されているかどうかについて、合理的ではあるが絶対確実ではない推測を行うことができます。特に、このアプローチでは、スクリプトが別のスクリプトによってソース化されている場合は検出されないことに注意してください。
「ソース呼び出しの処理方法」のセクションこの答えPOSIX 機能では処理できないエッジ ケースについてのみ詳細に説明します。
バイナリ ファイル名の検査は、の標準的な動作に依存します$0
が、zsh
たとえば、 ではそれが示されません。
したがって、最も安全なアプローチは、に依存しない上記の堅牢なシェル固有の方法と、$0
$0
残りのすべてのシェルに対するベースのフォールバック ソリューションを組み合わせることです。
簡単に言うと、次の解決策です。
シェル固有のテストでカバーされているシェルでは、堅牢に動作します。
その他のすべてのシェルの場合: スクリプトが別のスクリプトからではなく、そのようなシェルから直接ソースされている場合にのみ、期待どおりに動作します。
敬意を表してステファン・デヌーそして彼の答えインスピレーションをいただきありがとうございます (クロスシェル ステートメント式をsh
-compatibleif
ステートメントに変換し、他のシェル用のハンドラーを追加しました)。
sourced=0
if [ -n "$ZSH_VERSION" ]; then
case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
[ "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ] && sourced=1
elif [ -n "$BASH_VERSION" ]; then
(return 0 2>/dev/null) && sourced=1
else # All other shells: examine $0 for known shell binary filenames.
# Detects `sh` and `dash`; add additional shell filenames as needed.
case ${0##*/} in sh|-sh|dash|-dash) sourced=1;; esac
fi
堅牢性のために、各シェルバイナリファイル名 (例) は2 回sh
表されます。1 回はそのまま、2 回は がプレフィックスとして付きます。これは、macOS などの環境に対応するためです。この環境では、対話型シェルは、 がプレフィックスとして付いた (パスのない) シェルファイル名であるカスタム値を持つログインシェルとして起動されます。よろしくお願いします。-
$0
-
t7e(sh
と は対話型dash
シェルとして使用される可能性は低いですが、リストに追加する必要があるその他のシェルは使用される可能性があります。)
[1]ユーザー1902689は、 にあるスクリプトをバイナリにファイル名だけを渡すことによって[[ $0 != "$BASH_SOURCE" ]]
実行すると、誤検知が生じることを発見しました。たとえば、は だけであるのに対し、 は完全なパスであるためです。通常、 内のスクリプトを呼び出すのにこの手法は使用せず、スクリプトを直接呼び出します( )。ただし、を と組み合わせると、デバッグに役立ちます。$PATH
bash
bash my-script
$0
my-script
$BASH_SOURCE
$PATH
my-script
-x