Here’s a little recipe for printing a “call-stack” for Bash scripts. A call-stack is normally the list of nested function calls. This recipe will utilize the BASH_SOURCE, BASH_LINENO, and FUNCNAME environment variables, set by Bash during script executions, to display a call-stack of one script to another, excluding function calls; this will show only the invocations of one script to another.showStack() {
local i
for (( i=1; i < ${#BASH_SOURCE[@]}; i++ )); do
[ "${FUNCNAME[$i]}" = source ] && echo " ${BASH_SOURCE[$i]}:${BASH_LINENO[(($i - 1))]}"
done
}
Each of my startup dot-files echo their name when they start, so their call to showStack might look like:
.bash_profile... .profile... /home/bill/.profile:31 /home/bill/.bash_profile:28 .bashrc... /home/bill/.bashrc:30 /home/bill/.profile:243 /home/bill/.bash_profile:28
The explicit output shows that .bash_profile is started, followed by .profile. In .profile, is a call to showStack which displays the script calls: .bash_profile at line 28 invokes .profile, which, at line 31, calls showStack. Later, .bashrc is invoked and it calls showStack at line 30; it was invoked from .profile at its line 243; which was invoked by .bash_profile‘s line 28.
The three Bash variables, BASH_SOURCE, BASH_LINENO, and FUNCNAME, are arrays whose corresponding elements describe the script files, their line numbers, and function names which make up the call-stack. If the invocation is not contained within a function, then the corresponding FUNCNAME element is set to source.
caller Builtin
I was aware of the Bash variables and wanted the exercise of figuring them out; but if I’d queried StackOverflow on the topic, would have revealed other’s similar solutions; notably, one using Bash’s builtin command, caller:
showStack() {
while caller $i; do
((i++))
done
}
Which, for the same scripts, would output (here, I did not filter for the source entries and includes the function calls):
.bash_profile... .profile... 31 source /home/bill/.profile 21 _sourceFiles /home/bill/.bash_profile 25 _sourceDotFiles /home/bill/.bash_profile 30 source /home/bill/.bash_profile .bashrc... 30 source /home/bill/.bashrc 243 source /home/bill/.profile 21 _sourceFiles /home/bill/.bash_profile 25 _sourceDotFiles /home/bill/.bash_profile 30 source /home/bill/.bash_profile
These are well illustrated by @kyb in his utils.bash script:
function stacktrace {
local i=1
while caller $i | read line func file; do
echodbg "[$i] $file:$line $func()"
((i++))
done
}
function stacktrace2 {
local i=${1:-1} size=${#BASH_SOURCE[@]}
((i<size)) && echodbg "STACKTRACE"
for ((; i < size-1; i++)) ;do ## -1 to exclude main()
((frame=${#BASH_SOURCE[@]}-i-2 ))
echodbg "[$frame] ${BASH_SOURCE[$i]:-}:${BASH_LINENO[$i]} ${FUNCNAME[$i+1]}()"
done
}
