I'm trying to add some error checking to my bashrc libs. They're modular and loaded in a for loop:
for rcfile in $BASHRCD/*.bash; do
source "$rcfile"
done
I use a flag to enable debugging (if I'm trying to troubleshoot something). I've rewritten the following functions many different ways, but haven't found anyway to provide the name of the sourced script where the error occurred. I've tried disabling pipefail, I've tried resolving the value of $rcfile, I've tried to capture and pass in the arguments to the trap explicitly, etc. I intentionally don't have errexit set, because I don't want my shells to exit when I have an error - I just want meaningful output. :) This is what I currently have:
if [[ ${DEBUG:-0} -eq 1 ]]; then
shopt -s extdebug
set -o errtrace
set -o nounset
set -o pipefail
_err_handler() {
local _ec="$?"
local _cmd="${BASH_COMMAND:-unknown}"
traceback 1
_showed_traceback=t
echo "The command ${_cmd} exited with exit code ${_ec}." 1>&2
}
traceback () {
# Hide the traceback() call.
local -i start=$(( ${1:-0} + 1 ))
local -i end=${#BASH_SOURCE[@]}
local -i i=0
local -i j=0
echo "Traceback (last called is first):" 1>&2
for ((i=${start}; i < ${end}; i++)); do
j=$(( $i - 1 ))
local function="${FUNCNAME[$i]}"
local file="${BASH_SOURCE[$i]}"
local line="${BASH_LINENO[$j]}"
echo " ${function}() in ${file}:${line}" 1>&2
done
}
fi
trap _err_handler ERR
When I attempt to load a file with an error in it, it generates output like this:
/Users/nfarrar/.bashrc
Traceback (last called is first):
source() in /Users/nfarrar/.bashrc:43
source() in /Users/nfarrar/.bash_profile:94
The command source "$rcfile" exited with exit code 1.
bash: SOMEFILE: unbound variable
The bashrc line referenced is the line in the for loop (shown above) the sources $rcfile. The traceback references my .bash_profile as well because the .bashrc is sourced from it. This is helpful, but what I'm really looking for is the name of the file and line number in the sourced file where the error occurred.
EDIT
This snippet shows output from the BASH_SOURCE
array and walks the caller stack.
_err_trap() {
echo "Number of items in BASH_SOURCE: ${#BASH_SOURCE[@]}"
echo "${BASH_SOURCE[@]}"
i=0;
while caller $i; do
((i++))
done
}
trap _err_trap ERR
It results in the following output, which shows that the script that failed to be sourced is not in the BASH_SOURCE or the caller frames:
Number of items in BASH_SOURCE: 3
/Users/nfarrar/.bash/lib/debug.bash /Users/nfarrar/.bashrc /Users/nfarrar/.bash_profile
43 source /Users/nfarrar/.bashrc
94 source /Users/nfarrar/.bash_profile
The three items that are listed are:
- .bash/lib/debug.bash (this is sourced and contains _err_handler & traceback)
- .bashrc (sourced by .bash_profile and runs the loop that sources all the files in a directory)
- .bash_profile, which sources .bashrc