サブシェルにいるかどうかを検出するにはどうすればよいですか?


24

exitビルトインの機能を置き換える関数を作成して、端末を終了できないようにしています。

SHLVL環境変数を使用しようとしましたが、サブシェル内では変更されないようです:

$ echo $SHLVL
1
$ ( echo $SHLVL )
1
$ bash -c 'echo $SHLVL'
2

私の機能は次のとおりです。

exit () {
    if [[ $SHLVL -eq 1 ]]; then
        printf '%s\n' "Nice try!" >&2
    else
        command exit
    fi
}

exitただし、これによりサブシェル内で使用することはできません:

$ exit
Nice try!
$ (exit)
Nice try!

私がサブシェルにいるかどうかを検出する良い方法は何ですか?



1
それはこのためです。echo $ SHLVLコマンドが「サブシェル」で実行されているにもかかわらず、シェルレベル1のままであるため、$ SHLVLは1 です。その記事によると、括弧で生成されたサブシェルは(...)親プロセスのすべてのプロパティを継承します。提供される回答は、シェルレベルを決定するためのより堅牢なソリューションです。
kemotep


5
@mosvyそれは別の質問だと思う。たとえば、BASH_SUBSHELL答えは(たとえ論争的であっても)その質問には当てはまりません。
Sparhawk

2
HNQのタイトルを見て、これは量子力学の質問だと思った...
Mehrdad

回答:


43

bashで、あなたは比較することができます$BASHPID$$

$ ( if [ "$$" -eq "$BASHPID" ]; then echo not subshell; else echo subshell; fi )
subshell
$   if [ "$$" -eq "$BASHPID" ]; then echo not subshell; else echo subshell; fi
not subshell

bashを使用していない場合$$は、サブシェルで同じままにする必要があります。したがって、実際のプロセスIDを取得する他の方法が必要になります。

実際のpidを取得する1つの方法はsh -c 'echo $PPID'です。単純に( … )それをプレーンに入れると、シェルがフォークを最適化するため、動作しないように見える場合があります。追加のno-opコマンド( : ; sh -c 'echo $PPID'; : )を試して、サブシェルが複雑すぎて最適化できないと考えてください。このアプローチのスタックオーバーフローについては、John1024の功績です。


これを変更することもできます  。John1024の回答に関する私のコメント(sh -c 'echo $PPID'; : )参照しください。
G-Manが「Reinstate Monica」と言う

@Gマン だから、前と後の両方にノーオペレーションを入れました。うまくいけばすべてを処理できます。
デロバート

38

どうBASH_SUBSHELL

BASH_SUBSHELL
      シェル
      がその環境で実行を開始すると、各サブシェルまたはサブシェル環境内で1ずつ増加します。初期値は0です。

$ echo $BASH_SUBSHELL
0
$ (echo $BASH_SUBSHELL)
1

16
映画のインセプションでは便利なコマンドだったでしょう。
エリックドゥミニル

インセプションでは、おそらく$ SHLVL
Granny Aching

19

[これはコメントであるべきでしたが、私のコメントはモデレーターによって削除される傾向があるため、これは削除されても参照として使用できる回答として残ります]

使用BASH_SUBSHELLは、すべてのサブシェルではなく、一部のサブシェルでのみ1に設定されるため、完全に信頼できません。

$ (echo $BASH_SUBSHELL)
1
$ echo $BASH_SUBSHELL | cat
0

パイプラインコマンドが実行されるサブプロセス実際のサブシェルではないと主張する前に、次のman bashスニペットを検討してください。

パイプラインの各コマンドは、個別のプロセスとして(つまり、サブシェルで)実行されます。

実用的な意味-スクリプトフラグメントがサブプロセスで実行されるかどうかは、用語のquiめ言葉ではなく、必須です。

この質問に対する回答で既に説明したように、唯一の解決策は、$BASHPID等しい$$か、移植可能であるが効率がはるかに低いかどうかを確認することです。

if [ "$(exec sh -c 'echo "$PPID"')" != "$$" ]; then
    echo you\'re in a subshell
fi

11
Nit:BASH_SUBSHELLはかなり確実に設定されますが、その値を正しく取得するのは困難です。ドキュメントが言っていることに注意してください:「シェルがその環境で実行を開始すると、各サブシェルまたはサブシェル環境内で1ずつ増加します。」あなたは比較することができecho $BASH_VERSIONdeclare -p BASH_VERSION配管等、バックグラウンドジョブ、後者べきで確実に出力1 -
muru

6
実行開始後に変数が展開されるため、eval 'echo $BASH_SUBSHELL $BASHPID' | catと言う場合でも1が出力BASH_SUBSHELLされます。
ムー

4
これらのすべての引数は、プロセスとコマンドの置換、bgプロセスにも適用する必要がありますが、異なるのはパイプラインのみです。コードを見ると、フォアグラウンドパイプラインの場合、インクリメントはsubshell_level実際には延期されます。これにはおそらく何らかの理由がありますが、私は解決できません;-)
mosvy

2
あなたが正しい。Chetは明示的にそのように意図しているようです。lists.gnu.org/archive/html/bug-bash/2015-06/msg00050.html:「パイプライン要素ではなく、BASH_SUBSHELLメジャー(...)サブシェル」lists.gnu.org/archive/html/bug-bash/2015-06/msg00054.html:「現状を文書化するか、$ BASH_SUBSHELLが反映する「サブシェル」の定義を拡張するかを検討します。 」
ムル

2
@JoLあなたは間違っています、拡張は別のプロセスでも起こります。上記のこの議論のリンクと例を読んでください。またはで試してくださいecho $$ $BASHPID $BASH_SUBSHELL | cat
mosvy
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.