#!/usr/bin/env bash # CONSTANTS DEBUG=true TMP_ERROR=$(mktemp -t "$(basename $0).XXXXXXXX" --tmpdir=/run/user/$(id -u)) SCRIPT=$1 # FUNCTIONS function on_return { if [[ "${BASH_COMMAND}" == return* ]]; then code=$(echo "${BASH_COMMAND}" | cut -d' ' -f2) # regex does not work here unfortunately! thus use of basic cut if [[ -n "$code" ]] && [[ $code -ne 0 ]]; then trap - DEBUG >&2 echo "${BASH_SOURCE[2]}: line ${BASH_LINENO[0]}: return $code from ${FUNCNAME[1]}" fi fi } # overridden function which permits stacktracing of original `return` statement function exit { code=${1:-0} >&2 echo "${BASH_SOURCE[2]}:${BASH_LINENO[0]} exit $@" # >&2 echo "${BASH_SOURCE[2]}: line ${BASH_LINENO[0]}: exit $@" builtin exit $code } function on_exit { code=${1:-0} >&4 dump_stderr stack_lines=("${BASH_LINENO[@]}") stack_functions=("${FUNCNAME[@]}") stack_sources=("${BASH_SOURCE[@]}") unset stack_lines[-1] unset stack_lines[-1] unset stack_functions[0] unset stack_functions[-1] stack_functions[-1]='
' unset stack_sources[0] unset stack_sources[-1] if [[ "${stack_functions[1]}" == exit && "${stack_sources[1]}" == "${BASH_SOURCE[0]}" ]]; then # the exit case scenario unset stack_functions[1] unset stack_sources[1] unset stack_lines[0] fi stack_lines=("${stack_lines[@]}") stack_functions=("${stack_functions[@]}") stack_sources=("${stack_sources[@]}") if [[ $code -gt 0 ]]; then $DEBUG && >/dev/tty echo '---stacktrace---' last_error=$(tail -n1 $TMP_ERROR) last_source=${stack_sources[0]} last_source=${last_source//./"\."} # replace . by \. regex1="^$last_source: line ([0-9]+): (.*)\$" regex2="^$last_source:([0-9]+) (.*)\$" if [[ "$last_error" =~ $regex1 || "$last_error" =~ $regex2 ]]; then line=${BASH_REMATCH[1]} message=${BASH_REMATCH[2]} regex=': unbound variable$' [[ $code == 1 ]] && [[ "$message" =~ $regex ]] && stack_lines[0]=$line regex="^return [0-9]* from (.*)\$" if [[ "$message" =~ $regex ]]; then funcname="${BASH_REMATCH[1]}" # >&4 echo "RETURN DETECTED line ${stack_sources[0]} $line $funcname" stack_sources=("${stack_sources[0]}" "${stack_sources[@]}") stack_lines=("$line" "${stack_lines[@]}") stack_functions=("$funcname" "${stack_functions[@]}") fi >&4 echo -e "ERROR $code: [$message]" else >&4 echo "ERROR $code: " fi for i in "${!stack_functions[@]}"; do >&4 printf "\t%30s\t%s\n" "${stack_sources[$i]}:${stack_lines[$i]}" "${stack_functions[$i]}" done fi cleanup builtin exit $code } function dump_stderr { if [[ -s $TMP_ERROR ]]; then $DEBUG && >/dev/tty echo '---stderr---' cat $TMP_ERROR fi } function success { >&2 dump_stderr cleanup } function cleanup { trap - ERR TERM INT EXIT exec 3>&- 4>&- rm $TMP_ERROR } # MAIN set -TEue -o pipefail trap 'on_exit $?' ERR TERM INT EXIT trap 'on_return' DEBUG # magic FD exec 3>$TMP_ERROR 4>/dev/stderr $DEBUG && >/dev/tty echo '---stdout---' source $SCRIPT 2>&3 success