1

OK, so, I have a debugging setup a little like this:

DVAR=(); 
function DBG() {
    if [[ $1 == -s ]]; then shift; stackTrace; fi;
    if [[ ! -z ${DVAR[@]} ]]; then
        for _v in ${!DVAR[@]}; do
            echo "${DVAR[$_v]}" >> $LOG;
            unset DVAR[$_v];
        done;
    fi
    local tmp=("$*")
    [[ ! -z $tmp ]]&&echo "$tmp" >> $LOG||continue;
}

every once in a while I call it either directly or, and I'd like to take this approach more, by repeatedly adding things to the array and calling it later. SPECIFICALLY, I'd like to be using this:

DVAR+="${0##*/}${FUNCNAME[0]}:$LINENO === assorted local variables and stuff here ====="

That first part is quite a mouthful and really clutters up my code. I'd REALLY rather be able to say something like:

DBG === assorted local variables and stuff here=====

I've tried messing around with alias and even eval, all to no. . evail. ahem

Thoughts anyone?
BenG
  • 31
  • 5

1 Answers1

1

Like @ufopilot said, you should add your line as a new entry in the array with DVAR+=("...."), not overwriting it as a long string by concatenating it to the first element. Here's an explaining it:

Concatenating:

$  DVAR=()
$  DVAR+="foo"
$  DVAR+="bar"
$  declare -p DVAR
  declare -a DVAR=([0]="foobar")
$  echo ${DVAR[@]}
  foobar

Appending new entry:

$  DVAR=()
$  DVAR+=(foo)
$  DVAR+=(bar)
$  declare -p DVAR
  declare -a DVAR=([0]="foo" [1]="bar")
$  echo "${DVAR[@]}"
  foo bar

Here's an example of a function I put together for debugging purposes some time. The function is get_stack which will get the function name from whoever called it and the file name of where that calling function exists in, along with the trace so you can see the call history.

File test.sh:

#!/usr/bin/env bash
foo() {
    get_stack
    echo -e "$stack_trace" # Note the quotation to get indentation
}

get_stack () {
   stack_trace=""
   local i stack_size=${#FUNCNAME[@]}
   local indent="  "
   local newline="" # newline only after first line
   # Offset to skip get_stack function
   for (( i=0; i<$stack_size; i++ )); do
      local func="${FUNCNAME[$i]}"
      [ x$func = x ] && func=MAIN
      local linen="${BASH_LINENO[$(( i - 1 ))]}"
      local src="${BASH_SOURCE[$i]}"
      [ x"$src" = x ] && src=non_file_source
      stack_trace+="${newline}${indent}-> $src [$func]: $linen"
      newline="\n"
      indent="$indent  "
   done
}

echo "stack from test.sh"
foo

File test2.sh:

#!/usr/bin/env bash
source test.sh
echo "stack from test2.sh"
foo

Output:

stack from test.sh
  -> test.sh [foo]: 4
    -> test.sh [source]: 28
      -> ./test2.sh [main]: 3
stack from test2.sh
  -> test.sh [foo]: 4
    -> ./test2.sh [main]: 6

In my script I have a down-right arrow ascii character that looks better than "->" but can't figure out how to get stackoverflow to display it proberly. The ascii is \u21b3.

As you can see in the stack trace, "foo" ran upon sourcing the function, just as it's supposed to do. But now it is clear why it ran and outputed text!

I think this demonstrates well how you can the array FUNCNAME to walk backwards in the call stack. Also BASH_SORUCE is an array itself, with matching indices, however it will display what file the function call came from. Modifying the "get_stack" function to inspect these arrays:

foo() {
  get_stack
  echo -e "$stack_trace"
  echo "${BASH_SOURCE[@]}"
  echo "${FUNCNAME[@]}"
}

yields:

stack from test.sh
  -> test.sh [foo]: 4
    -> test.sh [source]: 30
      -> ./test2.sh [main]: 3
test.sh test.sh ./test2.sh
foo source main
stack from test2.sh
  -> test.sh [foo]: 4
    -> ./test2.sh [main]: 6
test.sh ./test2.sh
foo main

As you can see, the first set of outputs belong to test.sh, which came from the sourcing. This you can see in BASH_SOURCE: "foo source main". The "main" is the main scope, that is the stuff that runs but is not in a function but the main body of the file. In this case, test.sh had the "foo" call which upon sourcing this file will run.

I hope you see how the indices belong to the same call, but yield different info.

Now for your part, you wanted to add only the string and not the whole same-y info again and again. Since I'm not sure you want the stack trace or not I just added the message string to the first calling function instance. I also fixed up some old code here to make it a little better.

New improved function with message:

get_stack () {
   local msg="$@"
   stack_trace=""
   local i stack_size=${#FUNCNAME[@]}
   local indent="  "
   # Offset to skip get_stack function
   for (( i=1; i<$stack_size; i++ )); do
      local func="${FUNCNAME[$i]}"
      [ x$func = x ] && func=MAIN
      local linen="${BASH_LINENO[$(( i - 1 ))]}"
      local src="${BASH_SOURCE[$i]}"
      [ x"$src" = x ] && src=non_file_source
      stack_trace+="${newline:=\n}${indent}-> $src [$func:$linen]${msg:+": $msg"}"
      msg=""
      indent="$indent  "
   done
}

Output:

 $  ./test2.sh
stack from test.sh

  -> test.sh [foo:4]: my message
    -> test.sh [source:28]
      -> ./test2.sh [main:3]
stack from test2.sh

  -> test.sh [foo:4]: my message
    -> ./test2.sh [main:6]

Note that ${var:=X} will initialize "var" with "X" if var was uninitialized or set to empty string, ${var:+X} will replace "var" with "X" if var is initialized/set to something that is not the empty string, and finally as bonus ${var:-X} will replace "var" with "X" if var is uninitialized/set empty string. This is variable substitution in bash which is quite handy!

There are some pieces you can cut and past into your function, or if you need a stack based log you can use my function as a base.

Hope this helps you in your endeavors!

Lurvas777
  • 35
  • 6