web-dev-qa-db-ja.com

Bashスクリプトでエラーを発生させる

「テストケースに失敗しました!!!」というメッセージとともにBashスクリプトでエラーを発生させたい。 Bashでこれを行う方法は?

例えば:

if [ condition ]; then
    raise error "Test cases failed !!!"
fi
75
user3078630

これは、エラーメッセージを保存する場所によって異なります。

次のことができます。

echo "Error!" > logfile.log
exit 125

または以下:

echo "Error!" 1>&2
exit 64

例外を発生させると、プログラムの実行が停止します。

exit xxxのようなものを使用することもできます。ここで、xxxは、オペレーティングシステムに返すエラーコード(0〜255)です。ここで、12564は、終了できるランダムなコードです。プログラムが異常に停止した(たとえば、エラーが発生した)ことをOSに示す必要がある場合は、ゼロ以外の終了コードexitに渡す必要があります。

@chepner 指摘 のように、exit 1を実行できます。これは、不特定のエラーを意味します

91
ForceBru

基本的なエラー処理

テストケースランナーが失敗したテストに対して 非ゼロコード を返す場合、次のように書くことができます。

test_handler test_case_x; test_result=$?
if ((test_result != 0)); then
  printf '%s\n' "Test case x failed" >&2  # write error message to stderr
  exit 1                                  # or exit $test_result
fi

またはさらに短く:

if ! test_handler test_case_x; then
  printf '%s\n' "Test case x failed" >&2
  exit 1
fi

または最短:

test_handler test_case_x || { printf '%s\n' "Test case x failed" >&2; exit 1; }

Test_handlerの終了コードで終了するには:

test_handler test_case_x || { ec=$?; printf '%s\n' "Test case x failed" >&2; exit $ec; }

高度なエラー処理

より包括的なアプローチを取りたい場合は、エラーハンドラを使用できます。

exit_if_error() {
  local exit_code=$1
  shift
  [[ $exit_code ]] &&               # do nothing if no error code passed
    ((exit_code != 0)) && {         # do nothing if error code is 0
      printf 'ERROR: %s\n' "$@" >&2 # we can use better logging here
      exit "$exit_code"             # we could also check to make sure
                                    # error code is numeric when passed
    }
}

次に、テストケースの実行後に呼び出します。

run_test_case test_case_x
exit_if_error $? "Test case x failed"

または

run_test_case test_case_x || exit_if_error $? "Test case x failed"

exit_if_errorのようなエラーハンドラを使用する利点は次のとおりです。

  • loggingstack trace の出力、通知、クリーンアップなどのすべてのエラー処理ロジックを1か所で標準化できます。
  • エラーハンドラーに引数としてエラーコードを取得させることにより、エラーの終了コードをテストするifブロックの混乱から呼び出し元を解放できます。
  • シグナルハンドラがある場合( trap を使用)、そこからエラーハンドラを呼び出すことができます

エラー処理およびロギングライブラリ

エラー処理とロギングの完全な実装は次のとおりです。

https://github.com/codeforester/base/blob/master/lib/stdlib.sh


関連記事

25
codeforester

この問題に対処するには、さらにいくつかの方法があります。要件の1つは、いくつかのシェルコマンドを含むシェルスクリプト/関数を実行し、スクリプトが正常に実行されたかどうかを確認し、失敗した場合はエラーをスローすることです。

シェルコマンドは通常、返された終了コードに依存して、シェルに予期しないイベントが原因で成功したか失敗したかを通知します。

あなたがしたいことは、これらの2つのカテゴリーに分類されます

  • エラー時に終了
  • エラー時に終了してクリーンアップする

どちらを実行するかに応じて、使用可能なシェルオプションがあります。前者の場合、シェルはset -eのオプションを提供し、後者の場合はtrapEXITを実行できます

スクリプト/関数でexitを使用する必要がありますか?

exitを使用すると、一般に読みやすさが向上します。特定のルーチンでは、答えがわかれば、すぐに呼び出しルーチンに戻ります。エラーが検出された後、さらにクリーンアップを必要としないようにルーチンが定義されている場合、すぐに終了しないということは、さらにコードを記述する必要があることを意味します。

