This code is an adaptation of the following post http://www.runscripts.com/support/guides/scripting/bash/debugging-bash/stack-trace.
If you want to learn more about how this works, then you would do well to read about shopt -s extdebug
, caller
, BASH_ARGC
and BASH_ARGV
in the bash documentation; https://www.gnu.org/software/bash/manual/bashref.html.
With shopt -s extdebug
set on:
nicolaw@sansa:~$ ./test.sh cmd_ln_arg1 "cmd line arg 2" 3 four
hello world
inside woop_function() inside sourced file
subshell 1
subshell 2
test2.sh: line 14: RandomCommandThatDoesNotExist: command not found
Unexpected error executing ( RandomCommandThatDoesNotExist ) at test2.sh line 6
test2.sh:6 woop_function:: __stacktrace__
test2.sh:22 source:: woop_function my arguments 'are awesome'
./test.sh:52 my_function3:: source test2.sh
./test.sh:57 my_function2:: my_function3 'hello world' b2
./test.sh:61 my_function1:: my_function2 b1 b2 f1 f2 b3
./test.sh:64 main:: my_function1 f1 f2
/bin/bash -huBE ./test.sh cmd_ln_arg1 'cmd line arg 2' 3 four
Unexpected error executing false at test2.sh line 19
test2.sh:19 woop_function:: __stacktrace__
test2.sh:22 source:: woop_function my arguments 'are awesome'
./test.sh:52 my_function3:: source test2.sh
./test.sh:57 my_function2:: my_function3 'hello world' b2
./test.sh:61 my_function1:: my_function2 b1 b2 f1 f2 b3
./test.sh:64 main:: my_function1 f1 f2
/bin/bash -huBE ./test.sh cmd_ln_arg1 'cmd line arg 2' 3 four
With shopt -s extdebug
set off (the pauper's stack trace):
nicolaw@sansa:~$ ./test.sh cmd_ln_arg1 "cmd line arg 2" 3 four
hello world
inside woop_function() inside sourced file
subshell 1
subshell 2
test2.sh: line 14: RandomCommandThatDoesNotExist: command not found
Unexpected error executing ( RandomCommandThatDoesNotExist ) at test2.sh line 6
test2.sh:22 woop_function
test2.sh:52 source
./test.sh:57 my_function3
./test.sh:61 my_function2
./test.sh:64 my_function1
./test.sh:0 main
Unexpected error executing false at test2.sh line 19
test2.sh:22 woop_function
test2.sh:52 source
./test.sh:57 my_function3
./test.sh:61 my_function2
./test.sh:64 my_function1
./test.sh:0 main
#!/bin/bash
# file: test.sh
# vim:ft=bash:
shopt -s extdebug
set -uE -o pipefail
__stacktrace__() {
declare -i frame=0 argv_offset=0
if shopt -q extdebug ; then
while : ; do
declare -a caller_info=( $(caller $frame) ) argv=('')
declare -i argc=0 frame_argc=0
[[ $frame -lt ${#BASH_ARGC[@]} ]] && frame_argc=${BASH_ARGC[frame]}
for ((frame_argc--,argc=0; frame_argc >= 0; argc++,frame_argc--)); do
argv[argc]=${BASH_ARGV[argv_offset + frame_argc]}
case "${argv[argc]}" in
*[[:space:]]*) argv[argc]="'${argv[argc]}'" ;;
esac
done
if [[ ${#caller_info[@]} -gt 0 ]] ; then
printf "%$(( frame * 2 ))s%s:%s %s:: %s %s\n" \
"" "${caller_info[2]}" "${caller_info[0]}" "${caller_info[1]}" \
"${FUNCNAME[frame]}" "${argv[*]}"
else
printf "%$(( frame * 2 ))s%s %s %s %s\n" \
"" "$BASH" "-$-" "$0" "${argv[*]}"
break
fi
argv_offset=$((argv_offset + ${BASH_ARGC[frame]}))
frame=$((frame+1))
done
else
# Pauper's stack trace without extdebug's provision of BASH_ARG*
for fn in "${FUNCNAME[@]}"; do
(($frame)) && printf "%$(( frame * 2 ))s%s:%s %s %s\n" \
"" "${BASH_SOURCE[$frame]}" "${BASH_LINENO[$frame]}" "$fn"
frame=$((frame+1))
done
fi
}
trap '>&2 echo "Unexpected error executing $BASH_COMMAND at ${BASH_SOURCE[0]} line $LINENO"; __stacktrace__ >&2; exit' ERR
exit() {
trap - ERR
command exit "$@"
}
my_function3() {
source test2.sh
false
}
my_function2() {
my_function3 "hello world" "$2"
}
my_function1() {
my_function2 b1 b2 "$@" b3
}
my_function1 f1 f2
# file: test2.sh
# vim:ft=bash:
echo "hello world"
function woop_function() {
echo "inside woop_function() inside sourced file"
(
echo "subshell 1"
(
echo "subshell 2"
(
RandomCommandThatDoesNotExist
)
)
)
false
}
woop_function my arguments "are awesome"