スクリプトがソース化されているかどうかを検出する方法 質問する

スクリプトがソース化されているかどうかを検出する方法 質問する

ソース化されている場合は呼び出したくないスクリプトがあります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演算子と共に使用されます。01&&||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" ]]実行すると、誤検知が生じることを発見しました。たとえば、は だけであるのに対し、 は完全なパスであるためです。通常、 内のスクリプトを呼び出すのにこの手法は使用せず、スクリプトを直接呼び出します( )。ただし、を と組み合わせると、デバッグに役立ちます$PATHbashbash my-script$0my-script$BASH_SOURCE$PATHmy-script-x

おすすめ記事