「set -e」は条件文でエラーが発生してもスクリプトを終了しません

「set -e」は条件文でエラーが発生してもスクリプトを終了しません

次のスクリプトには構文エラーまたは何らかのエラーがあります。

#!/usr/bin/env bash
set -euo pipefail

if [ ! -f /custom.log]; then
  echo "test"
fi
abcxyz

スクリプトは失敗し、次の出力が表示されます:

./test.sh: line 4: [: missing `]'
./test.sh: line 7: abcxyz: command not found

このスクリプトを修正する方法については気にしていませんが、このエラーが発生した場合にスクリプトがそれ以上進まないようにするにはどうすればよいでしょうか?set -eこの動作を強制する必要があると思います。

答え1

set -eif//構文の条件セクションや の左側などの条件として使用される失敗したコマンド、whileまたはそれらの条件下で呼び出される関数、サブシェル、ソースファイル、 ed コードではトリガーされません。until||&&eval

もしそうなら、次のようになります。

if [ ! -f /custom.log ]; then

/custom.log通常のファイルの場合は[、ゼロ以外の終了ステータスで終了するため、スクリプトが終了します。

[シェルの組み込みコマンド(bashおよび他のほとんどの実装) は、1テストされた条件が満たされない場合、および2構文エラーがある場合 (ただし、すべての構文エラーがそうであるとは限りません。たとえば、 ではそうではありません[ -v 'a[+]' ]) 、ステータスとともに終了します。POSIXでは、エラーの場合の終了ステータスは1より大きい必要があります。

したがって、次のようにして、条件で使用されているかどうかに関係なく、コマンドが 1 より大きいコードで終了した場合にスクリプトを終了するように選択できます。

shopt -s extdebug # make sure the DEBUG trap propagates to subshells
trap '(($?>1 && (ret=$?))) && exit "$ret"' DEBUG
[ -f / ] || echo / not a regular file # OK
[ -f /] || echo was a syntax error # causes an exit, not output
echo not reached

トラップは によって終了をトリガーするものと同じ条件でのみ実行されるERRため、そのためにはトラップを使用できないことに注意してください。ERRset -e

さて、その影響には注意してください。たとえば、次のようなことが起こります。

if grep -qs pattern /file; then
  echo pattern was found in /file
fi

/fileが存在しなかったり読み取り可能でなかったりした場合には、 は 2 ステータスを返すため、終了しますgrep。ただし、 では-s、明らかにこれらのケースを無視することが意図されていました。

したがって、条件で使用するコマンドが 1 より大きいステータスで終了する可能性がある条件に注意する必要があります。これを回避するには、次のようなものが必要です。

if sh -c 'grep -sq pattern / file || exit 1'; then...

制限することができます終了ステータスが1より大きい場合に終了する[またはコマンドにtest次のように追加します。

unset -v previous_BASH_COMMAND
trap '
  case $previous_BASH_COMMAND in
    ("[ "* | "test "*) (($?>1 && (ret=$?))) && exit "$ret"
  esac
  previous_BASH_COMMAND=$BASH_COMMAND' DEBUG

これにはいくつかの制限があります。

echo x
([ -f/]; echo y)

これにより、サブシェルは終了しますが、親シェルは終了しません。親シェルには が$previous_BASH_COMMAND設定されていないためです。

[ -f / ] && echo a regular file
(grep -qs foo /file && echo foo in /file)
echo here

は 2 で は であるecho hereため、を実行するとシェルは終了します。$?$previous_BASH_COMMAND[ -f / ]

いずれにしても、

[ -f /] | cat
export var="$([ -f /])"

終了ステータスが親シェルプロセスに伝播されないため、検出できませんでした (pipefail最初のケースのオプションを除く)。

さて、開発時(スクリプトを書いてテストするとき)にエラーが簡単に検出できる場合、実行時にこの種の(脆弱な)検出を追加する手間をかける価値があるかどうかはわかりません。

関連情報