サブシェルからシェルスクリプトを終了する


30

このスニペットを検討してください。

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if false; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

通常、funcが呼び出されると、スクリプトが終了します。これは意図した動作です。ただし、次のようなサブシェルで実行される場合

result=`func`

スクリプトは終了しません。つまり、呼び出しコードは毎回関数の終了ステータスをチェックする必要があります。これを回避する方法はありますか?これは何のset -eためですか?


1
私はstderrにメッセージを出力し、スクリプトを停止機能「停止」をしたいが、停止を呼び出す関数は、例のように、サブシェルで実行されたとき、それは停止しません
アーネストAC

2
もちろん、現在のサブシェルではなくサブシェルから終了するためです。関数を直接呼び出すだけですfunc

1
変数に格納する必要がある文字列を返すため、直接呼び出すことはできません
アーネストAC

1
@ErnestAC元の質問のすべての詳細を提供してください。上記の関数は文字列を返しません。

1
@htor私は例を変更しました
アーネストAC

回答:


10

を呼び出す前に元のシェル()を強制終了できます、それはおそらく機能します。しかし:kill $$exit

  • 私にはかなりtoいようです
  • そこに2番目のサブシェルがある場合は壊れます。つまり、サブシェル内でサブシェルを使用します。

代わりに、いくつかの方法のいずれかを使用して、Bash FAQで値を返すことができます。それらのほとんどは、残念ながらそれほど素晴らしいものではありません。各関数呼び出しの後にエラーをチェックするだけで動かなくなるかもしれません(-e多くの問題があります)。それか、Perlに切り替えます。


5
ありがとう。しかし、私はむしろPythonに切り替えたいです。
アーネストAC

2
私が書いているように、それは2019年です。誰かに「Perlに切り替える」と言うのはばかげています。異議を申し立てて申し訳ありませんが、「C」にイライラしている人に、Cobolに切り替えるように伝えますか?アーネストが指摘しているように、Pythonははるかに優れた選択肢です。私の好みはRubyです。いずれにしても、Perl以外は何でも。
グラハムニコルズ

38

たとえば、終了ステータス77がサブシェルの任意のレベルを終了することを意味することを決定できます。

set -E
trap '[ "$?" -ne 77 ] || exit 77' ERR

(
  echo here
  (
    echo there
    (
      exit 12 # not 77, exit only this subshell
    )
    echo ici
    exit 77 # exit all subshells
  )
  echo not here
)
echo not here either

set -EERRトラップとの組み合わせは、set -e独自のエラー処理を定義できるという点で、改良版に少し似ています。

zshでは、ERRトラップは自動的に継承されるためset -E、必要はありません。また、トラップをTRAPERR()関数として定義し$functions[TRAPERR]、次のように変更することもできます。functions[TRAPERR]="echo was here; $functions[TRAPERR]"


1
興味深いソリューション!より明らかにエレガントですkill $$

3
気を付けなければならないのは、このトラップは補間されたコマンドを処理しないことecho "$(exit 77)"です。スクリプトは、私たちが書いたかのようにecho ""
続きます-Warbo

面白い!-Eを持たない(かなり古い)bashに幸運はありますか?おそらく、USERシグナルにトラップを定義し、そのシグナルにキルを使用する必要がありますか?私もいくつかの調査を行います...
オリヴィエデュラック

77の代わりに1を返すために、サブシェルトラップにないときに知る方法は?
16年

7

の代わりにkill $$kill 0ネストされたサブシェルの場合にも機能します(すべての呼び出し元とサイドプロセスがシグナルを受信します)… でも試すことができます。


2
これはプロセスID 0を強制終了しませんか
アーネストAC

5
それはプロセスグループ全体を殺します。不要なものをヒットする可能性があります(たとえば、バックグラウンドで何かを開始した場合)。
デロバート

2
@ErnestACはkill(2)マンページを参照してください。pid≤0には特別な意味があります。
デロバート

0

これを試して ...

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if $1; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

echo "shell..."
func $1

echo "subshell..."
result=`func $1`

echo "shell..."
echo "result=$result"

私が得る結果は...

# test_exitsubshell true
shell...
foo
subshell...
shell...
result=foo
# test_exitsubshell false
shell...
something went wrong

ノート

  • 許可するようにパラメータ化ifテストをするtrueか、false(2つの実行を参照してください)
  • ifテストがの場合、falseサブシェルに到達することはありません。

これは、ユーザーが投稿し、機能しなかったと言った元のアイデアに非常に似ています。これはサブシェルの場合には機能しないと思います。falseを使用したテストは、「シェル」ケースの後に終了し、「サブシェル」テストケースには到達しません。サブシェルは「exit 1」呼び出しを終了しますが、エラーは外部シェルに伝搬しないため、その場合は失敗すると考えています。
stuckj

0

(Bash固有の回答)Bashには例外の概念はありません。ただし、最も外側のレベルでset -o errexit(または同等のもの:set -e)を使用すると、失敗したコマンドにより、サブシェルがゼロ以外の終了ステータスで終了します。これが、これらのサブシェルの実行に関する条件のないネストされたサブシェルのセットである場合、スクリプト全体を効果的に「ロールアップ」して終了します。

これは、さまざまなbashコードの一部を大きなスクリプトに含めようとする場合に注意が必要です。1つのbashチャンクはそれ自体で正常に動作する場合がありますが、errexitの下で(またはerrexitなしで)実行されると、予期しない動作をします。

[192.168.13.16(f0f5e19e)〜22:58:22]#bash -o errexit / tmp / foo
何かがうまくいかなかった
[192.168.13.16(f0f5e19e)〜22:58:31]#bash / tmp / foo
何かがうまくいかなかった
とにかくここに着いた
[192.168.13.16(f0f5e19e)〜22:58:37]#cat / tmp / foo
#!/ bin / bash
やめる () {
    エコー "$ {1}"
    1番出口
}

falseの場合; それから
    エコー "foo"
他に
    (
        「何かがうまくいかなかった」を止める
    )
    echo「しかし、とにかくここに着いた」
fi
[192.168.13.16(f0f5e19e)〜22:58:40]#

-2

1つのライナーで終了する私の例:

COMAND || ( echo "ERROR – executing COMAND, exiting..." ; exit 77 );[ "$?" -eq 77 ] && exit

1
これは、OPが要求するサブシェルで実行されているコマンドで実際に機能する答えではないようです。コメントや理由のない下票は、悪い答えと同じくらい役に立たない。
DVS
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.