You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

142 lines
3.7 KiB

#!/usr/bin/env bash
# CONSTANTS
DEBUG=${BASH_BACK_DEBUG:-false}
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}
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]='<main>'
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[@]}")
stack_summary=()
if [[ $code -gt 0 ]]; then
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
return_code="${BASH_REMATCH[1]}"
funcname="${BASH_REMATCH[2]}"
# >&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[@]}")
stack_summary+=("ERROR $code: [return $return_code]")
else
stack_summary+=("ERROR $code: [$message]")
fi
else
regex="^ .*:[0-9]* .*\$"
[[ "$last_error" =~ $regex ]] || stack_summary+=("ERROR $code: <undefined>")
fi
for i in "${!stack_functions[@]}"; do
stack_detail=$(printf "\t%30s\t%s\n" "${stack_sources[$i]}:${stack_lines[$i]}" "${stack_functions[$i]}")
stack_summary+=("$stack_detail")
done
>&4 dump_stderr true
dump_summary
else
>&4 dump_stderr false
fi
cleanup
builtin exit $code
}
function dump_summary {
$DEBUG && >/dev/tty echo '---stacktrace---'
for i in "${stack_summary[@]}"; do
>&4 echo -e "$i"
done
}
function dump_stderr {
last_trim=${1:-false}
mapfile -t tmp_error <$TMP_ERROR
if $last_trim; then
unset tmp_error[-1]
tmp_error=("${tmp_error[@]}")
fi
if [[ "${#tmp_error[@]}" -gt 0 ]]; then
$DEBUG && >/dev/tty echo '---stderr---'
for i in "${tmp_error[@]}"; do
echo -e "$i"
done
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