6 comments

[ 3.8 ms ] story [ 24.5 ms ] thread
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
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
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
beware of using this. any operations that returns non-zero exit status will cause immediately exit