スクリプトが供給されているかどうかを検出する方法


217

exitソースが提供されている場合に呼び出されないようにするスクリプトがあります。

$0 == bashスクリプトを別のスクリプトから取得した場合、またはユーザーがのような別のシェルから取得した場合に問題があるかどうかを確認することを考えましたksh

スクリプトが供給されているかどうかを検出する信頼できる方法はありますか?


2
私はしばらく前に同様の問題を抱えていて、すべてのケースで「終了」を回避することで解決しました。「kill -INT $$」は、どちらの場合もスクリプトを安全に終了します。
JESii 2018年

1
この答えに気づきましたか?承認から5年後に贈られますが、「電池込み」です。
raratiru

回答:


72

これはBashとKornの間で移植できるようです:

[[ $_ != $0 ]] && echo "Script is being sourced" || echo "Script is a subshell"

これと同様の行またはpathname="$_"(後のテストとアクションを使用した)のような割り当ては、スクリプトの最初の行またはシバンの後の行に使用する必要があります(使用する場合は、kshで使用する必要があります)ほとんどの状況)。


10
残念ながら、動作することは保証されていません。ユーザーが設定している場合はBASH_ENV$_スクリプトの先頭にから実行する最後のコマンドとなりますBASH_ENV
ミケル

30
これは、bashを使用してスクリプトを実行する場合も機能しません。たとえば、$ bash script.shの場合、$ _は./script.shではなく、/ bin / bashになります。このように:$ ./script.shいずれにしても、での検出$_は問題です。
Wirawan Purwanto 2012

2
これらの呼び出しメソッドをチェックするために、追加のテストを含めることができます。
追って通知があるまで一時停止。

8
残念ながら、それは間違いです。私の回答を
F.ハウリ

8
要約すると、このアプローチは通常は機能しますが、堅牢ではありません。次の2つのシナリオで失敗します:(a)bash script(シェル実行可能ファイルを介した呼び出し、このソリューションはソースとして誤って報告します)、および(b)(はるかに可能性が低い)echo bash; . script$_シェルがスクリプトをソースしていると一致する場合、このソリューションは誤って報告しますサブシェル)。唯一のシェル固有の特殊変数(例えば、$BASH_SOURCE)堅牢なソリューションを(強固なPOSIX準拠のソリューションが存在しないということになる)ことができます。面倒ではありますが、堅牢なクロスシェルテストを作成すること可能です。
mklement0 2016年

170

BashのバージョンがBASH_SOURCE配列変数を認識している場合は、次のようにしてください。

# man bash | less -p BASH_SOURCE
#[[ ${BASH_VERSINFO[0]} -le 2 ]] && echo 'No BASH_SOURCE array variable' && exit 1

[[ "${BASH_SOURCE[0]}" != "${0}" ]] && echo "script ${BASH_SOURCE[0]} is being sourced ..."

11
$ BASH_SOURCEがまさにその目的のために意図されているので、それはおそらく最もクリーンな方法です。
con-f-use

4
これは、OPが指定した条件であるkshでは機能しないことに注意してください。
追って通知があるまで一時停止。

2
${BASH_SOURCE[0]}代わりに使用する理由はあり$BASH_SOURCEますか?そして${0}vs $0
hraban 2016年

4
BASH_SOURCEソースのスタックトレースを保持する配列変数(manualを参照)です${BASH_SOURCE[0]}。は最新のものです。中括弧は、変数名の一部をbashに伝えるためにここで使用されます。$0この場合は必要ありませんが、害はありません。;)
Konrad

4
@Konrad、およびを展開すると$array${array[0]}デフォルトで取得されます。それで、もう一度、理由があります[...]?
Charles Duffy、

133

以下のための堅牢なソリューションをbashkshzshを含む、クロスシェル 1、プラス合理的に堅牢なPOSIX準拠のソリューション

  • 与えられたバージョン番号は、機能が検証されたものです -おそらく、これらのソリューションは以前のバージョンでも動作します- フィードバックを歓迎します。

  • 使い方POSIX機能のみ(などのようdashとして機能する、 /bin/shUbuntuの上で)、ありません何の堅牢な方法スクリプトが調達されているかどうかを決定するためには-最高のために以下を参照してください近似

