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


48

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

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

#!/bin/bash

function bla() {
    return 1
}

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

echo '2'

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

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

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

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

()何とか連鎖するコマンドは、スコープを作成しますか?exitスクリプトではない場合、何が終了しますか?


5
これには、1つの単語の答えが必要です。サブシェル
ジョシュア14

回答:


87

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

bashマニュアルから:

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

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

シェル構文は非常に一貫性があり、サブシェルは()コマンド置換(古いスタイルの`..`構文でも)やプロセス置換などの他の構成要素にも関与するため、以下も現在のシェルから終了しません。

echo $(exit)
cat <(exit)

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

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

    exit &

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

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

  • パイプライン

    exit | echo foo

    サブシェルからのみ終了します。

    ただし、この点で異なるシェルの動作は異なります。たとえばbash、パイプラインのすべてのコンポーネントを個別のサブシェルに配置します(lastpipeジョブ制御が有効になっていない呼び出しでオプションを使用しない限り)が、AT&T kshおよびzsh現在のシェル内の最後の部分を実行します(両方の動作はPOSIXで許可されます)。副<文>この[前述の事実の]結果として、それ故に、従って、だから◆【同】consequently; therefore <文>このような方法で、このようにして、こんなふうに、上に述べたように◆【同】in this manner <文>そのような程度まで<文> AひいてはB◆【用法】A and thus B <文>例えば◆【同】for example; as an example

    exit | exit | exit

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

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


5
あ。今、私の前任者が間違った中括弧を使用したすべての場所を見つけるために。洞察力をありがとう。
ミニックス14

10
manページ内の間隔の慎重な注意してください:{そして}、彼らは言葉を予約されており、スペースで囲まれていなければならない、とリストは、コマンド・ターミネータで終了しなければなりません(セミコロン、改行、アンパサンド)、構文ではありません
グレンはジャックマン

興味深いことに、これは実際には別のプロセスになる傾向があるのですか、それとも内部スタック内の別の環境に過ぎないのですか?私は()をchdirの分離によく使用しますが、前者であれば$$などを使用して幸運だったに違いありません。
ダンシェパード

5
@DanSheppardこれは別のプロセスですが、サブシェルが作成される前でも展開される(echo $$)ため、親シェルIDを出力し$$ます。実際にサブシェルプロセスIDは難しいかもしれない印刷、参照stackoverflow.com/questions/9119885/...
jimmij

@jimmij、$$サブシェルが作成される前に展開され、サブシェル$BASHPIDの正しい値を表示する方法はありますか?
ワイルドカード

13

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

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

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

さらに不可解な点は、次のスクリプトのように組み込み関数にexitラップすることで、その効果を妨げていlocalます。入力値を検証する関数を書いたとき、私はこの問題につまずいた。例:

「year month day.log」という名前の、つまり20141211.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 1の最後はlocal …トリガーされません。これは、後に実行されるため、戻りコードが上書きされるためです。そのため、スクリプトは続行され、空のパラメーターで呼び出されます。この例は単純ですが、bashの動作は実際のアプリケーションではかなり混乱する可能性があります。実際のプログラマーはローカルを使用しません。☺local0local $(fname)touch

明確にするために:なしではlocal、無効な日付が入力されると、スクリプトは期待どおりに中断します。

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

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

奇妙な動作localは、bashのマニュアルページ内のドキュメントに準拠しています。「関数の外部でlocalを使用するか、無効な名前を指定するか、名前が読み取り専用変数でない限り、戻りステータスは0です。」

バグではありませんが、bashの動作は直観に反すると感じます。私は実行の順序を知っていますがlocal、それでも壊れた割り当てを隠すべきではありません。

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


@mikeserv:関連性を示すための例を追加しました。
ヘルマンク14

@mikeserv:はい、そうです。いっそう簡潔に。しかし、落とし穴はまだそこにあります。
hermannk 14

@mikeserv:申し訳ありませんが、私の例は壊れていました。私はテストを忘れていましたdoit()
ヘルマンク14


2

実際のソリューション:

#!/bin/bash

function bla() {
    return 1
}

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

echo '2'

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


1

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

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

#!/bin/bash

function bla() {
    return 1
}

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

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

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