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?
You can just do
should do the same thing. Also you should set "errtrace" (-E) and possibly "nounset" (-u) and "pipefail".