RSS
 

Bash recipe: Script call-stack

25 Mar
Bash logoHere’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
}

 

 

Tags: , , , ,