神秘的LINENO在bash陷阱ERR中

hmo*_*liu 11 bash shell

我正在玩bash来绕过这个夏天的下午炎热,突然间我有一个神秘的结果,我无法确定它的起源.

让我解释一下.

我正在使用陷阱ERR来为我的bash脚本创建一些调试功能.

这是运行良好的脚本:

traperror () {
    local err=$? # error status
    local line=$1 # LINENO
    [ "$2" != "" ] && local funcstack=$2 # funcname
    [ "$3" != "" ] && local linecallfunc=$3 # line where func was called
    echo "<---"
    echo "ERROR: line $line - command exited with status: $err" 
    if [ "$funcstack" != "" ]; then
        echo -n "   ... Error at function ${funcstack[0]}() "
        if [ "$linecallfunc" != "" ]; then
            echo -n "called at line $3"
        fi
        echo
    fi
    echo "--->" 
    }
#trap 'traperror $LINENO ${FUNCNAME}' ERR

somefunction () {
trap 'traperror $LINENO ${FUNCNAME} $BASH_LINENO' ERR
asdfas
}

somefunction

echo foo
Run Code Online (Sandbox Code Playgroud)

输出是(stderr /dev/null为了清楚起见; bash错误当然foo.sh: line 23: asdfas: command not found是你知道错误代码127)

~$ bash foo.sh 2> /dev/null 
<---
ERROR: line 21 - command exited with status: 127
   ... Error at function somefunction() called at line 24
--->
foo
Run Code Online (Sandbox Code Playgroud)

所有行号都是正确的,第21行是启动函数"somefunction"的地方,第24行是调用它的地方.

但是如果我取消注释第一个陷阱(main中的那个),我得到这个输出:

~$ bash foo.sh 2> /dev/null 
<---
ERROR: line 21 - command exited with status: 127
   ... Error at function somefunction() called at line 24
--->
<---
ERROR: line 15 - command exited with status: 127
--->
foo
Run Code Online (Sandbox Code Playgroud)

如果我取消注释第一个陷阱并注释第二个陷阱,我得到的错误是第23行,这也是正确的,因为它是放置错误命令的绝对行.

~$ bash foo.sh 
<---
ERROR: line 23 - command exited with status: 127
--->
foo
Run Code Online (Sandbox Code Playgroud)

所以我的问题是:为什么第15行呢?该行号来自哪里?第15行是陷阱功能的最后一行.任何人都可以用简单的英语解释为什么陷阱返回它所调用的函数的最后一行作为第21行产生错误的行

提前致谢!

编辑

以防万一有人对调试功能感兴趣.这是生产版本:

# Copyright (c): Hilario J. Montoliu <hmontoliu@gmail.com>
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
# the full text of the license.

set -o errtrace
trap 'traperror $? $LINENO $BASH_LINENO "$BASH_COMMAND" $(printf "::%s" ${FUNCNAME[@]})'  ERR

traperror () {
    local err=$1 # error status
    local line=$2 # LINENO
    local linecallfunc=$3 
    local command="$4"
    local funcstack="$5"
    echo "<---"
    echo "ERROR: line $line - command '$command' exited with status: $err" 
    if [ "$funcstack" != "::" ]; then
        echo -n "   ... Error at ${funcstack} "
        if [ "$linecallfunc" != "" ]; then
            echo -n "called at line $linecallfunc"
        fi
        else
            echo -n "   ... internal debug info from function ${FUNCNAME} (line $linecallfunc)"
    fi
    echo
    echo "--->" 
    }

somefunction () {
    asdfasdf param1
    }

somefunction

echo foo
Run Code Online (Sandbox Code Playgroud)

这适用于:

~$ bash foo.sh 2> /dev/null 
<---
ERROR: line 26 - command 'asdfasdf param1' exited with status: 127
   ... Error at ::somefunction::main called at line 29
--->
<---
ERROR: line 22 - command 'asdfasdf param1' exited with status: 127
   ... internal debug info from function traperror (line 0)
--->
foo
Run Code Online (Sandbox Code Playgroud)

jw0*_*013 6

一些相关的事实/背景信息:

  • ERR除非errtrace已设置,否则陷阱打开不会被shell函数继承,即使它们获得了其余的环境.

  • 函数的退出状态是其最后一个命令的退出状态.

我猜测发生了什么:

在两个陷阱都处于活动状态的情况下

  • 不存在的命令会触发ERR函数中的陷阱.LINENO是不存在的命令.
  • 陷阱完成执行.由于不存在的命令是最后一个命令,因此该函数的返回状态为非零,因此ERR触发了shell中的陷阱. LINENO仍然设置为最后一行,traperror因为它是最后一行,仍然是当前行,因为还没有新行执行.

在只有shell陷阱处于活动状态的情况下(函数中的一个被注释掉)

  • 不存在的命令是函数中的最后一个命令,因此使函数返回非零值,从而导致shell的ERR陷阱触发.出于同样的原因,这LINENO是函数的最后一行,因为它是最后一行,仍然是当前行.