Getting decent error reports in Bash when you're using 'set -e'

  • You can just do

      trap 'caller 1' ERR
    
    should do the same thing. Also you should set "errtrace" (-E) and possibly "nounset" (-u) and "pipefail".

  • FWIW, I've grown the following which handles a few more cases. For some reason I wasn't aware of `caller` ...

        set -e
    
        is-oil()
        {
            test -n "$OIL_VERSION"
        }
    
        set -E || is-oil
    
        trap 'echo "$BASH_SOURCE:$LINENO: error: failure during early startup! Details unavailable."' ERR
    
        magic_exitvalue=$(($(kill -l CONT)+128))
    
        backtrace()
        {
            {
                local status=$?
                if [ "$status" -eq "$magic_exitvalue" ]
                then
                    echo '(omit backtrace)'
                    exit "$magic_exitvalue"
                fi
                local max file line func argc argvi i j
                echo
                echo 'Panic! Something failed unexpectedly.' "(status $status)"
                echo 'While executing' "$BASH_COMMAND"
                echo
                echo Backtrace:
                echo
                max=${#BASH_LINENO[@]}
                let max-- # The top-most frame is "special".
                argvi=${BASH_ARGC[0]}
                for ((i=1;i<max;++i))
                do
                    file=${BASH_SOURCE[i]}
                    line=${BASH_LINENO[i-1]}
                    func=${FUNCNAME[i]}
                    argc=${BASH_ARGC[i]}
                    printf '%s:%d: ... in %q' "$file" "$line" "$func"
                    # BASH_ARGV: ... bar foo ...
                    # argvi          ^
                    # argvi+argc             ^
                    for ((j=argc-1; j>=0; --j))
                    do
                        printf ' %q' ${BASH_ARGV[argvi+j]}
                    done
                    let argvi+=argc || true
                    printf '\n'
                done
    
                if true
                then
                    file=${BASH_SOURCE[i]}
                    line=${BASH_LINENO[i-1]}
                    printf '%s:%d: ... at top level\n' "$file" "$line"
                fi
            } >&2
            exit "$magic_exitvalue"
            unreachable
        }
        shopt -s extdebug
        trap 'backtrace' ERR

  • Setting PS4 gets decent error reports with `set -x` (and `set -x -v`; `help set`).

    Here's an excerpt that shows how to set PS4 from a main() in a .env shell script for configuring devcontainer userspace:

      for arg in "${@}"; do
      case "$arg" in
      --debug)
          export __VERBOSE=1 ;
          #export PS4='+${LINENO}: ' ;
          #export PS4='+ #${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]:+${FUNCNAME[0]}()}:$(date +%T)\n+ ' ;
          #export PS4='+ ${LINENO} ${FUNCNAME[0]:+${FUNCNAME[0]}()}: ' ;
          #export PS4='+ $(printf "%-4s" ${LINENO}) | '
          export PS4='+ $(printf "%-4s %-24s " ${LINENO} ${FUNCNAME[0]:+${FUNCNAME[0]}} )| '
          #export PS4='+ $(printf "%-4s %-${SHLVL}s %-24s" ${LINENO} "     " ${FUNCNAME[0]:+${FUNCNAME[0]}} )| '
          ;;
      --debug-color|--debug-colors)
          export __VERBOSE=1 ;
          # red=31
          export ANSI_FG_BLACK='\e[30m'
          #export MID_GRAY_256='\e[38;5;244m'    # Example: a medium gray
          export _CRESET='\e[0m'
          export _COLOR="${ANSI_FG_BLACK}"
          printf "${_COLOR}DEBUG: --debug-color: This text is ANSI gray${_CRESET}\n" >&2
          export PS4='+ $(printf "${_COLOR}%-4s %-24s%s |${_CRESET} " ${LINENO} "${FUNCNAME[0]:+${FUNCNAME[0]}}" )'
          ;;
       esac
       done
    
    This, too:

      function error_handler {
        echo "Error occurred on line $(caller)" >&2
        awk 'NR>L-4 && NR<L+4 { printf "%-5d%3s%s\n",NR,(NR==L?">>>":""),$0 }' L=$1 $0 >&2
      }
      if (echo "${SHELL}" | grep "bash"); then
        trap 'error_handler $LINENO' ERR
      fi

  • But trap doesn't "stack" (like e.g. defer in Go) so if you do this it's not available for other purposes like cleanup

  • beware of using this. any operations that returns non-zero exit status will cause immediately exit

  • Why don’t all shells just do this?