1

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:

  1. .bash/lib/debug.bash (this is sourced and contains _err_handler & traceback)
  2. .bashrc (sourced by .bash_profile and runs the loop that sources all the files in a directory)
  3. .bash_profile, which sources .bashrc
Jolta
  • 2,620
  • 1
  • 29
  • 42
nfarrar
  • 2,291
  • 4
  • 25
  • 33
  • What do you get if you use `traceback 0` instead of `traceback 1`? – Etan Reisner Feb 03 '15 at 20:17
  • It includes the topmost entry from BASH_SOURCE, which is the error handler function itself: _err_handler() in /Users/nfarrar/.bash/lib/debug.bash – nfarrar Feb 03 '15 at 20:27
  • And what about `traceback -1`? – Etan Reisner Feb 03 '15 at 20:31
  • So this is terrible, but maybe something like this? `local _cmd_text=$(eval echo "${_cmd}")` – Mr. Llama Feb 03 '15 at 20:42
  • 1
    @EtanReisner It keeps walking back up the caller stack, but the information about the script where the error occurred is not in it at all. If you remove everything from _err_handler and have it just print out ${BASH_SOURCE[@]}, the only files it sees are .bash_profile and .bashrc - the script we attempted to source is not present. – nfarrar Feb 03 '15 at 21:11
  • How can there be nothing else in `BASH_SOURCE` and yet your skipping two entries not break your output entirely? If you start at index two (which is what happens when you call `traceback 1`) and `BASH_SOURCE` only has two entries then I wouldn't expect your traceback to print anything at all. – Etan Reisner Feb 03 '15 at 21:15
  • @EtanReisner I added another snippet that demonstrates it. The source of confusion may be that I didn't specify that the functions in question are sourced from another file. – nfarrar Feb 03 '15 at 21:39
  • @Mr.Llama that works, but it scares me. :) Thanks! – nfarrar Feb 03 '15 at 21:49
  • It should scare you. It isn't at all safe. – Etan Reisner Feb 03 '15 at 23:35

0 Answers0