陷阱,ERR和回显错误行


30

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

Trap "_func" ERR

是否有可能获得发送ERR信号的线路?外壳是bash。

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

或者,也许我错了吗?

我测试了以下内容:

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

echo hello | grep "asdf"

$LINENO返回2。不起作用。


您可以查看bash调试器脚本bashdb。似乎第一个参数to trap可以包含在所需上下文中评估的变量。所以trap 'echo $LINENO' ERR'应该工作。
donothing成功地

嗯,尝试了此错误回声| grep命令,它返回Trap语句的行。但我将看一下bashdb
Mechaflash 2012年

很抱歉...我没有在原始问题中指定需要本机解决方案。我编辑了问题。
Mechaflash

抱歉,我让示例行感到厌烦trap 'echo $LINENO' ERR。第一个参数trap是整个echo $LINENO硬引号。这是bash。
donothing成功地

5
@Mechaflash必须是trap 'echo $LINENO' ERR,用单引号而不是双引号。使用您编写的命令,$LINENO在解析第2行时将其展开,因此陷阱为echo 2(或者更确切地说ECHO 2,将输出bash: ECHO: command not found)。
吉尔(Gilles)'所以

Answers:


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,2015年

@geotheory在我的系统grep上,如果存在匹配项,则退出状态为0;如果没有匹配项,则退出状态为1;对于错误,退出状态为> 1。您可以使用echo hello | grep foo; echo $?
Patrick

不,您是对的,这是一个错误:)
geotheory 2015年

您是否不需要在调用行上使用-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

6

我真的很喜欢上面@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)即使失败不是在当前脚本中而是其导入之一,使用的数据提供上下文也会更好。很好!
tricasse

2

受其他答案的启发,这是一个更简单的上下文错误处理程序:

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

如果需要,您也可以使用awk代替尾巴和头部


1
有一个原因是另一个答案通过有问题的行上方的3行和下面的3行提供了上下文-如果错误是从延续行发出的,该怎么办?
iruvar

@iruvar这是可以理解的,但是我不需要任何其他上下文。一行内容就变得尽可能简单,并满足我的需要
Sanmai

好的,我的朋友,+ 1
iruvar,

0

这是另一个受@sanmai和@unpythonic启发的版本。它显示错误周围的脚本行,行号和退出状态-使用尾部和头部,因为这似乎比awk解决方案更简单。

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

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信号的线路?

是的,LINENOBASH_LINENO变量是获取故障的线路,导致了它的线晚饭有用。

或者,也许我错了吗?

不,只是缺少-qgrep选项...

echo hello | grep -q "asdf"

......随着-q选项grep将返回0true1false。而且在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 当函数中的某项失败时,原因会导致更多的冗长

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

  • 引用LINENOBASH_LINENO传递引用而不是当前值,尽管在链接到trap的更高版本中可能会缩短引用,以便最终的失败行使其成为输出

  • 传递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上的这些示例其他示例所示,有很多方法可以使用内置实用程序来构建调试辅助工具。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.