Debugging Bash
Stack Trace
Bash 3.0 introduced several variables to support the Bash debugger. Short of running the debugger we can make use of several of these to construct a stack backtrace.
Two of the variables are only made available if Bash is in extended debugging mode, the shell option extdebug:
% shopt -s extdebug
The Variables and Commands
Commands
caller
caller is a command which returns the context of the current subroutine call. With an argument you can inspect the nth frame back up the stack.
Variables
BASH_ARGC
BASH_ARGC is an array which indicates how many arguments were put on the stack by the nth subroutine call. New values are pushed onto the front.
BASH_ARGV
BASH_ARGV is an array which the arguments put on the stack by the subroutine calls. All the subroutines in a single array with the per-subroutine arguments in reverse order. New values are pushed onto the front.
This sounds as complicated as it is. You need to use BASH_ARGC to tot up how many arguments were placed on the stack before the nth subroutine call and again to find out how many arguments are for this subroutine. You then need to reverse them.
For example, a function call:
foo f1 f2
which calls:
bar b1 b2 b3
will result in:
BASH_ARGC=( 3 2 ) BASH_ARGV=( b3 b2 b1 f2 f1 )
FUNCNAME
FUNCNAME is an array of the subroutine names on the stack. It is unset if there are no subroutines on the stack.
Stack Trace
The basic algorithm is to walk back up the stack frame determining the caller at each frame. We then extract the arguments for that context and can print out a useful trace:
stacktrace () { declare frame=0 declare argv_offset=0 while caller_info=( $(caller $frame) ) ; do if shopt -q extdebug ; then declare argv=() declare argc declare frame_argc for ((frame_argc=${BASH_ARGC[frame]},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 argv_offset=$((argv_offset + ${BASH_ARGC[frame]})) echo ":: ${caller_info[2]}: Line ${caller_info[0]}: ${caller_info[1]}(): ${FUNCNAME[frame]} ${argv[*]}" fi frame=$((frame+1)) done if [[ $frame -eq 1 ]] ; then caller_info=( $(caller 0) ) echo ":: ${caller_info[2]}: Line ${caller_info[0]}: ${caller_info[1]}" fi }
Document Actions