そのため、スクリプトの終了をクリーンにするためにスクリプトでクリーンアップアクションを実行する必要がある場合は、notを使用してexitを使用することをお勧めします。

終了時のエラーにset -eを使用する必要がありますか?

番号!

set -eは、シェルに「自動エラー検出」を追加する試みでした。その目標は、エラーが発生したときにシェルを中断させることでしたが、たとえば潜在的な落とし穴がたくさんあります。

  • Ifテストの一部であるコマンドは無効です。この例では、存在しないディレクトリのtestチェックで中断すると予想されますが、そうではなく、else条件に進みます。

    set -e
    f() { test -d nosuchdir && echo no dir; }
    f
    echo survived
    
  • 最後のパイプライン以外のパイプライン内のコマンドは影響を受けません。以下の例では、最後に実行された(右端の)コマンドの終了コードが考慮され(cat)、成功したためです。これはset -o pipefailオプションで設定することで回避できますが、それでも警告です。

    set -e
    somecommand that fails | cat -
    echo survived 
    

使用が推奨されています-終了時にtrap

判断は、set -eを使用する代わりに、盲目的に終了する代わりにエラーを処理できるようにする場合、trap疑似信号でERRを使用します。

ERRトラップは、シェル自体がゼロ以外のエラーコードで終了するときにコードを実行するのではなく、条件の一部ではないシェルによって実行されるコマンド(if cmdcmd ||など)が終了するときゼロ以外の終了ステータス。

一般的な方法は、トラップハンドラを定義して、どの行と何が終了を引き起こすかに関する追加のデバッグ情報を提供することです。 ERRシグナルを発生させた最後のコマンドの終了コードは、この時点でまだ利用可能であることに注意してください。

cleanup() {
    exitcode=$?
    printf 'error condition hit\n' 1>&2
    printf 'exit code returned: %s\n' "$exitcode"
    printf 'the command executing at the time of the error was: %s\n' "$BASH_COMMAND"
    printf 'command present on line: %d' "${BASH_LINENO[0]}"
    # Some more clean up code can be added here before exiting
    exit $exitcode
}

失敗しているスクリプトの上で、このハンドラーを以下のように使用します

trap cleanup ERR

15行目にfalseを含む単純なスクリプトにこれをまとめると、

error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15

trapは、エラーに関係なく、シグナルEXITでシェルの完了(シェルスクリプトの終了など)でクリーンアップを実行するだけのオプションも提供します。複数の信号を同時にトラップすることもできます。トラップするためにサポートされているシグナルのリストは trap.1p-Linuxマニュアルページ にあります。

別の注意点は、サブシェルを扱っている場合、提供されているメソッドはどれも機能しないことを理解することです。その場合、独自のエラー処理を追加する必要があります。

  • set -eのサブシェルでは機能しません。 falseはサブシェルに制限されており、親シェルに伝搬されることはありません。ここでエラー処理を行うには、独自のロジックを追加して(false) || falseを実行します

    set -e
    (false)
    echo survived
    
  • trapでも同じことが起こります。上記の理由により、以下のロジックは機能しません。

    trap 'echo error' ERR
    (false)
    
6
Inian

STDERRに失敗したものの最後の引数を出力し、失敗した行を報告し、行番号を終了コードとしてスクリプトを終了する簡単なトラップを次に示します。これらは常に素晴らしいアイデアではありませんが、これはあなたが構築できる創造的なアプリケーションを示しています。

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR

それをテストするためのループを備えたスクリプトに入れました。いくつかの乱数のヒットをチェックします。実際のテストを使用できます。救済する必要がある場合は、スローするメッセージでfalse(トラップをトリガーする)を呼び出します。

詳細な機能については、トラップが処理関数を呼び出すようにします。さらにクリーンアップなどを行う必要がある場合は、arg($ _)でいつでもcaseステートメントを使用できます。少し構文的なシュガーのvarに割り当てます-

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
throw=false
raise=false

while :
do x=$(( $RANDOM % 10 ))
   case $x in
   0) $throw "DIVISION BY ZERO" ;;
   3) $raise "MAGIC NUMBER"     ;;
   *) echo got $x               ;;
   esac
done

サンプル出力:

