Statements: Zero cost debugging

I like to sprinkle my code with tracing functions for runtime debugging, which might look something like this:

#! /usr/bin/env bash

log_debug()
{
   if [ -v VERBOSE ]
   then
      printf "log: %s\n" "$*" >&2
   fi
}

debug_info()
{
   printf "hello\n"
}

log_debug $(debug_info)

printf "done\n"

So normally it would print only “done”, but if you set the environment variable VERBOSE it prints

log: hello
done

The function call for log_debug is obviously always performed. But since this is a shell script, debug_info is also executed. That is regardless of VERBOSE being set or not. This slows my scripts down somewhat, even when I am not requesting debugging output.

There is a way to fix this without restructuring the code and it uses a macro again:

#! /usr/bin/env bash

log_debug()
{
   printf "log: %s\n" "$*" >&2
}

# remove old aliases, which might do unexpected stuff
# enable aliases in script
unalias -a
shopt -s expand_aliases

# Turn a previously defined log_debug function into a harmless ':' builtin
# function which does nothing and comment out the rest of the arguments.
# Without the ':' placing a log_debug in an otherwise empty function would
# fail.

if [ -v VERBOSE ]
then
   alias log_debug=': #'
fi


debug_info()
{
   printf "hello\n"
}

log_debug `debug_info`

printf "done\n"

This can be tricky to pull off though as the alias expansion happens as the script is read by bash and not when the line gets executed.

#! /usr/bin/env bash

log_debug()
{
   printf "log: %s\n" "$*" >&2
}

# Remove old aliases, which might do unexpected stuff.
unalias -a

# Enable aliases in script.
shopt -s expand_aliases

debug_info()
{
   log_debug "hello"
}

# too late, the log_debug in debug_info() has already been expanded

alias log_debug=': #'


debug_info

Another pitfall are continuations. The comment in the expanded alias swallows the continuation character \ and the second line is left intact.

#! /usr/bin/env bash

alias log_debug=': #'

call_me()
{
   log_debug "first line \
second line"
}

call_me

The same caveat applies to other multi-line statements. So this technique takes some care to execute properly.