トラップ、ERR、およびエラー行のエコー


30

トラップを使用してすべてのエラーで関数を呼び出すエラー報告を作成しようとしています:

Trap "_func" ERR

ERR信号の送信元の行を取得することはできますか?シェルはbashです。

それを行うと、使用されたコマンドを読んで報告し、いくつかのアクションを記録/実行できます。

それとも私はこれですべて間違っていますか?

私は以下でテストしました:

#!/bin/bash
trap "ECHO $LINENO" ERR

echo hello | grep "asdf"

そして$LINENO2を返しています。動作していません。


bashデバッガースクリプトを見ることができますbashdb。の最初の引数にtrapは、目的のコンテキストで評価される変数を含めることができるようです。だから、trap 'echo $LINENO' ERR'動作するはずです。
12

うーん、ちょうど悪いエコーでこれを試した| grepコマンドを実行すると、Trapステートメントの行が返されます。しかし、bashdbを見てみましょう
Mechaflash

申し訳ありませんが、元の質問でネイティブソリューションが必要であることを指定しませんでした。質問を編集しました。
メカフラッシュ

申し訳ありませんが、例の行を中断しました:trap 'echo $LINENO' ERR。への最初の引数trapは、echo $LINENOハードクォート全体です。これはbashです。
歯磨きが正常に

5
@Mechaflash trap 'echo $LINENO' ERR二重引用符ではなく、単一引用符で囲む必要があります。あなたが書いたコマンドで$LINENOは、行2が解析されるときに展開されるため、トラップはecho 2(または、むしろECHO 2を出力するbash: ECHO: command not found)です。
ジル 'SO-悪であるのをやめる

回答:


61

コメントで指摘されているように、引用は間違っています。防止するには一重引用符が必要です$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

関数呼び出しの例に感謝します。この場合、二重引用符が変数を展開することを知りませんでした。
Mechaflash

echo hello | grep foo私にはエラーをスローしないようです。私は何かを誤解していますか?
地理理論

@geotheory私のシステムgrepの終了ステータスは、一致した場合は0、一致しなかった場合は1、エラーの場合は> 1です。システムの動作を確認するには、次のようにしますecho hello | grep foo; echo $?
Patrick

いいえ、それはエラーです:)
地理理論

コマンドの失敗時にエラーを引き起こすために、呼び出し行で-eを使用する必要はありませんか?つまり:#!/ bin / bash -e?
ティムバード

14

bash組み込みの 'caller'を使用することもできます。

#!/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

7

上記の@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

これは$(caller)、失敗が現在のスクリプトではなく、そのインポートの1つにある場合でも、コンテキストを提供するためにのデータを使用するのがさらに良いでしょう。でもいいね!
トリカス

2

他の答えに触発されて、ここに簡単なコンテキストエラーハンドラがあります。

trap '>&2 echo Command failed: $(tail -n+$LINENO $0 | head -n1)' ERR

必要に応じて、tail&headの代わりにawkを使用することもできます。


1
他の回答が問題のある行の上の3行と下の3行によってコンテキストを提供する理由があります-エラーが継続行から発生した場合はどうなりますか?
iruvar

@iruvarこれは理解されていますが、追加のコンテキストは必要ありません。コンテキストの1行は、取得するのと同じくらい簡単で、必要に応じて十分です
sanmai

友達よ、+ 1
iruvar

1

@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非公式の厳密モード)で非常にうまく機能します -未定義の変数エラーは、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)

0

ERR信号の送信元の行を取得することはできますか?

はい、LINENOおよびBASH_LINENO変数は、障害のラインとそれに至るまでのラインを取得するための夕食便利です。

それとも私はこれですべて間違っていますか?

いいえ、-qgrepのオプションがありません...

echo hello | grep -q "asdf"

... -qオプションを使用grepすると0for trueおよび1for が返されますfalse。そして、バッシュでtrapはそうではありませんTrap...

trap "_func" ERR

...ネイティブソリューションが必要です...

これは、ややサイクロマティックな複雑さを持っているものをデバッグするのに役立つと思われるトラッパーです...

failure.sh

## 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}
}

...および関数トレース用に上記のトラップを設定する方法の微妙な違いを明らかにするための使用スクリプトの例...

example_usage.sh

#!/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のシステムで障害をトラップできない場合ます。

主なポイントは...

set -E -o functrace
  • -E関数内でエラーが発生バブルアップを

  • -o functrace 原因は、関数内の何かが失敗した場合により多くの冗長性を考慮します

trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
  • 関数呼び出しの周りに一重引用符が使用され、個々の引数の周りに二重引用符が使用されます

  • 参照LINENOとはBASH_LINENO、このトラップにリンクされたそれ以降のバージョンに短縮されるかもしれないけれども、最終的な故障ラインが出力にそれを作るように、電流値の代わりに渡されます。

  • BASH_COMMANDと終了ステータス($?)が渡されます。1つ目はエラーを返したコマンドを取得し、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

スタックオーバーフローに関するこれらの例他の例が示すように、組み込みのユーティリティを使用してデバッグ支援を作成する方法はたくさんあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.