ワンライナーが続きます-以下の説明; クロスシェルバージョンは複雑ですが、堅牢に動作するはずです。

  • bash(3.57および4.4.19で検証済み)

    (return 0 2>/dev/null) && sourced=1 || sourced=0
  • ksh(93u +で検証済み)

    [[ $(cd "$(dirname -- "$0")" && 
       printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] &&
         sourced=1 || sourced=0
  • zsh(5.0.5で検証済み)- 関数の外で呼び出してください

    [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
  • クロスシェル(bash、ksh、zsh)

    ([[ -n $ZSH_EVAL_CONTEXT && $ZSH_EVAL_CONTEXT =~ :file$ ]] || 
     [[ -n $KSH_VERSION && $(cd "$(dirname -- "$0")" &&
        printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] || 
     [[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)) && sourced=1 || sourced=0
  • POSIX準拠 ; ないワンライナー(単一のパイプライン)技術的な理由とのためではない、完全に堅牢(下を参照してください):

    sourced=0
    if [ -n "$ZSH_EVAL_CONTEXT" ]; 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|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し、エラーメッセージを抑制します。その後、終了コードは、スクリプトのソース()かそうでないか()を示します。これは、および演算子と共に使用され、それに応じて変数を設定します。01&&||sourced

    • returnソーススクリプトのトップレベルのスコープで実行するとスクリプトが終了するため、サブシェルを使用する必要があります。
    • オペランドとして明示的に使用することでコマンドをより堅牢にした@Haozhunへの帽子の先端。彼は次のように述べています:bashのヘルプごとに:「Nが省略されている場合、戻りステータスは最後のコマンドのステータスです。」その結果、ユーザーのシェルの最後のコマンドがゼロ以外の戻り値を持っている場合、以前のバージョン(オペランドなしでを使用)は誤った結果を生成しました。0returnreturn [N]return

ksh

[[ \
   $(cd "$(dirname -- "$0")" && printf '%s' "${PWD%/}/")$(basename -- "$0") != \
   "${.sh.file}" \
]] && 
sourced=1 || sourced=0

特殊変数${.sh.file}はにいくらか似てい$BASH_SOURCEます。はbash、zsh、およびdashで構文エラー${.sh.file}引き起こすので、マルチシェルスクリプトで条件付きで実行してください。

bashのとは異なり、$0かつ${.sh.file}であることが保証されない正確として、非ソースの場合は同じ$0かもしれ相対パスながら、${.sh.file}常に完全なパスので、$0比較する前に完全なパスに解決されなければなりません。


zsh

[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0

$ZSH_EVAL_CONTEXT評価コンテキストに関する情報が含まれています-関数の外で呼び出します。ソーススクリプト[のトップレベルスコープ]内で、で$ZSH_EVAL_CONTEXT 終了:fileます。

警告:コマンド置換の内部では、zshがを追加:cmdsubstするので、そこをテスト$ZSH_EVAL_CONTEXT:file:cmdsubst$てください。


POSIX機能のみを使用する

スクリプトを実行している可能性のあるシェルのバイナリファイル名知っていることに基づいて、特定の仮定をすることいとわない場合は合理的ではありますが、スクリプトが供給されているかどうかについては間違いのない推測を行うことができます。 特に、これは、スクリプトが別のスクリプトによって供給されている場合、このアプローチが失敗することを意味します

この回答の「ソース化された呼び出しを処理する方法」のセクションでは、POSIX機能では処理できないエッジケースについて詳しく説明しています。

これは、の標準的な動作に依存して$0zshインスタンスがないため、ない呈します。

したがって、最も安全なアプローチは、上記の堅牢なシェル固有のメソッドを、残りのすべてのシェルのフォールバックソリューションと組み合わせることです。

帽子の先端ステファンDesneux彼の答えのインスピレーションのため(に私のクロスシェル文の表現を変換sh互換ifの文や他のシェルのハンドラを追加します)。

sourced=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; 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|dash) sourced=1;; esac
fi

[1] user1902689は単にファイル名をバイナリに渡して、に[[ $0 != "$BASH_SOURCE" ]]あるスクリプトを実行すると、誤検知を引き起こすことを発見しました。例えば、、理由だけで、その後であるのに対し、あるフルパス。あなたが通常でスクリプトを呼び出すために、この技術を使用していないだろうが-あなたはちょうどそれら呼び出すと思い、直接(それは- )であると組み合わせる際に役立つためのデバッグ$PATHbashbash my-script$0my-script$BASH_SOURCE$PATHmy-script-x


1
そのような包括的な答えに対する称賛。
DrUseful

75

@DennisWilliamsonの回答を読んだ後、いくつかの問題があります。以下を参照してください。

この質問は そして 、この回答には別の部分があります ... 下記参照。

シンプルな 仕方

[ "$0" = "$BASH_SOURCE" ]

試してみましょう(そのbashが可能なため、その場で;-):

source <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 29301 is sourced (bash, /dev/fd/63)

bash <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 16229 is own (/dev/fd/63, /dev/fd/63)

私は読みやすさのためにsource代わりにoff を使用しています..のエイリアスとしてsource):

. <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 29301 is sourced (bash, /dev/fd/63)

プロセスがソースのままである間、プロセス番号は変更されないことに注意してください。

echo $$
29301

$_ == $0比較を使用しない理由

多くのケースを確実にするために、私は本当のスクリプトを書き始めます:

#!/bin/bash

# As $_ could be used only once, uncomment one of two following lines

#printf '_="%s", 0="%s" and BASH_SOURCE="%s"\n' "$_" "$0" "$BASH_SOURCE"
[[ "$_" != "$0" ]] && DW_PURPOSE=sourced || DW_PURPOSE=subshell

[ "$0" = "$BASH_SOURCE" ] && BASH_KIND_ENV=own || BASH_KIND_ENV=sourced;
echo "proc: $$[ppid:$PPID] is $BASH_KIND_ENV (DW purpose: $DW_PURPOSE)"

これを次のファイルにコピーしますtestscript

cat >testscript   
chmod +x testscript

これでテストできます:

./testscript 
proc: 25758[ppid:24890] is own (DW purpose: subshell)

それで大丈夫です。

. ./testscript 
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)

source ./testscript 
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)

それで大丈夫です。

ただし、-xフラグを追加する前にスクリプトをテストする場合:

bash ./testscript 
proc: 25776[ppid:24890] is own (DW purpose: sourced)

または、事前定義された変数を使用するには:

env PATH=/tmp/bintemp:$PATH ./testscript 
proc: 25948[ppid:24890] is own (DW purpose: sourced)

env SOMETHING=PREDEFINED ./testscript 
proc: 25972[ppid:24890] is own (DW purpose: sourced)

これはもう機能しません。

コメントを5行目から6行目に移動すると、より読みやすい回答が得られます。

./testscript 
_="./testscript", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26256[ppid:24890] is own

. testscript 
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced

source testscript 
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced

bash testscript 
_="/bin/bash", 0="testscript" and BASH_SOURCE="testscript"
proc: 26317[ppid:24890] is own

env FILE=/dev/null ./testscript 
_="/usr/bin/env", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26336[ppid:24890] is own

もっと強く: 今...

使わないので 多くの場合、manページを読んだ後、私の試みがあります:

#!/bin/ksh

set >/tmp/ksh-$$.log

これを次の場所にコピーしますtestfile.ksh

cat >testfile.ksh
chmod +x testfile.ksh

それを2回実行するより:

./testfile.ksh
. ./testfile.ksh

ls -l /tmp/ksh-*.log
-rw-r--r-- 1 user user   2183 avr 11 13:48 /tmp/ksh-9725.log
-rw-r--r-- 1 user user   2140 avr 11 13:48 /tmp/ksh-9781.log

echo $$
9725

そして見る:

diff /tmp/ksh-{9725,9781}.log | grep ^\> # OWN SUBSHELL:
> HISTCMD=0
> PPID=9725
> RANDOM=1626
> SECONDS=0.001
>   lineno=0
> SHLVL=3

diff /tmp/ksh-{9725,9781}.log | grep ^\< # SOURCED:
< COLUMNS=152
< HISTCMD=117
< LINES=47
< PPID=9163
< PS1='$ '
< RANDOM=29667
< SECONDS=23.652
<   level=1
<   lineno=1
< SHLVL=2

ソース実行で継承された変数がいくつかありますが、実際には何も関連していません...

あなたはそれ$SECONDSがに近いことを確認することもできますが、それは手動でソースされたケース0.000のみを確実にします...

親が何であるかを確認することもできます:

これをあなたの中に入れてくださいtestfile.ksh

ps $PPID

より:

./testfile.ksh
  PID TTY      STAT   TIME COMMAND
32320 pts/4    Ss     0:00 -ksh

. ./testfile.ksh
  PID TTY      STAT   TIME COMMAND
32319 ?        S      0:00 sshd: user@pts/4

またはps ho cmd $PPID、ただし、これは1レベルのサブセッションでのみ機能します...

申し訳ありませんが、信頼できる方法を見つけることができませんでした。


[ "$0" = "$BASH_SOURCE" ] || [ -z "$BASH_SOURCE" ]パイプ経由で読み込まれるスクリプト(cat script | bash
hakre

2
はの.エイリアスではないことに注意してくださいsource。実際にはその逆です。source somescript.shBash-ismであり、移植性はありません. somescript.sh。POSIXと移植性のあるIIRCです。
dragon788

32

BASH_SOURCE[]答(後のbash-3.0)、最も簡単なようだが、しかしBASH_SOURCE[]れる関数本体の外に仕事に文書化されていません(それが現在のmanページと不一致で、仕事に起こります)。

Wirawan Purwantoによって提案されている最も堅牢な方法はFUNCNAME[1] 、関数内でチェックすることです

function mycheck() { declare -p FUNCNAME; }
mycheck

次に:

$ bash sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="main")'
$ . sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="source")'

これは、の出力とcaller値をチェックして、呼び出し元のコンテキストmainsource区別することと同じです。を使用FUNCNAME[]すると、caller出力をキャプチャして解析する手間が省けます。ただし、ローカルコールの深さを把握または計算して正確にする必要があります。スクリプトが別の関数またはスクリプト内から供給されるような場合は、配列(スタック)がより深くなります。(FUNCNAMEは特別なbash配列変数であり、それが決してない限り、呼び出しスタックに対応する連続したインデックスを持つ必要がありますunset。)

function issourced() {
    [[ ${FUNCNAME[@]: -1} == "source" ]]
}

(bash-4.2以降${FUNCNAME[-1]}では、配列の最後の項目の代わりに、より単純なフォームを使用できます。以下のDennis Williamsonのコメントのおかげで、改善および簡略化されています。)

ただし、述べられているようにあなたの問題は「私はそれが供給されている場合、「exit」を呼び出さないスクリプトを持っています」です。bashこの状況の一般的なイディオムは次のとおりです。

return 2>/dev/null || exit

スクリプトが読み込まれている場合、returnは読み込まれたスクリプトを終了し、呼び出し元に戻ります。

スクリプトが実行されている場合、returnエラーが返され(リダイレクトされ)、exitスクリプトは通常どおり終了します。両方ともreturnexit必要に応じて終了コードを取得できます。

悲しいことに、これは機能しませんksh(少なくとも、私が持っているAT&T派生バージョンでは機能しません)。これは、関数またはドットソーススクリプトの外部で呼び出された場合returnと同等に扱われexitます。

更新:の最新バージョンでできることは、関数呼び出しの深さに設定されてkshいる特殊変数をチェックすること.sh.levelです。呼び出されたスクリプトの場合、これは最初は設定されておらず、ドットソースのスクリプトの場合は1に設定されます。

function issourced {
    [[ ${.sh.level} -eq 2 ]]
}

issourced && echo this script is sourced

これはbashのバージョンほど堅牢ではありません。issourced()テストするファイルの最上位または既知の関数の深さで呼び出す必要があります。

(また、規律関数といくつかのデバッグトラップトリックを使用してbash 配列をエミュレートするgithubのこのコードに興味があるかもしれません。)kshFUNCNAME

ここでの正解:http : //mywiki.wooledge.org/BashFAQ/109$-は、シェルの状態の(不完全ではありますが)別の指標としても提供しています。


ノート:

  • "main"と "source"という名前のbash関数を作成することが可能です(組み込みをオーバーライドします)。これらの名前はに表示されFUNCNAME[]ますが、その配列の最後の項目のみがテストされている限り、あいまいさはありません。
  • 良い答えはありませんpdksh。私が見つけることができる最も近いものはにのみ適用されpdkshます。スクリプトをソースするたびに、新しいファイル記述子が開きます(元のスクリプトは10から始まります)。ほとんどの場合、信頼したいものではありません...

${FUNCNAME[(( ${#FUNCNAME[@]} - 1 ))]}スタックの最後(下)のアイテムを取得するにはどうすればよいですか?次に、「メイン」(OPの否定)に対するテストが、私にとって最も信頼できるものでした。
エイドリアン・ギュンター

PROMPT_COMMANDセットがあるFUNCNAME場合、を実行すると、配列の最後のインデックスとして表示されますsource sourcetest.sh。チェックを反転させる(main最後のインデックスとして探す)と、より堅牢に見えますis_main() { [[ ${FUNCNAME[@]: -1} == "main" ]]; }
dimo414

1
マンページには、FUNCNAME関数でのみ使用できると記載されています。を使用したテストによるとdeclare -p FUNCNAMEbash動作は異なります。v4.3は関数の外でエラーを出し、v4.3は関数の外でエラーを出しますdeclare -a FUNCNAME。どちらも(実行される場合)メインスクリプトでmainfor ${FUNCNAME[0]}を返しますが、$FUNCNAME何も返しません。そして$BASH_SOURCE、外部の関数を "ab"で使用しているスクリプトが非常に多いため、これが変更される可能性があるか、変更されることはないと思います。
ティノ

24

編集者注:この回答の解決策はbash確実に機能しますが、それだけです。それはに合理化することができます
(return 2>/dev/null)

TL; DR

returnステートメントを実行してみてください。スクリプトが提供されていない場合、エラーが発生します。そのエラーをキャッチして、必要に応じて続行できます。

これをファイルに入れて、たとえばtest.shと呼びます。

#!/usr/bin/env sh

# Try to execute a `return` statement,
# but do it in a sub-shell and catch the results.
# If this script isn't sourced, that will raise an error.
$(return >/dev/null 2>&1)

# What exit code did that give?
if [ "$?" -eq "0" ]
then
    echo "This script is sourced."
else
    echo "This script is not sourced."
fi

直接実行します。

shell-prompt> sh test.sh
output: This script is not sourced.

ソース:

shell-prompt> source test.sh
output: This script is sourced.

私にとって、これはzshとbashで動作します。

説明

returnあなたは関数の外やスクリプトが供給されていない場合は、それを実行しようとした場合のステートメントは、エラーが発生します。シェルプロンプトからこれを試してください:

shell-prompt> return
output: ...can only `return` from a function or sourced script

そのエラーメッセージを表示する必要はないので、出力をdev / nullにリダイレクトできます。

shell-prompt> return >/dev/null 2>&1

次に、終了コードを確認します。0はOK(エラーは発生していない)を意味し、1はエラーが発生したことを意味します。

shell-prompt> echo $?
output: 1

また、returnサブシェル内でステートメントを実行したいとします。returnステートメントが実行するとき。。。上手 。。。戻り値。サブシェルで実行すると、スクリプトから戻るのではなく、そのサブシェルから戻ります。サブシェルで実行するには、次のようにラップし$(...)ます。

shell-prompt> $(return >/dev/null 2>$1)

これで、サブシェルの内部でエラーが発生したため、サブシェルの終了コードが1であることがわかります。

shell-prompt> echo $?
output: 1

これは私にとって0.5.8-2.1ubuntu2で失敗します $ readlink $(which sh) dash $ . test.sh This script is sourced. $ ./test.sh This script is sourced.
Phil Rutschman 2016

3
POSIXはreturnトップレベルで何をすべきかを指定していません(pubs.opengroup.org/onlinepubs/9699919799/utilities/…)。dashシェル扱いreturnとしてトップレベルexit。以下のような他のシェルbashかはzsh許可されていませんreturn。このような悪用技術特徴である、トップレベルで。
user5754163

$サブシェルの前を削除すると、shで動作します。つまり、の(return >/dev/null 2>&1)代わりにを使用しますが、$(return >/dev/null 2>&1)bashで機能しなくなります。
代名詞'13年

@Eponymous:以来dash、この解決策は仕事は、として機能しない場合shのUbuntu上で、例えば、この溶液はない一般的と連携しますsh。このソリューションは、Bash 3.2.57および4.4.5で私にとっては機能します- $以前のバージョンの有無に(...)かかわらず(ただし、の正当な理由は決してありません$)。
mklement0 2017年

2
returnsource不正に終了したコマンドの直後にスクリプトを実行すると、明示的な戻り値を使用しないと壊れます。拡張編集を提案しました。
DimG

12

FWIW、他のすべての答えを読んだ後、私は次の解決策を思いついた:

更新:実際に、誰かが私に影響を与えた別の回答に後で修正されたエラーを見つけました。ここでの更新も改善だと思います(興味があれば編集を参照してください)。

これはすべてのスクリプトで機能します。スクリプトは、最初#!/bin/bashmain機能しますが、関数の外部で保持されるいくつかの情報(設定など)を学習するために、別のシェルによって提供される場合もあります。

以下のコメントによると、この回答は明らかにすべてのbashバリアントで機能するわけではありません。また、/bin/shはに基づいてbashいるシステムにも当てはまりません。IE bashはMacOSのv3.xでは失敗します。(現在、私はこれを解決する方法がわかりません。)

#!/bin/bash

# Function definitions (API) and shell variables (constants) go here
# (This is what might be interesting for other shells, too.)

# this main() function is only meant to be meaningful for bash
main()
{
# The script's execution part goes here
}

BASH_SOURCE=".$0" # cannot be changed in bash
test ".$0" != ".$BASH_SOURCE" || main "$@"

最後の2行の代わりに、次の(私の意見では読みにくい)コードをBASH_SOURCE使用set -eして、他のシェルで設定しないで、で作業できるようにすることができmainます。

if ( BASH_SOURCE=".$0" && exec test ".$0" != ".$BASH_SOURCE" ); then :; else main "$@"; fi

このスクリプトレシピには、次のプロパティがあります。

  • bash通常の方法で実行された場合、main呼び出されます。これにはbash -x scriptscriptパスが含まれない)などの呼び出しは含まれません。以下を参照してください。

  • 供給した場合bashmain呼び出し元のスクリプトが同じ名前を持つように発生した場合にのみ、と呼ばれています。(たとえば、それ自体がソースである場合、またはbash -c 'someotherscript "$@"' main-script args..どこにあるmain-script必要があるかによって、とtest見なされるもの$BASH_SOURCE)。

  • ソースの場合/ /読み込み/実行eval以外のシェルによって編bashmain呼び出されません(BASH_SOURCE常にとは異なります$0)。

  • main次のように空の文字列にbash設定$0しない限り、標準入力からスクリプトを読み取る場合は呼び出されません。( exec -a '' /bin/bash ) <script

  • 他のスクリプト内からbashwith evaleval "`cat script`" すべての引用符が重要です!)によって評価された場合、これはを呼び出しますmainevalをコマンドラインから直接実行する場合、これは前のケースと同様であり、スクリプトはstdinから読み取られます。(BASH_SOURCEは空白ですが、$0通常は/bin/bash完全に異なるものに強制されていません。)

  • main呼び出されない場合は、戻りますtrue$?=0)。

  • これは予期しない動作に依存しません(以前はドキュメント化されていませんunsetでしたがBASH_SOURCE、どちらも変更できないドキュメントは見つかりませんでした)。

    • BASH_SOURCEbashの予約済み配列です。しかし、BASH_SOURCE=".$0"変更を許可すると、非常に危険なワームの缶が開かれるため、これは効果がないはずです(おそらく、いくつかの醜い警告がの将来のバージョンで表示されることを除いてbash)。
    • BASH_SOURCE関数の外部で機能するドキュメントはありません。ただし、その反対(関数でのみ機能する)は文書化されていません。観察結果は、それが機能すること(bashv4.3とv4.4でテスト済み、残念ながらbashもうv3.xがない)であり、$BASH_SOURCE観察されたように機能しなくなった場合、非常に多くのスクリプトが壊れることになります。したがって、私の期待は、のBASH_SOURCE将来のバージョンでbashもそのままです。
    • 対照的に(良い検索、BTW!)を検討してください( return 0 )。これにより0、ソースが提供されている1かどうかがわかります。 これは私にとってだけでなく、予想外のことです。(そこでの読みによると)POSIXによると、returnサブシェルからの動作は未定義です(そして、returnここは明らかにサブシェルからのものです)。おそらく、この機能は最終的には十分に広く使用されるようになり、変更できなくなりますが、AFAICSの場合、将来のbashバージョンによっては、誤って戻り動作が変更される可能性が高くなります。
  • 残念ながらbash -x script 1 2 3動作しませんmain(パスがないscript 1 2 3場所scriptを比較してください)。以下は回避策として使用できます。

    • bash -x "`which script`" 1 2 3
    • bash -xc '. script' "`which script`" 1 2 3
    • それはbash script 1 2 3実行されませんmain機能と考えることができます。
  • ( exec -a none script )呼び出しに注意してくださいmain(これはスクリプトにbash渡されません。$0このため-c、最後のポイントで示したように使用する必要があります)。

したがって、いくつかのコーナーケースを除いてmain、スクリプトが通常の方法で実行されたときにのみ呼び出されます。 通常、これは必要なものです。特に、理解しにくい複雑なコードが不足しているためです。

Pythonコードに非常に似ていることに注意してください。

if __name__ == '__main__': main()

mainスクリプトをインポート/ロードして強制することができるため、一部の例外を除いて、の呼び出しも防止されます。__name__='__main__'

これが課題を解決する一般的な方法であると私が思う理由

複数のシェルからソースを取得できるものがある場合は、互換性がある必要があります。ただし(他の回答を読んで)、ING を検出するための(実装が簡単な)ポータブルな方法sourceがないため、ルールを変更する必要があります

スクリプトをで実行する必要があることを強制することにより/bin/bash、正確にこれを実行します。

これはすべてのケースを解決しますが、以下の場合、スクリプトは直接実行できません。

  • /bin/bash インストールされていないか、機能していない(つまり、ブート環境にある)
  • パイプでシェルのように curl https://example.com/script | $SHELL
  • (注:これは、bash最近の場合にのみ当てはまります。このレシピは、特定のバリアントで失敗することが報告されています。そのため、自分のケースで機能することを確認してください。)

ただし、それが必要な本当の理由や、まったく同じスクリプトを並行してソース化する機能については考えられません。通常、それをラップしてmain手動で実行できます。そのように:

  • $SHELL -c '. script && main'
  • { curl https://example.com/script && echo && echo main; } | $SHELL
  • $SHELL -c 'eval "`curl https://example.com/script`" && main'
  • echo 'eval "`curl https://example.com/script`" && main' | $SHELL

ノート

  • この答えは、他のすべての答えの助けがなければ不可能でした!間違ったものでも-最初にこれを投稿させました。

  • 更新:https ://stackoverflow.com/a/28776166/490291にある新しい発見により編集されました


kshおよびbash-4.3でテスト済み。いいね。他の回答がすでに何年も賛成票を集めていたことを考えると、あなたの回答が困難な人生を送るのは残念です。
hagello 2018年

この回答をありがとうございます。両方の状況を処理して少なくとも非サイレント障害を発生させるのは良いことなので、IFステートメントを使用した、長くて「読みにくい」テストに感謝します。私の場合、スクリプトを入手するか、ソースを使用しない場合のエラーをユーザーに通知する必要があります。
Tim Richardson

@Tino:「だけでなくさまざまなシェルによって供給されるかもしれない」について:オンMacOSの、/bin/sh効果的であるbashに割り当てる、POSIXモードでBASH_SOURCE 休憩スクリプト。他のシェルでは(dashkshzsh)、ファイルの引数として渡すことで、スクリプトを呼び出すシェル実行可能ファイルに直接誤動作(例えば、zsh <your-script>スクリプトが誤ってそれがいると思うようになります調達)。(すでに、すべてのシェルでコードのパイプが誤動作していると述べました。)
mklement0

@Tino:余談ですが、. <your-script>(ソーシング)は原則としてすべてのPOSIX風のシェルで機能しますが、スクリプトがPOSIX機能のみを使用するように明示的に記述されている場合にのみ意味があり、1つのシェルに固有の機能が実行を妨害しないようにします。他のシェルでは; したがって、(ではなく)Bashシバン行を使用すると#!/bin/sh混乱が生じます-少なくとも目立つコメントはありません。逆に、スクリプトがBashのみから実行されることを意図している場合(たとえ移植できない機能を考慮しないためであっても)、非Bashシェルでの実行を拒否することをお勧めします。
mklement0

1
@ mklement0おかげで、問題があるというメモが追加されました。 他の読者の場合 bash v3.xで調達した場合は実行されませんmainが、この場合は実行されます。供給する場合と/bin/sh、しているbash --posix、同じことが、この場合に発生し、それは同様に平野間違っています。
Tino、

6

これは後でスクリプトで機能し、_変数に依存しません。

## Check to make sure it is not sourced:
Prog=myscript.sh
if [ $(basename $0) = $Prog ]; then
   exit 1  # not sourced
fi

または

[ $(basename $0) = $Prog ] && exit

1
この回答は、ここで数少ないPOSIX準拠の1つだと思います。明らかな欠点は、ファイル名を知っている必要があり、両方のスクリプトが同じファイル名を持っている場合は機能しないことです。
JepZ

5

BASH固有の答えを示します。コーンシェル、ごめんなさい。スクリプト名がであるとしますinclude2.sh。次に、呼び出されたに関数作成します。これが私のデモバージョンです:include2.sham_I_sourcedinclude2.sh

am_I_sourced()
{
  if [ "${FUNCNAME[1]}" = source ]; then
    if [ "$1" = -v ]; then
      echo "I am being sourced, this filename is ${BASH_SOURCE[0]} and my caller script/shell name was $0"
    fi
    return 0
  else
    if [ "$1" = -v ]; then
      echo "I am not being sourced, my script/shell name was $0"
    fi
    return 1
  fi
}

if am_I_sourced -v; then
  echo "Do something with sourced script"
else
  echo "Do something with executed script"
fi

次に、さまざまな方法で実行してみます。

~/toys/bash $ chmod a+x include2.sh

~/toys/bash $ ./include2.sh 
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script

~/toys/bash $ bash ./include2.sh 
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script

~/toys/bash $ . include2.sh
I am being sourced, this filename is include2.sh and my caller script/shell name was bash
Do something with sourced script

したがって、これは例外なく機能し、もろい$_ものは使用していません。このトリックは、BASHのイントロスペクション機能、つまり組み込み変数FUNCNAMEBASH_SOURCE;を使用します。bashのマニュアルページにあるドキュメントを参照してください。

2つの警告のみ:

1)コールはするam_I_called 必要があります場所を取るソース化スクリプトが、しない範囲内、任意の関数LEST ${FUNCNAME[1]}何か他のものを返します。ええ...あなたはチェックすることができ${FUNCNAME[2]}たでしょう-しかしあなたはただあなたの人生をより困難にします

2)含まれているファイルの名前を知りたい場合は、関数がソーススクリプトに存在するam_I_called 必要あります。


1
明確化:この機能を使用するには、BASHバージョン3以降が必要です。BASH 2では、FUNCNAMEは配列ではなくスカラー変数です。また、BASH 2にはBASH_SOURCE配列変数がありません。
Wirawan Purwanto 2012

4

Dennisの非常に役立つ回答を少し修正して、少し移植性を高めたいと思います。

[ "$_" != "$0" ] && echo "Script is being sourced" || echo "Script is a subshell"

[[(やや残忍な私見)Debian POSIX互換シェルによって認識されていないためですdash。また、スペースを含むファイル名から保護するために引用符が必要になる場合もあります(このシェルでも同様です)。


2

$_かなりもろいです。スクリプトで最初に行うこととして、それを確認する必要があります。それでも、シェルの名前(ソースの場合)またはスクリプトの名前(実行された場合)が含まれているとは限りません。

たとえば、ユーザーがを設定した場合、BASH_ENVスクリプトの上部には、スクリプトで$_最後に実行されたコマンドの名前が含まれBASH_ENVます。

私が見つけた最良の方法は、次の$0ように使用することです:

name="myscript.sh"

main()
{
    echo "Script was executed, running main..."
}

case "$0" in *$name)
    main "$@"
    ;;
esac

残念ながら、functionargzeroオプションがその名前が示す以上のことを行い、デフォルトでオンになっているため、この方法はzshの初期状態では機能しません。

これを回避するために、を入れunsetopt functionargzeroました.zshenv


1

mklement0のコンパクトな表現を踏襲しました

それはすばらしいですが、kshの場合、次のように呼び出すと失敗する可能性があることに気付きました。

/bin/ksh -c ./myscript.sh

(それはそれが供給されていると考えていますが、それはサブシェルを実行するためではありません)しかし、式はこれを検出するように機能します:

/bin/ksh ./myscript.sh

また、式がコンパクトであっても、構文はすべてのシェルと互換性があるわけではありません。

したがって、bash、zsh、dash、kshで機能する次のコードで終了しました

SOURCED=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
    [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && SOURCED=1
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
    [[ $0 != "$BASH_SOURCE" ]] && SOURCED=1
elif grep -q dash /proc/$$/cmdline; then
    case $0 in *dash*) SOURCED=1 ;; esac
fi

エキゾチックなシェルのサポートを追加してください:)


ではksh 93+uksh ./myscript.sh私にとっては問題なく動作します(私のステートメントを使用)-どのバージョンを使用していますか?
mklement0 2017年

スクリプトがPOSIX機能のみを使用して供給されているかどうかを確実に判断する方法がないのではないかと恐れています。この試みはLinux(/proc/$$/cmdline)を想定しておりdash、(shたとえばUbuntu でも機能する)Linux のみに焦点を当てています。特定の仮定をする$0用意がある場合は、移植可能な妥当な(ただし不完全な)テストを調べることができます。
mklement0 2017年

ただし、基本的なアプローチについては++です。私は、私の回答の補遺として、sh/ をサポートする移植性の最もよい近似であると私が考えるものに自由に適応できるようにしましたdash
mklement0 2017年

0

kshとbashの両方でこれを行うポータブルな方法はないと思います。bashではcaller出力を使用してそれを検出できますが、kshに同等のものは存在しないと思います。


$0で動作しbashksh93pdkshksh88テストする必要はありません。
ミケル

0

[mac、linux]で動作するbash.version> = 3のワンライナーが必要でしたが、これらの答えはどれも適切ではありません。

[[ ${BASH_SOURCE[0]} = $0 ]] && main "$@"

1
bash解決策は、(あなたが単純化できうまく動作します$BASH_SOURCE)が、ksh解決策は、堅牢ではありません:あなたのスクリプトはによって供給されている場合、別のスクリプト、あなたは偽陽性を得るでしょう。
mklement0 2015

0

要は、変数"$ 0"がシェルの名前と等しいかどうかを評価する必要があります。


このような:

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" ]] ; then
    echo "The script was sourced."
else
    echo "The script WAS NOT sourced."
fi


シェル経由

$ bash check_source.sh 
First Parameter: check_source.sh

The script WAS NOT sourced.

SOURCE経由

$ source check_source.sh
First Parameter: bash

The script was sourced.



スクリプトがソースであるかどうかを検出するための100%ポータブルな方法を用意するのはかなり困難です。

私の経験(Shellscriptingで7年間)、唯一の安全な方法(PIDなどの環境変数に依存せず、それがVARIABLEであるという事実のため安全ではない)に関しては、次のことを行う必要があります。

  • あなたの可能性を広げる
  • 必要に応じて、スイッチ/ケースを使用します。

どちらのオプションも自動スケーリングできませんが、より安全な方法です。



例えば:

SSHセッションを介してスクリプトをソースする場合、変数"$ 0"によって返される値(sourceを使用する場合)は-bashです。

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" || "$0" == "-bash" ]] ; then
    echo "The script was sourced."
else
    echo "The script WAS NOT sourced."
fi

または

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" ]] ; then
    echo "The script was sourced."
elif [[ "$0" == "-bash" ]] ; then
    echo "The script was sourced via SSH session."
else
    echo "The script WAS NOT sourced."
fi

2
:これは単純に間違っているとして、Downvoted /bin/bash -c '. ./check_source.sh'できますThe script WAS NOT sourced.。同じバグ:ln -s /bin/bash pumuckl; ./pumuckl -c '. ./check_source.sh'->The script WAS NOT sourced.
Tino

2
あなたの反対投票によってシナリオ全体が変更され、Tinoは大きな貢献をしました。ありがとう!
ivanleoncz

0

私はチェックしてしまいました [[ $_ == "$(type -p "$0")" ]]

if [[ $_ == "$(type -p "$0")" ]]; then
    echo I am invoked from a sub shell
else
    echo I am invoked from a source command
fi

使用はときにcurl ... | bash -s -- ARGSオンザフライでのリモートスクリプトを実行するには、$ 0がただになるbash代わりに、通常の/bin/bash私が使用して、実際のスクリプトファイルを実行したときにtype -p "$0"はbashのフルパスを表示します。

テスト:

curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE

source <(curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath)
relpath /a/b/c/d/e /a/b/CC/DD/EE

wget https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath
chmod +x relpath
./relpath /a/b/c/d/e /a/b/CC/DD/EE

0

これは、「ユニバーサル」クロスシェルサポートに関する他のいくつかの回答からのスピンオフです。これは確かにhttps://stackoverflow.com/a/2942183/3220983と非常によく似ていますが、少し異なります。これの弱点は、クライアントスクリプトがその使用方法を尊重する必要があることです(つまり、最初に変数をエクスポートすることにより)。強みは、これが単純で「どこでも」機能することです。これがカットアンドペーストのテンプレートです。

# NOTE: This script may be used as a standalone executable, or callable library.
# To source this script, add the following *prior* to including it:
# export ENTRY_POINT="$0"

main()
{
    echo "Running in direct executable context!"
}

if [ -z "${ENTRY_POINT}" ]; then main "$@"; fi

注:exportこのメカニズムをサブプロセスに拡張できることを確認して使用します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.