: # SPDX-License-Identifier: BSD-2-Clause # NAME: # debug.sh - selectively debug scripts # # SYNOPSIS: # $_DEBUG_SH . debug.sh # DebugOn [-eo] "tag" ... # DebugOff [-eo] [rc="rc"] "tag" ... # Debugging # DebugAdd "tag" # DebugEcho ... # DebugLog ... # DebugShell "tag" ... # DebugTrace ... # Debug "tag" ... # # $DEBUG_SKIP echo skipped when Debug "tag" is true. # $DEBUG_DO echo only done when Debug "tag" is true. # # DESCRIPTION: # debug.sh provides the following functions to facilitate # flexible run-time tracing of complicated shell scripts. # # DebugOn turns tracing on if any "tag" is found in "DEBUG_SH". # It turns tracing off if "!tag" is found in "DEBUG_SH". # It also sets "DEBUG_ON" to the "tag" that caused tracing to be # enabled, or "DEBUG_OFF" if we matched "!tag". # If '-e' option given returns 1 if no "tag" matched. # If the '-o' flag is given, tracing is turned off unless there # was a matched "tag", useful for functions too noisy to tace. # # Further; when we set "DEBUG_ON" if we find # "$DEBUG_ON:debug_add:tag" in "DEBUG_SH" we will # add the new "tag" to "DEBUG_SH" so it only has effect after that # point. # # DebugOff turns tracing on if any "tag" matches "DEBUG_OFF" or # off if any "tag" matches "DEBUG_ON". This allows nested # functions to not interfere with each other. # # DebugOff accepts but ignores the '-e' and '-o' options. # The optional "rc" value will be returned rather than the # default of 0. Thus if DebugOff is the last operation in a # function, "rc" will be the return code of that function. # # DebugAdd allows adding a "tag" to "DEBUG_SH" to influence # later events, possibly in a child process. # # DebugEcho is just shorthand for: #.nf # $DEBUG_DO echo "$@" #.fi # # Debugging returns true if tracing is enabled. # It is useful for bounding complex debug actions, rather than # using lots of "DEBUG_DO" lines. # # DebugShell runs an interactive shell if any "tag" is found in # "DEBUG_INTERACTIVE", and there is a tty available. # The shell used is defined by "DEBUG_SHELL" or "SHELL" and # defaults to '/bin/sh'. # # Debug calls DebugOn and if that does not turn tracing on, it # calls DebugOff to turn it off. # # The variables "DEBUG_SKIP" and "DEBUG_DO" are set so as to # enable/disable code that should be skipped/run when debugging # is turned on. "DEBUGGING" is the same as "DEBUG_SKIP" for # backwards compatability. # # The use of $_DEBUG_SH is to prevent multiple inclusion, though # it does no harm in this case. # # BUGS: # Does not work with some versions of ksh. # If a function turns tracing on, ksh turns it off when the # function returns - useless. # PD ksh works ok ;-) # # AUTHOR: # Simon J. Gerraty # RCSid: # $Id: debug.sh,v 1.42 2024/10/30 18:23:19 sjg Exp $ # # @(#) Copyright (c) 1994-2024 Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # _DEBUG_SH=: Myname=${Myname:-`basename $0 .sh`} DEBUGGING= DEBUG_DO=: DEBUG_SKIP= export DEBUGGING DEBUG_DO DEBUG_SKIP case "$isPOSIX_SHELL,$local" in :,:|:,local|false,:) ;; # sane *) # this is the bulk of isposix-shell.sh if (echo ${PATH%:*}) > /dev/null 2>&1; then # true should be a builtin, : certainly is isPOSIX_SHELL=: # you need to eval $local var local=local : KSH_VERSION=$KSH_VERSION case "$KSH_VERSION" in Version*) local=: ;; # broken esac else isPOSIX_SHELL=false local=: false() { return 1 } fi ;; esac is_posix_shell() { $isPOSIX_SHELL return } ## # _debugAdd match # # Called from _debugOn when $match also appears in $DEBUG_SH with # a suffix of :debug_add:tag we will add tag to DEBUG_SH # _debugAdd() { eval $local tag for tag in `IFS=,; echo $DEBUG_SH` do : tag=$tag case "$tag" in $1:debug_add:*) if is_posix_shell; then tag=${tag#$1:debug_add:} else tag=`expr $tag : '.*:debug_add:\(.*\)'` fi case ",$DEBUG_SH," in *,$tag,*) ;; *) set -x : _debugAdd $1 DEBUG_SH=$DEBUG_SH,$tag set +x ;; esac ;; esac done export DEBUG_SH } ## # _debugOn match first # # Actually turn on tracing, set $DEBUG_ON=$match # # Check if $DEBUG_SH contains $match:debug_add:* and call _debugAdd # to add the suffix to DEBUG_SH. This useful when we only want # to trace some script when run under specific circumstances. # # If we have included hooks.sh $_HOOKS_SH will be set # and if $first (the first arg to DebugOn) is suitable as a variable # name we will run ${first}_debugOn_hooks. # # We disable tracing for hooks_run itself but functions can trace # if they want based on DEBUG_DO # _debugOn() { DEBUG_OFF= DEBUG_DO= DEBUG_SKIP=: DEBUG_X=-x # do this firt to reduce noise case ",$DEBUG_SH," in *,$1:debug_add:*) _debugAdd $1;; *,$2:debug_add:*) _debugAdd $2;; esac set -x DEBUG_ON=$1 case "$_HOOKS_SH,$2" in ,*|:,|:,*[${CASE_CLASS_NEG:-!}A-Za-z0-9_]*) ;; *) # avoid noise from hooks_run set +x hooks_run ${2}_debugOn_hooks set -x ;; esac } ## # _debugOff match $DEBUG_ON $first # # Actually turn off tracing, set $DEBUG_OFF=$match # # If we have included hooks.sh $_HOOKS_SH will be set # and if $first (the first arg to DebugOff) is suitable as a variable # name we will run ${first}_debugOff_hooks. # # We do hooks_run after turning off tracing, but before resetting # DEBUG_DO so functions can trace if they want # _debugOff() { DEBUG_OFF=$1 set +x case "$_HOOKS_SH,$3" in ,*|:,|:,*[${CASE_CLASS_NEG:-!}A-Za-z0-9_]*) ;; *) hooks_run ${3}_debugOff_hooks;; esac set +x # just to be sure DEBUG_ON=$2 DEBUG_DO=: DEBUG_SKIP= DEBUG_X= } ## # DebugAdd tag # # Add tag to DEBUG_SH # DebugAdd() { DEBUG_SH=${DEBUG_SH:+$DEBUG_SH,}$1 export DEBUG_SH } ## # DebugEcho message # # Output message if we are debugging # DebugEcho() { $DEBUG_DO echo "$@" } ## # Debugging # # return 0 if we are debugging. # Debugging() { test "$DEBUG_SKIP" } ## # DebugLog message # # Outout message with timestamp if we are debugging # DebugLog() { $DEBUG_SKIP return 0 echo `date '+@ %s [%Y-%m-%d %H:%M:%S %Z]'` "$@" } ## # DebugTrace message # # Something hard to miss when wading through huge -x output # DebugTrace() { $DEBUG_SKIP return 0 set +x echo "@ ==================== [ $DEBUG_ON ] ====================" DebugLog "$@" echo "@ ==================== [ $DEBUG_ON ] ====================" set -x } ## # DebugOn [-e] [-o] match ... # # Turn on debugging if any $match is found in $DEBUG_SH. # DebugOn() { eval ${local:-:} _e _match _off _rc _rc=0 # avoid problems with set -e _off=: while : do case "$1" in -e) _rc=1; shift;; # caller ok with return 1 -o) _off=; shift;; # off unless we have a match *) break;; esac done case ",${DEBUG_SH:-$DEBUG}," in ,,) return $_rc;; *,[Dd]ebug,*) ;; *) $DEBUG_DO set +x;; # reduce the noise esac _match= # if debugging is off because of a !e # don't add 'all' to the On list. case "$_off$DEBUG_OFF" in :) _e=all;; *) _e=;; esac for _e in ${*:-$Myname} $_e do : $_e in ,${DEBUG_SH:-$DEBUG}, case ",${DEBUG_SH:-$DEBUG}," in *,!$_e,*|*,!$Myname:$_e,*) # only turn it off if it was on _rc=0 $DEBUG_DO _debugOff $_e $DEBUG_ON $1 break ;; *,$_e,*|*,$Myname:$_e,*) # only turn it on if it was off _rc=0 _match=$_e $DEBUG_SKIP _debugOn $_e $1 break ;; esac done if test -z "$_off$_match"; then # off unless explicit match, but # only turn it off if it was on $DEBUG_DO _debugOff $_e $DEBUG_ON $1 fi DEBUGGING=$DEBUG_SKIP # backwards compatability $DEBUG_DO set -x # back on if needed $DEBUG_DO set -x # make sure we see it in trace return $_rc } ## # DebugOff [-e] [-o] [rc=$?] match ... # # Only turn debugging off if one of our args was the reason it # was turned on. # # We normally return 0, but caller can pass rc=$? as first arg # so that we preserve the status of last statement. # # The options '-e' and '-o' are ignored, they just make it easier to # keep DebugOn and DebugOff lines in sync. # DebugOff() { eval ${local:-:} _e _rc case ",${DEBUG_SH:-$DEBUG}," in *,[Dd]ebug,*) ;; *) $DEBUG_DO set +x;; # reduce the noise esac _rc=0 # always happy while : do case "$1" in -[eo]) shift;; # ignore it rc=*) eval "_$1"; shift;; *) break;; esac done for _e in $* do : $_e==$DEBUG_OFF DEBUG_OFF case "$DEBUG_OFF" in "") break;; $_e) _debugOn $DEBUG_ON $1; return $_rc;; esac done for _e in $* do : $_e==$DEBUG_ON DEBUG_ON case "$DEBUG_ON" in "") break;; $_e) _debugOff "" "" $1; return $_rc;; esac done DEBUGGING=$DEBUG_SKIP # backwards compatability $DEBUG_DO set -x # back on if needed $DEBUG_DO set -x # make sure we see it in trace return $_rc } _TTY=${_TTY:-`test -t 0 && tty`}; export _TTY # override this if you like _debugShell() { test "x$_TTY" != x || return 0 { echo DebugShell "$@" echo "Type 'exit' to continue..." } > $_TTY ${DEBUG_SHELL:-${SHELL:-/bin/sh}} < $_TTY > $_TTY 2>&1 } # Run an interactive shell if appropriate # Note: you can use $DEBUG_SKIP DebugShell ... to skip unless debugOn DebugShell() { eval ${local:-:} _e case "$_TTY%${DEBUG_INTERACTIVE}" in *%|%*) return 0;; # no tty or no spec esac for _e in ${*:-$Myname} all do case ",${DEBUG_INTERACTIVE}," in *,!$_e,*|*,!$Myname:$_e,*) return 0 ;; *,$_e,*|*,$Myname:$_e,*) # Provide clues as to why/where _debugShell "$_e: $@" return $? ;; esac done return 0 } # For backwards compatability Debug() { case "${DEBUG_SH:-$DEBUG}" in "") ;; *) DEBUG_ON=${DEBUG_ON:-_Debug} DebugOn -e $* || DebugOff $DEBUG_LAST DEBUGGING=$DEBUG_SKIP ;; esac }