如何获取失败的Bash命令的实际行号?

use*_*471 8 bash

在提出一种在我的Bash脚本中捕获错误的方法的过程中,我一直在试验"set -e","set -E"和"trap"命令.在这个过程中,我发现了一些奇怪的行为,如何$LINENO在函数的上下文中进行评估.首先,这是我正在尝试记录错误的简化版本:

#!/bin/bash

set -E
trap 'echo Failed on line: $LINENO at command: $BASH_COMMAND && exit $?' ERR
Run Code Online (Sandbox Code Playgroud)

现在,根据故障发生的位置,行为会有所不同.例如,如果我遵循以上内容:

echo "Should fail at: $((LINENO + 1))"
false
Run Code Online (Sandbox Code Playgroud)

我得到以下输出:

Should fail at: 6
Failed on line: 6 at command: false
Run Code Online (Sandbox Code Playgroud)

一切都如预期.第6行是包含单个命令"false"的行.但是如果我将失败的命令包装在一个函数中并像这样调用它:

function failure {
    echo "Should fail at $((LINENO + 1))"
    false
}
failure
Run Code Online (Sandbox Code Playgroud)

然后我得到以下输出:

Should fail at 7
Failed on line: 5 at command: false
Run Code Online (Sandbox Code Playgroud)

如您所见,$BASH_COMMAND包含正确的失败命令:"false",但是$LINENO将"失败"函数定义的第一行报告为当前命令.这对我来说毫无意义.有没有办法获得引用的行号$BASH_COMMAND

这种行为可能特定于旧版本的Bash.我暂时停留在3.2.51.如果在以后的版本中行为发生了变化,那么知道是否有一种解决方法可以获得我想要的值3.2.51仍然是一件好事.

编辑:我害怕有些人感到困惑,因为我把我的例子分成了几块.让我试着澄清一下我拥有什么,我得到了什么,以及我想要什么.

这是我的脚本:

#!/bin/bash

set -E
function handle_error {
    local retval=$?
    local line=$1
    echo "Failed at $line: $BASH_COMMAND"
    exit $retval
}
trap 'handle_error $LINENO' ERR

function fail {
    echo "I expect the next line to be the failing line: $((LINENO + 1))"
    command_that_fails
}

fail
Run Code Online (Sandbox Code Playgroud)

现在,我期望的是以下输出:

I expect the next line to be the failing line: 14
Failed at 14: command_that_fails
Run Code Online (Sandbox Code Playgroud)

现在,我得到的是以下输出:

I expect the next line to be the failing line: 14
Failed at 12: command_that_fails
Run Code Online (Sandbox Code Playgroud)

第12行不是 command_that_fails.第12行function fail {,这有点不太有帮助.我还研究了${BASH_LINENO[@]}阵列,它并没有获得订单14项.

Cha*_*ffy 10

对于4.1之前的bash版本,需要一个特殊级别的可怕,hacky,性能破坏地狱来解决一个问题,其中,在出现错误时,系统会在调用错误处理程序之前跳回到函数定义点.

#!/bin/bash

set -E
set -o functrace
function handle_error {
    local retval=$?
    local line=${last_lineno:-$1}
    echo "Failed at $line: $BASH_COMMAND"
    echo "Trace: " "$@"
    exit $retval
}
if (( ${BASH_VERSION%%.*} <= 3 )) || [[ ${BASH_VERSION%.*} = 4.0 ]]; then
        trap '[[ $FUNCNAME = handle_error ]] || { last_lineno=$real_lineno; real_lineno=$LINENO; }' DEBUG
fi
trap 'handle_error $LINENO ${BASH_LINENO[@]}' ERR

fail() {
    echo "I expect the next line to be the failing line: $((LINENO + 1))"
    command_that_fails
}

fail
Run Code Online (Sandbox Code Playgroud)


Cha*_*ffy 5

BASH_LINENO是一个数组。您可以参考不同的价值观在里面:${BASH_LINENO[1]}${BASH_LINENO[2]},等备份堆栈。(BASH_SOURCE如果要花哨的地方并实际打印堆栈跟踪,则此数组中的位置与数组中的位置对齐)。

但是,更好的是,您可以在陷阱中注入正确的行号:

failure() {
  local lineno=$1
  echo "Failed at $lineno"
}
trap 'failure ${LINENO}' ERR
Run Code Online (Sandbox Code Playgroud)

您可能还会在/sf/answers/13013031/(带有更完整的错误处理示例)中找到我的先前答案,这很有趣。