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

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

私が望むときに終了しないスクリプトがあります。

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

#!/bin/bash

function bla() {
    return 1
}

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

echo '2'

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

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

しかし、実際には次のようになります。

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

コマンド接続は()どのようにスコープを生成しますか?exitスクリプトでない場合は何を終了しますか?

答え1

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

Bashのマニュアルから:

(リスト)リストはサブシェル環境で実行されます。シェル環境に影響を与える変数割り当ておよび組み込みコマンドは、コマンドの完了後に無効になります。戻り状態はリストの終了状態です。

{リスト; }listは現在のシェル環境でのみ実行されます。リストは改行またはセミコロンで終わる必要があります。これをグループコマンドと呼びます。戻り状態はリストの終了状態です。メタ文字(および)とは異なり、{と}は予約語であるため、予約語を認識できる場所に表示する必要があります。単語の分離を引き起こさないので、スペースやその他のシェルメタ文字でリストと区別する必要があります。

シェル構文は非常に一貫しており、サブシェルは()コマンド置換(以前のスタイル`..`構文を使用)やプロセス置換などの他の構成にも参加しているため、以下は現在シェルで終了しません。

echo $(exit)
cat <(exit)

コマンドが明示的に内部に配置されたときにサブシェルが関連付けられることは明らかですが()、サブシェルがこれらの他の構造内でも生成されるという事実はあまり明らかではありません。

  • コマンドはバックグラウンドで開始されます。

    exit &
    

    man bash(以降)ので、現在のシェルを終了しないでください。

    コマンドが制御演算子&によって終了すると、シェルはサブシェルのバックグラウンドでコマンドを実行します。シェルは、コマンドが完了するのを待たずに状態0を返します。

  • 管路

    exit | echo foo
    

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

    しかし、これに関して、他のシェルは異なる動作をします。たとえば、AT&T はbashパイプラインのすべてのコンポーネントを別々のサブシェルに入れます (タスク制御が有効になっていない呼び出しlastpipeでこのオプションを使用しない限り)、AT&T は現在のシェルで最後の部分をksh実行しますzsh(POSIX では 2 つすべての動作を許可します)。だから

    exit | exit | exit
    

    デフォルトでは、bashでは何もしませんが、次のようにzshを終了します。ついに exit

  • coproc exitexitサブシェルでも実行されます。

答え2

サブシェルで実行するのはexitトラップです。

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

スクリプトは42を印刷して終了します。サブシェル戻りコードを使用すると、1スクリプトの実行が続行されます。のリターンコードに関係なく、リターンコードは常にゼロなので、呼び出しをに置き換えるのはecho $(CALC) || exit 1役に立ちません。そして前に実行してください。echocalccalcecho

exitlocalより混乱しているのは、次のスクリプトに示すように組み込みスクリプトでラップして効果をブロックすることです。入力値を検証する関数を書くときに、この問題が偶然発見されました。例:

20141211.log今日のファイル「年月日.log」というファイルを作成したいと思います。合理的な価値を提供できないユーザーが日付を入力しました。したがって、私の関数はfname戻り値をチェックしdateてユーザー入力の妥当性を確認します。

#!/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…サブシェルで誤った入力データが検出されたことを示します。ただし、コマンドが常に返されるため、行末exit 1local …トリガーされません。処刑されるからです。local0local後ろに $(fname)したがって、戻りコードを上書きします。したがって、スクリプトは続行され、空のパラメータをtouch使用して呼び出されます。この例は簡単ですが、実際のアプリケーションではbashの動作が非常に混乱する可能性があります。実際のプログラマはローカルを使用しないことを知っています。 ☺

明確に言えば、そうでない場合、local間違った日付を入力すると、スクリプトは期待どおりに中断されます。

解決策は線を分離することです

local FNAME
FNAME=$(fname "$1") || exit 1

この奇妙な動作はbashのマニュアルページの文書と一致しますlocal。 "local が関数の外部で使用されているか、無効な名前が指定されているか、name が読み取り専用変数でない限り、戻り状態は 0 です."

バグではありませんが、bashの動作は直感的ではありません。しかし、私は実行順序を知っているので、破損したlocal割り当てを隠すべきではありません。

私の元の答えにはいくつかの不正確な内容が含まれていました。 mikeservと長くて徹底的な議論の終わりに(ありがとう)問題を解決し始めました。

答え3

実際のソリューション:

#!/bin/bash

function bla() {
    return 1
}

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

echo '2'

エラーグループ化は、blaエラー状態が返されたときにのみ実行され、exitサブシェル内では実行されないため、スクリプト全体が停止します。

答え4

括弧はaで始まります。サブシェルシャットダウンは、対応するサブシェルのみを終了します。

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

#!/bin/bash

function bla() {
    return 1
}

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

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

echo '2'

関連情報