web-dev-qa-db-ja.com

(exit 1)がスクリプトを終了しないのはなぜですか?

必要なときに終了しないスクリプトがあります。

同じエラーのサンプルスクリプトは次のとおりです。

#!/bin/bash

function bla() {
    return 1
}

bla || ( echo '1' ; exit 1 )

echo '2'

私は出力を見ると仮定します:

:~$ ./test.sh
1
:~$

しかし、私は実際に見ます:

:~$ ./test.sh
1
2
:~$

()コマンドチェーンはどういうわけかスコープを作成しますか?スクリプトでない場合、exitは何を終了しますか?

51
Minix

()はサブシェルでコマンドを実行するため、exitを使用すると、サブシェルを終了して親シェルに戻ります。現在のシェルでコマンドを実行する場合は、中かっこ{}を使用します。

Bashマニュアルから:

(list)リストはサブシェル環境で実行されます。シェルの環境に影響を与える変数の割り当てと組み込みコマンドは、コマンドの完了後も有効になりません。返却ステータスはリストの終了ステータスです。

{リスト;}リストは、現在のシェル環境で単純に実行されます。リストは改行またはセミコロンで終了する必要があります。これはグループコマンドと呼ばれます。返却ステータスはリストの終了ステータスです。メタ文字(および)とは異なり、{および}は予約語であり、予約語の認識が許可されている場所に出現する必要があることに注意してください。これらはワードブレークを引き起こさないため、空白または別のシェルメタキャラクターによってリストから分離する必要があります。

シェル構文は非常に一貫しており、サブシェルはコマンド置換(古いスタイルの()構文も使用)やプロセス置換などの他の`..`構文にも参加しているため、次のようになることに言及する価値があります。 t現在のシェルを終了する:

echo $(exit)
cat <(exit)

コマンドが()内に明示的に配置されている場合、サブシェルが関係することは明らかですが、あまり目に見えない事実は、これらが他の構造でも生成されることです。

  • コマンドがバックグラウンドで開始されました

    exit &
    

    man bashの後)のため、現在のシェルを終了しません

    コマンドが制御演算子&によって終了された場合、シェルはサブシェルのバックグラウンドでコマンドを実行します。シェルはコマンドが完了するまで待機せず、戻りステータスは0です。

  • パイプライン

    exit | echo foo
    

    それでもサブシェルからのみ終了します。

    ただし、この点では、シェルが異なると動作が異なります。たとえば、bashは、パイプラインのすべてのコンポーネントを個別のサブシェルに配置します(ジョブ制御が有効になっていない呼び出しでlastpipeオプションを使用しない限り)が、AT&T kshおよびzsh現在のシェル内で最後の部分を実行します(両方の動作がPOSIXで許可されています)。したがって

    exit | exit | exit
    

    基本的にbashでは何もしませんが、最後exitのためにzshを終了します。

  • coproc exitは、サブシェルでexitも実行します。

87
jimmij

サブシェルでexitを実行することは、1つの落とし穴です。

_#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)
_

スクリプトは42を出力し、サブシェルから戻りコード_1_で終了し、スクリプトを続行します。 echoの戻りコードに関係なく、calcの戻りコードは0であるため、呼び出しをecho $(CALC) || exit 1に置き換えても効果がありません。また、calcechoの前に実行されます。

さらに不可解なのは、次のスクリプトのようにexitビルトインにラップすることで、localの効果を妨げていることです。入力値を検証する関数を書いたとき、私はこの問題を偶然見つけました。例:

「年月日.log」という名前のファイル、つまり、今日の_20141211.log_を作成します。日付は、妥当な値を提供できない可能性があるユーザーによって入力されます。したがって、関数fnamedateの戻り値をチェックして、ユーザー入力の有効性を確認します。

_#!/bin/bash

doit ()
    {
    local FNAME=$(fname "$1") || exit 1
    touch "${FNAME}"
    }

fname ()
    {
    date +"%Y%m%d.log" -d"$1" 2>/dev/null
    if [ "$?" != 0 ] ; then
        echo "fname reports \"Illegal Date\"" >&2
        exit 1
    fi
    }

doit "$1"
_

いいね。スクリプトの名前は_s.sh_とします。ユーザーが_./s.sh "Thu Dec 11 20:45:49 CET 2014"_を使用してスクリプトを呼び出すと、ファイル_20141211.log_が作成されます。ただし、ユーザーが_./s.sh "Thu hec 11 20:45:49 CET 2014"_と入力すると、スクリプトは次のように出力します。

_fname reports "Illegal Date"
touch: cannot touch ‘’: No such file or directory
_

_fname…_という行は、サブシェルで不正な入力データが検出されたことを示しています。ただし、localディレクティブは常に_exit 1_を返すため、_local …_行の最後の_0_はトリガーされません。これは、localが実行されるafter$(fname)であり、その結果、戻りコードが上書きされるためです。そのため、スクリプトは続行し、touchを空のパラメーターで呼び出します。この例は単純ですが、bashの動作は実際のアプリケーションではかなり混乱する可能性があります。実際のプログラマはローカルを使用しません。use

明確にするために、localがないと、無効な日付が入力されたときにスクリプトが予期したとおりに中止されます。

修正は次のように行を分割することです

_local FNAME
FNAME=$(fname "$1") || exit 1
_

奇妙な動作は、bashのmanページにあるlocalのドキュメントに準拠しています。

バグではありませんが、bashの動作は直感に反すると思います。私は実行のシーケンスを知っていますが、localは壊れた割り当てをマスクするべきではありません。

私の最初の答えはいくつかの不正確さを含んでいました。 mikeserv(それをありがとう)との明確で綿密な議論の後、私はそれらを修正するために行きました。

13
hermannk

実際の解決策:

#!/bin/bash

function bla() {
    return 1
}

bla || { echo '1'; exit 1; }

echo '2'

エラーのグループ化は、blaがエラーステータスを返し、exitがサブシェルにないため、スクリプト全体が停止した場合にのみ実行されます。

2
Walf

大括弧はサブシェルで始まり、出口はそのサブシェルのみを終了します。

$?を使用して終了コードを読み取り、これをスクリプトに追加して、サブシェルが終了した場合にスクリプトを終了することができます。

#!/bin/bash

function bla() {
    return 1
}

bla || ( echo '1' ; exit 1 )

exitcode=$?
if [ $exitcode != 0 ]; then exit $exitcode; fi

echo '2'
1
rubo77