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
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,
ERR
trap in the function. LINENO
is that of the nonexistent command.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)
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.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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With