Trap、ERR 和回显错误行

Trap、ERR 和回显错误行

我正在尝试使用陷阱创建一些错误报告,以针对所有错误调用函数:

Trap "_func" ERR

是否可以获得ERR信号是从哪条线路发出的?外壳是bash。

如果我这样做,我可以读取并报告使用了什么命令并记录/执行一些操作。

或者也许我的做法全错了?

我测试了以下内容:

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

echo hello | grep "asdf"

并且$LINENO正在返回 2。不工作。

答案1

正如评论中指出的,您的引用是错误的。您需要单引号以防止$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

答案2

您还可以使用 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

答案3

是否可以获得ERR信号是从哪条线路发出的?

是的,LINENO变量BASH_LINENO对于获取失败行和导致失败的行非常有用。

或者也许我的做法全错了?

没有,只是失踪了-qgrep 选项...

echo hello | grep -q "asdf"

... 与-q选项grep0返回true1false。在 Bash 中是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当函数内的某些内容失败时, Causes 允许更详细的内容

trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
  • 单引号用于函数调用,双引号用于各个参数

  • 参考文献LINENOBASH_LINENO传递而不是当前值,尽管在链接到陷阱的更高版本中这可能会被缩短,以便最终的失败行使其进入输出

  • 的价值观BASH_COMMAND和退出状态($?)被传递,首先是为了获取返回错误的命令,其次是为了确保陷阱不会在非错误状态下触发

虽然其他人可能不同意,但我发现构建输出数组并使用 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

正如这些和所示其他例子在 Stack Overflow 上,有很多方法可以使用内置实用程序构建调试辅助工具。

答案4

这是另一个版本,灵感来自 @sanmai 和 @unpythonic。它显示错误周围的脚本行、行号和退出状态 - 使用 tail 和 head,因为这似乎比 awk 解决方案更简单。

为了便于阅读,将其显示为两行 - 如果您愿意,可以将这些行合并为一行(保留;):

trap 'echo >&2 "Error - exited with status $? at line $LINENO:"; 
         pr -tn $0 | tail -n+$((LINENO - 3)) | head -n7 >&2' ERR

这与set -eEuo 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)

相关内容