BashXTrace

6th April 2017 at 7:28pm
Bash Debugging TechnicalNotes

If you have an horrific piece of shit legacy Bash script application, (let me guess, it's your build system?) like I do, then you may want to trace it to see what code paths are or are not ever executed.

If you're really unlucky like me, and there's lots of weird and wonderful redirection of STDERR all over the place, then simply adding set -xv at the top of your script might do more harm than good.

In this situation, you can use BASH_XTRACEFD to tell Bash to send this output to a specific file handle instead of STDERR.

Here I use both BASH_XTRACEFD and PS4 to create a useful log for every run of this script, which I can then analyse later on. From there I can work out which lines of code are not executed, and then take a large axe to great swathes of the code.

DIE LEGACY PoS BUILD SYSTEM WITH ALL YOUR CRAPPY GLOBAL VARIABLES AND BROKEN LOGIC. </triggered> ahem,.. sorry. I'm fine now, honest. :-)

#!/bin/bash
# vim:ts=2:sw=2:tw=79

exec 19> ~/"${BASH_SOURCE[0]##*/}.xtrace.log"
{
  printf "Command line: \"%q\"" "$0"
  printf " \"%q\"" "$@"
  printf "\n"
  printf "\$-: %s\n" "$-"
  printf "BASHOPTS: %s\n" "$BASHOPTS"
  printf "SHELLOPTS: %s\n" "$SHELLOPTS"
  printf "Start time: %(%Y%m%d %H%M%S %z)T\n" -2
  printf "Git revision: %s\n" \
    "$(git -C "${BASH_SOURCE[0]%/*}" rev-parse --verify HEAD 2>&1)"
  printf "Hostname: %s\n" "${HOSTNAME:-$(hostname -f 2>&1)}"
  printf "\n"
  env
  printf "\n"
} >&19 2>&19
export BASH_XTRACEFD=19
export PS4='+ $$($BASHPID) +${SECONDS}s (${BASH_SOURCE[0]}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x

set -euo pipefail

unusedfunc () {
  echo "Blerg."
}

myfunc () {
  >&2 echo "Hello world!"
}

main () {
  echo stdout
  >&2 echo stderr
  myfunc 2>&1
  return 0
}

main "$@"

Example of use:

nicolaw@myhost:~$ env -i BLERG=999 ~/bin/test.sh "Hello \"world\"!" arg2 Three
stdout
stderr
Hello world!
nicolaw@myhost:~$ cat ~/test.sh.xtrace.log
Command line: "/home/nicolaw/bin/test.sh" "Hello\ \"world\"\!" "arg2" "Three"
$-: hB
BASHOPTS: cmdhist:complete_fullquote:extquote:force_fignore:hostcomplete:interactive_comments:progcomp:promptvars:sourcepath
SHELLOPTS: braceexpand:hashall:interactive-comments
Start time: 20170406 202625 +0100
Git revision: cdc10a4d0ef4c8042d04bcbc30f18698bbc90eb3
Hostname: myhost

PWD=/home/nicolaw
SHLVL=1
BLERG=999
_=/usr/bin/env

+ 26007(26007) +0s (/home/nicolaw/bin/test.sh:24): main(): set -euo pipefail
+ 26007(26007) +0s (/home/nicolaw/bin/test.sh:41): main(): main 'Hello "world"!' arg2 Three
+ 26007(26007) +0s (/home/nicolaw/bin/test.sh:35): main(): echo stdout
+ 26007(26007) +0s (/home/nicolaw/bin/test.sh:36): main(): echo stderr
+ 26007(26007) +0s (/home/nicolaw/bin/test.sh:37): main(): myfunc
+ 26007(26007) +0s (/home/nicolaw/bin/test.sh:31): myfunc(): echo 'Hello world!'
+ 26007(26007) +0s (/home/nicolaw/bin/test.sh:38): main(): return 0
nicolaw@myhost:~$