神秘的LINENO在bash陷阱ERR中

时间:2011-08-03 15:22:34

标签: 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

输出为(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

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

然而如果我取消注释第一个陷阱(主要的陷阱),我会得到此输出:

~$ 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

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

~$ bash foo.sh 
<---
ERROR: line 23 - command exited with status: 127
--->
foo

所以我的问题是:为什么第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

哪个将起作用:

~$ 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

2 个答案:

答案 0 :(得分:6)

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

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

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

我猜测发生了什么:

如果两个陷阱都处于活动状态,

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

如果只有shell陷阱处于活动状态(函数中的那个被注释掉)

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

答案 1 :(得分:2)

要确保在traperror函数的第一个版本中ERR信号处理程序不会执行两次,您可以忽略或重置ERR信号处理程序为其默认操作对于你的程序的其余部分 - 在ERR信号处理程序本身的定义内。这也应该始终为自定义EXIT信号处理程序完成。

trap "" EXIT ERR  # ignore
trap - EXIT ERR   # reset

# for the first version of your traperror function
- trap 'traperror $LINENO ${FUNCNAME}' ERR
- trap 'traperror $LINENO ${FUNCNAME} $BASH_LINENO' ERR
+ trap 'traperror $LINENO ${FUNCNAME}; trap - ERR' ERR
+ trap 'traperror $LINENO ${FUNCNAME} $BASH_LINENO; trap - ERR' ERR