Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mysterious LINENO in bash trap ERR

Tags:

bash

shell

I was just playing with bash to bypass this summer afternoon heat, when suddenly I've got a mysterious result for which I cannot determine it's origin.

Let me explain it bit a bit.

I'm playing with trap ERR to create some debugging functions for my bash scripts.

This is the script that runs fine:

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

The output is (stderr goes to /dev/null for clarity; the bash error is of course foo.sh: line 23: asdfas: command not found which is as you know error code 127)

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

All the line numbers are right, line 21 is where starts the function "somefunction" and line 24 is where it is called.

However if I uncomment the first trap (the one in main) I get this output:

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

In case I uncomment the first trap and comment the second one I get that the error is in line 23 which is right too because it is the absolute line where the wrong command is placed.

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

So my question is: why line 15? where does that line number come from? Line 15 is the last line in the trap function. Can anyone explain in plain English why trap returns the last line of the function it calls as the line that produced the error in line 21?

Thanks in advance!

EDIT

Just in case someone is interested in the debug function. This is the production version:

# Copyright (c): Hilario J. Montoliu <[email protected]>
# 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

Which will work as:

~$ 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
like image 319
hmontoliu Avatar asked Aug 03 '11 15:08

hmontoliu


2 Answers

Some relevant facts/background info:

  • Traps on ERR are not inherited by shell functions even though they get the rest of the environment, unless errtrace is set.

  • The exit status of a function is that of its last command.

My guess as to what is happening:

In the case where both traps are active,

  • The nonexistent command triggers the ERR trap in the function. LINENO is that of the nonexistent command.
  • The trap finishes executing. Since the nonexistent command was the last command, the return status of the function is nonzero, so the ERR trap in the shell is triggered. LINENO is still set to the last line of traperror since it was the last line to execute and is still the current line, as no new line has been executed yet.

In the case where only the shell trap is active (the one in the function is commented out)

  • The nonexistent command is the last command in the function, so causes the function to return non-zero, thus causing the shell's ERR trap to trigger. For the same reason above, LINENO is the last line of the function as it was the last line to execute and is still the current line.
like image 97
jw013 Avatar answered Oct 17 '22 21:10

jw013


To make sure in your first version of your traperror function that the ERR signal handler will not be executed twice, you can ignore or reset the ERR signal handler to its default action for the rest of your program - within the definition of the ERR signal handler itself. And this should always be done for a custom EXIT signal handler as well.

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
like image 42
chad Avatar answered Oct 17 '22 21:10

chad