必要なときに終了しないスクリプトがあります。
同じエラーのサンプルスクリプトは次のとおりです。
#!/bin/bash
function bla() {
return 1
}
bla || ( echo '1' ; exit 1 )
echo '2'
私は出力を見ると仮定します:
:~$ ./test.sh
1
:~$
しかし、私は実際に見ます:
:~$ ./test.sh
1
2
:~$
()
コマンドチェーンはどういうわけかスコープを作成しますか?スクリプトでない場合、exit
は何を終了しますか?
()
はサブシェルでコマンドを実行するため、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
も実行します。
サブシェルでexit
を実行することは、1つの落とし穴です。
_#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)
_
スクリプトは42を出力し、サブシェルから戻りコード_1
_で終了し、スクリプトを続行します。 echo
の戻りコードに関係なく、calc
の戻りコードは0であるため、呼び出しをecho $(CALC) || exit 1
に置き換えても効果がありません。また、calc
はecho
の前に実行されます。
さらに不可解なのは、次のスクリプトのようにexit
ビルトインにラップすることで、local
の効果を妨げていることです。入力値を検証する関数を書いたとき、私はこの問題を偶然見つけました。例:
「年月日.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…
_という行は、サブシェルで不正な入力データが検出されたことを示しています。ただし、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(それをありがとう)との明確で綿密な議論の後、私はそれらを修正するために行きました。
実際の解決策:
#!/bin/bash
function bla() {
return 1
}
bla || { echo '1'; exit 1; }
echo '2'
エラーのグループ化は、bla
がエラーステータスを返し、exit
がサブシェルにないため、スクリプト全体が停止した場合にのみ実行されます。
大括弧はサブシェルで始まり、出口はそのサブシェルのみを終了します。
$?
を使用して終了コードを読み取り、これをスクリプトに追加して、サブシェルが終了した場合にスクリプトを終了することができます。
#!/bin/bash
function bla() {
return 1
}
bla || ( echo '1' ; exit 1 )
exitcode=$?
if [ $exitcode != 0 ]; then exit $exitcode; fi
echo '2'