# bash tst
got 2
got 8
DIVISION BY ZERO at 6
# echo $?
6

明らかに、あなたはできる

runTest1 "Test1 fails" # message not used if it succeeds

設計を改善する余地がたくさんあります。

欠点はfalseがきれいではないという事実(したがって砂糖)を含んでおり、トラップをトリッピングする他のことは少し愚かに見えるかもしれません。それでも、私はこの方法が好きです。

4
Paul Hodges

コードが全体的にきれいになるように、エラーメッセージを処理する関数を作成すると便利なことがよくあります。

# Usage: die [exit_code] [error message]
die() {
  local code=$? now=$(date +%T.%N)
  if [ "$1" -ge 0 ] 2>/dev/null; then  # assume $1 is an error code if numeric
    code="$1"
    shift
  fi
  echo "$0: ERROR at ${now%???}${1:+: $*}" >&2
  exit $code
}

これは、前のコマンドからエラーコードを取得し、スクリプト全体を終了するときにデフォルトのエラーコードとして使用します。また、サポートされている場合はマイクロ秒で時刻を記録します(GNU日付の%Nはナノ秒であり、マイクロ秒に切り捨てます)。

最初のオプションがゼロまたは正の整数の場合、それが終了コードになり、オプションのリストから削除されます。次に、スクリプトの名前、Wordの「ERROR」、および時間を使用して、メッセージを標準エラーに報告します(パラメーター拡張を使用して、ナノ秒からマイクロ秒、またはGNU以外の時間、たとえば12:34:56.%Nから12:34:56を切り捨てます) 。 Word ERRORの後にコロンとスペースが追加されますが、提供されたエラーメッセージがある場合のみです。最後に、以前に決定した終了コードを使用してスクリプトを終了し、通常どおりトラップをトリガーします。

いくつかの例(コードがscript.shにあると仮定):

if [ condition ]; then die 123 "condition not met"; fi
# exit code 123, message "script.sh: ERROR at 14:58:01.234564: condition not met"

$command |grep -q condition || die 1 "'$command' lacked 'condition'"
# exit code 1, "script.sh: ERROR at 14:58:55.825626: 'foo' lacked 'condition'"

$command || die
# exit code comes from command's, message "script.sh: ERROR at 14:59:15.575089"
3
Adam Katz

次の2つのオプションがあります。スクリプトの出力をファイルにリダイレクトする、スクリプトにログファイルを導入する、

  1. 出力をファイルにリダイレクトする

ここでは、警告およびエラーメッセージを含む、必要なすべての情報をスクリプトが出力すると仮定します。その後、出力を任意のファイルにリダイレクトできます。

./runTests &> output.log

上記のコマンドは、標準出力とエラー出力の両方をログファイルにリダイレクトします。

このアプローチを使用すると、スクリプトにログファイルを導入する必要がないため、ロジックが少し簡単になります。

  1. スクリプトにログファイルを導入する

スクリプトで、ハードコーディングしてログファイルを追加します。

logFile='./path/to/log/file.log'

またはパラメータで渡す:

logFile="${1}"  # This assumes the first parameter to the script is the log file

スクリプトの上部にあるログファイルに、実行時のタイムスタンプを追加することをお勧めします。

date '+%Y%-m%d-%H%M%S' >> "${logFile}"

その後、エラーメッセージをログファイルにリダイレクトできます。

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
fi

これにより、ログファイルにエラーが追加され、実行が継続されます。重大なエラーが発生したときに実行を停止する場合、スクリプトをexitできます:

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
    # Clean up if needed
    exit 1;
fi

exit 1は、不特定のエラーによりプログラムが実行を停止することを示すことに注意してください。必要に応じてこれをカスタマイズできます。

このアプローチを使用して、ログをカスタマイズし、スクリプトのコンポーネントごとに異なるログファイルを作成できます。


比較的小さなスクリプトがある場合、または他の人のスクリプトを変更せずに実行したい場合、最初のアプローチがより適しています。

ログファイルを常に同じ場所に配置する場合は、2の方が適切なオプションです。また、複数のコンポーネントを含む大きなスクリプトを作成した場合は、各部分を別々にログに記録することができます。オプション。

3
alamoot