トラップを使用してすべてのエラーで関数を呼び出すエラーレポートを作成しようとしています。
Trap "_func" ERR
ERR信号が送信されたラインを取得することは可能ですか?シェルはbashです。
これを行うと、使用されたコマンドを読み取って報告し、いくつかのアクションをログに記録または実行できます。
それとも私はこれですべて間違っているのでしょうか?
私は以下でテストしました:
#!/bin/bash
trap "ECHO $LINENO" ERR
echo hello | grep "asdf"
そして$LINENO
は2を返します。機能しません。
コメントで指摘されているように、あなたの引用は間違っています。トラップ行が最初に解析されるときに$LINENO
が展開されないようにするには、一重引用符が必要です。
これは機能します:
#! /bin/bash
err_report() {
echo "Error on line $1"
}
trap 'err_report $LINENO' ERR
echo hello | grep foo # This is line number 9
それを実行する:
$ ./test.sh
Error on line 9
Bash組み込みの「呼び出し側」を使用することもできます。
#!/bin/bash
err_report() {
echo "errexit on line $(caller)" >&2
}
trap err_report ERR
echo hello | grep foo
ファイル名も出力します:
$ ./test.sh
errexit on line 9 ./test.sh
上記の@Matの答えが本当に好きです。これに基づいて、エラーのコンテキストをもう少し提供する小さなヘルパーを作成しました。
失敗の原因となった行のスクリプトを検査できます。
err() {
echo "Error occurred:"
awk 'NR>L-4 && NR<L+4 { printf "%-5d%3s%s\n",NR,(NR==L?">>>":""),$0 }' L=$1 $0
}
trap 'err $LINENO' ERR
ここでそれは小さなテストスクリプトにあります:
#!/bin/bash
set -e
err() {
echo "Error occurred:"
awk 'NR>L-4 && NR<L+4 { printf "%-5d%3s%s\n",NR,(NR==L?">>>":""),$0 }' L=$1 $0
}
trap 'err $LINENO' ERR
echo one
echo two
echo three
echo four
false
echo five
echo six
echo seven
echo eight
実行すると、次のようになります。
$ /tmp/test.sh
one
two
three
four
Error occurred:
12 echo two
13 echo three
14 echo four
15 >>>false
16 echo five
17 echo six
18 echo seven
ERR信号が送信されたラインを取得することは可能ですか?
はい、LINENO
変数とBASH_LINENO
変数は、失敗の行とそれにつながる行を取得するのに便利です。
それとも私はこれですべて間違っているのでしょうか?
いいえ、grepの-q
オプションがありません...
echo hello | grep -q "asdf"
...-q
オプションを使用すると、grep
は0
fortrue
を返しますおよび1
forfalse
。そしてBashではそれはtrap
ではなくTrap
...です.
trap "_func" ERR
...ネイティブソリューションが必要です...
これは、循環的複雑度が少し高いものをデバッグするのに役立つと思われるトラッパーです...
## Outputs Front-Mater formatted failures for functions not returning 0
## Use the following line after sourcing this file to set failure trap
## trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
failure(){
local -n _lineno="${1:-LINENO}"
local -n _bash_lineno="${2:-BASH_LINENO}"
local _last_command="${3:-${BASH_COMMAND}}"
local _code="${4:-0}"
## Workaround for read EOF combo tripping traps
if ! ((_code)); then
return "${_code}"
fi
local _last_command_height="$(wc -l <<<"${_last_command}")"
local -a _output_array=()
_output_array+=(
'---'
"lines_history: [${_lineno} ${_bash_lineno[*]}]"
"function_trace: [${FUNCNAME[*]}]"
"exit_code: ${_code}"
)
if [[ "${#BASH_SOURCE[@]}" -gt '1' ]]; then
_output_array+=('source_trace:')
for _item in "${BASH_SOURCE[@]}"; do
_output_array+=(" - ${_item}")
done
else
_output_array+=("source_trace: [${BASH_SOURCE[*]}]")
fi
if [[ "${_last_command_height}" -gt '1' ]]; then
_output_array+=(
'last_command: ->'
"${_last_command}"
)
else
_output_array+=("last_command: ${_last_command}")
fi
_output_array+=('---')
printf '%s\n' "${_output_array[@]}" >&2
exit ${_code}
}
...そして、関数トレースのために上記のトラップを設定する方法の微妙な違いを明らかにするための使用例スクリプトも...
#!/usr/bin/env bash
set -E -o functrace
## Optional, but recommended to find true directory this script resides in
__SOURCE__="${BASH_SOURCE[0]}"
while [[ -h "${__SOURCE__}" ]]; do
__SOURCE__="$(find "${__SOURCE__}" -type l -ls | sed -n 's@^.* -> \(.*\)@\1@p')"
done
__DIR__="$(cd -P "$(dirname "${__SOURCE__}")" && pwd)"
## Source module code within this script
source "${__DIR__}/modules/trap-failure/failure.sh"
trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
something_functional() {
_req_arg_one="${1:?something_functional needs two arguments, missing the first already}"
_opt_arg_one="${2:-SPAM}"
_opt_arg_two="${3:0}"
printf 'something_functional: %s %s %s' "${_req_arg_one}" "${_opt_arg_one}" "${_opt_arg_two}"
## Generate an error by calling nothing
"${__DIR__}/nothing.sh"
}
## Ignoring errors prevents trap from being triggered
something_functional || echo "Ignored something_functional returning $?"
if [[ "$(something_functional 'Spam!?')" == '0' ]]; then
printf 'Nothing somehow was something?!\n' >&2 && exit 1
fi
## And generating an error state will cause the trap to _trace_ it
something_functional '' 'spam' 'Jam'
上記はBashバージョン4+でテストされているため、4より前のバージョンが必要な場合はコメントを残します。最小バージョンが4のシステムで障害をトラップできない場合は Open Issue を使用します。
主なテイクアウェイは...
set -E -o functrace
-E
関数内でエラーが発生し、bubble up
-o functrace
を使用すると、関数内の何かが失敗したときに、より多くの冗長性が可能になります
trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
一重引用符は関数呼び出しの周りに使用され、二重引用符は個々の引数の周りに使用されます
LINENO
およびBASH_LINENO
への参照が現在の値の代わりに渡されますが、これはトラップにリンクされた以降のバージョンでは短縮され、最終的な障害ラインが出力に含まれるようになりました
BASH_COMMAND
と終了ステータス($?
)の値が渡され、最初に次のコマンドを取得しますエラーを返し、エラー以外のステータスでトラップがトリガーされないようにするための2番目
そして、他の人は同意しないかもしれませんが、出力配列を構築し、それ自体の行に各配列要素を出力するためにprintfを使用する方が簡単だと思います...
printf '%s\n' "${_output_array[@]}" >&2
...最後に>&2
ビットを指定すると、エラーが発生するはずの場所(標準エラー)に移動し、エラーのみをキャプチャできるようになります...
## ... to a file...
some_trapped_script.sh 2>some_trapped_errros.log
## ... or by ignoring standard out...
some_trapped_script.sh 1>/dev/null
スタックオーバーフローでこれらと 他の例 に示されているように、組み込みユーティリティを使用してデバッグ支援を構築する方法はたくさんあります。
他の答えに触発されて、これがより簡単なコンテキストエラーハンドラです。
trap '>&2 echo Command failed: $(tail -n+$LINENO $0 | head -n1)' ERR
また、必要に応じて tail&headの代わりにawkを使用 することもできます。
@sanmaiと@unpythonicに触発された別のバージョンがあります。エラーの周りのスクリプト行と、行番号、および終了ステータスが表示されます。これは、awkソリューションよりもシンプルに見えるため、tail&headを使用しています。
読みやすくするために、ここではこれを2行で示しています。必要に応じて、これらの行を1行に結合できます(;
):
trap 'echo >&2 "Error - exited with status $? at line $LINENO:";
pr -tn $0 | tail -n+$((LINENO - 3)) | head -n7' ERR
これはset -euo pipefail
( nofficial strict mode )-未定義の変数エラーはERR
疑似信号を発生させずに行番号を示しますが、他の場合はコンテキストを示します。
出力例:
myscript.sh: line 27: blah: command not found
Error - exited with status 127 at line 27:
24 # Do something
25 lines=$(wc -l /etc/passwd)
26 # More stuff
27 blah
28
29 # Check time
30 time=$(date)