私が望むときに終了しないスクリプトがあります。
同じエラーを持つサンプルスクリプトは次のとおりです。
#!/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 exit
exit
サブシェルでも実行されます。
答え2
サブシェルで実行するのはexit
トラップです。
#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)
スクリプトは42を印刷して終了します。サブシェル戻りコードを使用すると、1
スクリプトの実行が続行されます。のリターンコードに関係なく、リターンコードは常にゼロなので、呼び出しをに置き換えるのはecho $(CALC) || exit 1
役に立ちません。そして前に実行してください。echo
calc
calc
echo
exit
local
より混乱しているのは、次のスクリプトに示すように組み込みスクリプトでラップして効果をブロックすることです。入力値を検証する関数を書くときに、この問題が偶然発見されました。例:
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 1
はlocal …
トリガーされません。処刑されるからです。local
0
local
後ろに $(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'