Skip to content

Instantly share code, notes, and snippets.

@dotysan
Last active July 6, 2025 20:19
Show Gist options
  • Save dotysan/4422732a09535bb062e145d8bd289b4c to your computer and use it in GitHub Desktop.
Save dotysan/4422732a09535bb062e145d8bd289b4c to your computer and use it in GitHub Desktop.
Template for writing a bash script with fancy debugging.
#! /usr/bin/env bash
#
# Template for writing a bash script with fancy debugging.
#
# Not yet tested at depth. Only basic handling of:
# - running from stdin i.e. |bash
# - sub-functions
# - sourcing of other files
#
# Pythonic asssumptions:
# - you have a main() worker
# - it can be sourced without running main()
# - other functions can be run by passing as args
#
DEBUG=yep
main() {
gethere # sets $HERE
echo 'Hello world!'
source "$HERE/sourceme.sh"
donky
foo
}
# ----------------------------------------------------------------------
gethere() {
local here="${BASH_SOURCE[0]%/*}"
[[ $here != "${BASH_SOURCE[0]}" ]] || here=.
HERE=$(cd -P -- "$here" && pwd)
}
foo() {
source "$HERE/sourceme.sh"
bar
}
bar() {
source "$HERE/sourceme.sh"
}
# ======================================================================
# shellcheck disable=SC2128
if [ -z "$BASH_VERSINFO" ] || [[ ${BASH_VERSINFO[0]} -lt 4 ]]
then
echo "ERROR: Only tested on Bourne-Again SHell v4/v5."
exit 1
fi >&2
# poor man's __main__
return 2>/dev/null ||:
if [ "$(type -t "$1")" = "function" ]
then
func="$1"
shift 1
"$func" "$@"
exit 0
fi
set -o errtrace
set -o errexit
set -o nounset
set -o pipefail
if [[ "${DEBUG:-}" ]]
then
PS4func() {
local normal="\033[0m"
#local red="\033[0;31m"
#local green="\033[0;32m"
local yellow="\033[0;33m"
#local blue="\033[0;34m"
#local magenta="\033[0;35m"
local cyan="\033[0;36m"
local i caller_idx=-1
# walk the call stack to see if we are sourced and from where
if [[ ${#BASH_SOURCE[@]} -gt 2 ]]
then
for (( i=2; i<${#BASH_SOURCE[@]}; i++ ))
do
if [[ ${BASH_SOURCE[1]} != "${BASH_SOURCE[i]}" ]]
then
caller_idx=$i
break
fi
done
fi
local src func=''
local caller deep
if [[ $caller_idx -ne -1 ]]
then # we're in a sourced script
if [[ ${BASH_SOURCE[0]} == main ]]
then caller='<stdin>'
else caller="${BASH_SOURCE[0]##*/}"
fi
src="$caller:${BASH_LINENO[caller_idx-1]}${cyan}source->$yellow${BASH_SOURCE[1]##*/}:${BASH_LINENO[0]}"
if [[ ${FUNCNAME[-2]} == main && ${FUNCNAME[-1]} == main ]]
then deep=$((${#FUNCNAME[@]}-5))
else deep=$((${#FUNCNAME[@]}-4))
fi
elif [[ ${BASH_SOURCE[0]} == main ]]
then # reading from standard input instead of a script file
src="<stdin>:${BASH_LINENO[0]}"
deep=$((${#FUNCNAME[@]}-1))
else # a normal script file
src="${BASH_SOURCE[0]##*/}:${BASH_LINENO[0]}"
deep=$((${#FUNCNAME[@]}-2))
fi
if (( deep >= 1 ))
then
if [[ ${FUNCNAME[1]} != source ]]
then func+="${FUNCNAME[1]}()"
fi
fi
# local lines="${BASH_LINENO[@]}"
# local sources="${BASH_SOURCE[@]##*/}"
# local funcs="${FUNCNAME[@]}"
# printf "${red}sources(%d): %s\nlines(%d): %s | funcs(%d): %s|%b" \
# "${#BASH_SOURCE[@]}" "$sources" \
# "${#BASH_LINENO[@]}" "$lines" \
# "${#FUNCNAME[@]}" "$funcs" \
# "$yellow$src$cyan$func$normal "
# run the above to debug your stacks
printf "%b" "$yellow$src$cyan$func$normal "
}
PS4='\r$(PS4func)'
set -o xtrace
fi
main "$@"
exit 0
main() {
echo 'Hello from sourced.'
donky
}
donky() {
echo 'Hee haw.'
balls
}
balls() {
echo 'Dafuq!'
}
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment