:1# NAME:2# debug.sh - selectively debug scripts3#4# SYNOPSIS:5# $_DEBUG_SH . debug.sh6# DebugOn [-eo] "tag" ...7# DebugOff [-eo] [rc="rc"] "tag" ...8# Debugging9# DebugAdd "tag"10# DebugEcho ...11# DebugLog ...12# DebugShell "tag" ...13# DebugTrace ...14# Debug "tag" ...15#16# $DEBUG_SKIP echo skipped when Debug "tag" is true.17# $DEBUG_DO echo only done when Debug "tag" is true.18#19# DESCRIPTION:20# debug.sh provides the following functions to facilitate21# flexible run-time tracing of complicated shell scripts.22#23# DebugOn turns tracing on if any "tag" is found in "DEBUG_SH".24# It turns tracing off if "!tag" is found in "DEBUG_SH".25# It also sets "DEBUG_ON" to the "tag" that caused tracing to be26# enabled, or "DEBUG_OFF" if we matched "!tag".27# If '-e' option given returns 1 if no "tag" matched.28# If the '-o' flag is given, tracing is turned off unless there29# was a matched "tag", useful for functions too noisy to tace.30#31# Further; when we set "DEBUG_ON" if we find32# "$DEBUG_ON:debug_add:tag" in "DEBUG_SH" we will33# add the new "tag" to "DEBUG_SH" so it only has effect after that34# point.35#36# DebugOff turns tracing on if any "tag" matches "DEBUG_OFF" or37# off if any "tag" matches "DEBUG_ON". This allows nested38# functions to not interfere with each other.39#40# DebugOff accepts but ignores the '-e' and '-o' options.41# The optional "rc" value will be returned rather than the42# default of 0. Thus if DebugOff is the last operation in a43# function, "rc" will be the return code of that function.44#45# DebugAdd allows adding a "tag" to "DEBUG_SH" to influence46# later events, possibly in a child process.47#48# DebugEcho is just shorthand for:49#.nf50# $DEBUG_DO echo "$@"51#.fi52#53# Debugging returns true if tracing is enabled.54# It is useful for bounding complex debug actions, rather than55# using lots of "DEBUG_DO" lines.56#57# DebugShell runs an interactive shell if any "tag" is found in58# "DEBUG_INTERACTIVE", and there is a tty available.59# The shell used is defined by "DEBUG_SHELL" or "SHELL" and60# defaults to '/bin/sh'.61#62# Debug calls DebugOn and if that does not turn tracing on, it63# calls DebugOff to turn it off.64#65# The variables "DEBUG_SKIP" and "DEBUG_DO" are set so as to66# enable/disable code that should be skipped/run when debugging67# is turned on. "DEBUGGING" is the same as "DEBUG_SKIP" for68# backwards compatability.69#70# The use of $_DEBUG_SH is to prevent multiple inclusion, though71# it does no harm in this case.72#73# BUGS:74# Does not work with some versions of ksh.75# If a function turns tracing on, ksh turns it off when the76# function returns - useless.77# PD ksh works ok ;-)78#79# AUTHOR:80# Simon J. Gerraty <[email protected]>8182# RCSid:83# $Id: debug.sh,v 1.47 2025/08/07 21:59:54 sjg Exp $84#85# @(#) Copyright (c) 1994-2024 Simon J. Gerraty86#87# SPDX-License-Identifier: BSD-2-Clause88#89# Please send copies of changes and bug-fixes to:90# [email protected]91#9293_DEBUG_SH=:9495Myname=${Myname:-`basename $0 .sh`}9697DEBUGGING=98DEBUG_DO=:99DEBUG_SKIP=100export DEBUGGING DEBUG_DO DEBUG_SKIP101102# have is handy103if test -z "$_HAVE_SH"; then104_HAVE_SH=:105106##107# have that does not rely on return code of type108#109have() {110case `(type "$1") 2>&1` in111*" found") return 1;;112esac113return 0114}115fi116117# does local *actually* work?118local_works() {119local _fu120}121122if local_works > /dev/null 2>&1; then123_local=local124else125_local=:126fi127# for backwards compatability128local=$_local129130if test -z "$isPOSIX_SHELL"; then131if (echo ${PATH%:*}) > /dev/null 2>&1; then132# true should be a builtin, : certainly is133isPOSIX_SHELL=:134else135isPOSIX_SHELL=false136false() {137return 1138}139fi140fi141142is_posix_shell() {143$isPOSIX_SHELL144return145}146147148##149# _debugAdd match150#151# Called from _debugOn when $match also appears in $DEBUG_SH with152# a suffix of :debug_add:tag we will add tag to DEBUG_SH153#154_debugAdd() {155eval $_local tag156157for tag in `IFS=,; echo $DEBUG_SH`158do159: tag=$tag160case "$tag" in161$1:debug_add:*)162if is_posix_shell; then163tag=${tag#$1:debug_add:}164else165tag=`expr $tag : '.*:debug_add:\(.*\)'`166fi167case ",$DEBUG_SH," in168*,$tag,*) ;;169*) set -x170: _debugAdd $1171DEBUG_SH=$DEBUG_SH,$tag172set +x173;;174esac175;;176esac177done178export DEBUG_SH179}180181182##183# _debugOn match first184#185# Actually turn on tracing, set $DEBUG_ON=$match186#187# Check if $DEBUG_SH contains $match:debug_add:* and call _debugAdd188# to add the suffix to DEBUG_SH. This useful when we only want189# to trace some script when run under specific circumstances.190#191# If we have included hooks.sh $_HOOKS_SH will be set192# and if $first (the first arg to DebugOn) is suitable as a variable193# name we will run ${first}_debugOn_hooks.194#195# We disable tracing for hooks_run itself but functions can trace196# if they want based on DEBUG_DO197#198_debugOn() {199DEBUG_OFF=200DEBUG_DO=201DEBUG_SKIP=:202DEBUG_X=-x203# do this firt to reduce noise204case ",$DEBUG_SH," in205*,$1:debug_add:*) _debugAdd $1;;206*,$2:debug_add:*) _debugAdd $2;;207esac208set -x209DEBUG_ON=$1210case "$_HOOKS_SH,$2" in211,*|:,|:,*[${CASE_CLASS_NEG:-!}A-Za-z0-9_]*) ;;212*) # avoid noise from hooks_run213set +x214hooks_run ${2}_debugOn_hooks215set -x216;;217esac218}219220##221# _debugOff match $DEBUG_ON $first222#223# Actually turn off tracing, set $DEBUG_OFF=$match224#225# If we have included hooks.sh $_HOOKS_SH will be set226# and if $first (the first arg to DebugOff) is suitable as a variable227# name we will run ${first}_debugOff_hooks.228#229# We do hooks_run after turning off tracing, but before resetting230# DEBUG_DO so functions can trace if they want231#232_debugOff() {233DEBUG_OFF=$1234set +x235case "$_HOOKS_SH,$3" in236,*|:,|:,*[${CASE_CLASS_NEG:-!}A-Za-z0-9_]*) ;;237*) hooks_run ${3}_debugOff_hooks;;238esac239set +x # just to be sure240DEBUG_ON=$2241DEBUG_DO=:242DEBUG_SKIP=243DEBUG_X=244}245246##247# DebugAdd tag248#249# Add tag to DEBUG_SH250#251DebugAdd() {252DEBUG_SH=${DEBUG_SH:+$DEBUG_SH,}$1253export DEBUG_SH254}255256##257# DebugEcho message258#259# Output message if we are debugging260#261DebugEcho() {262$DEBUG_DO echo "$@"263}264265##266# Debugging267#268# return 0 if we are debugging.269#270Debugging() {271test "$DEBUG_SKIP"272}273274##275# DebugLog message276#277# Outout message with timestamp if we are debugging278#279DebugLog() {280$DEBUG_SKIP return 0281echo `date '+@ %s [%Y-%m-%d %H:%M:%S %Z]'` "$@"282}283284##285# DebugTrace message286#287# Something hard to miss when wading through huge -x output288#289DebugTrace() {290$DEBUG_SKIP return 0291set +x292echo "@ ==================== [ $DEBUG_ON ] ===================="293DebugLog "$@"294echo "@ ==================== [ $DEBUG_ON ] ===================="295set -x296}297298##299# DebugOn [-e] [-o] match ...300#301# Turn on debugging if any $match is found in $DEBUG_SH.302#303DebugOn() {304eval ${local:-:} _e _match _off _rc305_rc=0 # avoid problems with set -e306_off=:307while :308do309case "$1" in310-e) _rc=1; shift;; # caller ok with return 1311-o) _off=; shift;; # off unless we have a match312*) break;;313esac314done315case ",${DEBUG_SH:-$DEBUG}," in316,,) return $_rc;;317*,[Dd]ebug,*) ;;318*) $DEBUG_DO set +x;; # reduce the noise319esac320_match=321# if debugging is off because of a !e322# don't add 'all' to the On list.323case "$_off$DEBUG_OFF" in324:) _e=all;;325*) _e=;;326esac327for _e in ${*:-$Myname} $_e328do329: $_e in ,${DEBUG_SH:-$DEBUG},330case ",${DEBUG_SH:-$DEBUG}," in331*,!$_e,*|*,!$Myname:$_e,*)332# only turn it off if it was on333_rc=0334$DEBUG_DO _debugOff $_e $DEBUG_ON $1335break336;;337*,$_e,*|*,$Myname:$_e,*)338# only turn it on if it was off339_rc=0340_match=$_e341$DEBUG_SKIP _debugOn $_e $1342break343;;344esac345done346if test -z "$_off$_match"; then347# off unless explicit match, but348# only turn it off if it was on349$DEBUG_DO _debugOff $_e $DEBUG_ON $1350fi351DEBUGGING=$DEBUG_SKIP # backwards compatability352$DEBUG_DO set -x # back on if needed353$DEBUG_DO set -x # make sure we see it in trace354return $_rc355}356357##358# DebugOff [-e] [-o] [rc=$?] match ...359#360# Only turn debugging off if one of our args was the reason it361# was turned on.362#363# We normally return 0, but caller can pass rc=$? as first arg364# so that we preserve the status of last statement.365#366# The options '-e' and '-o' are ignored, they just make it easier to367# keep DebugOn and DebugOff lines in sync.368#369DebugOff() {370eval ${local:-:} _e _rc371case ",${DEBUG_SH:-$DEBUG}," in372*,[Dd]ebug,*) ;;373*) $DEBUG_DO set +x;; # reduce the noise374esac375_rc=0 # always happy376while :377do378case "$1" in379-[eo]) shift;; # ignore it380rc=*) eval "_$1"; shift;;381*) break;;382esac383done384for _e in $*385do386: $_e==$DEBUG_OFF DEBUG_OFF387case "$DEBUG_OFF" in388"") break;;389$_e) _debugOn $DEBUG_ON $1; return $_rc;;390esac391done392for _e in $*393do394: $_e==$DEBUG_ON DEBUG_ON395case "$DEBUG_ON" in396"") break;;397$_e) _debugOff "" "" $1; return $_rc;;398esac399done400DEBUGGING=$DEBUG_SKIP # backwards compatability401$DEBUG_DO set -x # back on if needed402$DEBUG_DO set -x # make sure we see it in trace403return $_rc404}405406_TTY=${_TTY:-`test -t 0 && tty`}; export _TTY407408# override this if you like409_debugShell() {410test "x$_TTY" != x || return 0411{412echo DebugShell "$@"413echo "Type 'exit' to continue..."414} > $_TTY415${DEBUG_SHELL:-${SHELL:-/bin/sh}} < $_TTY > $_TTY 2>&1416}417418# Run an interactive shell if appropriate419# Note: you can use $DEBUG_SKIP DebugShell ... to skip unless debugOn420DebugShell() {421eval ${local:-:} _e422case "$_TTY%${DEBUG_INTERACTIVE}" in423*%|%*) return 0;; # no tty or no spec424esac425for _e in ${*:-$Myname} all426do427case ",${DEBUG_INTERACTIVE}," in428*,!$_e,*|*,!$Myname:$_e,*)429return 0430;;431*,$_e,*|*,$Myname:$_e,*)432# Provide clues as to why/where433_debugShell "$_e: $@"434return $?435;;436esac437done438return 0439}440441# For backwards compatability442Debug() {443case "${DEBUG_SH:-$DEBUG}" in444"") ;;445*) DEBUG_ON=${DEBUG_ON:-_Debug}446DebugOn -e $* || DebugOff $DEBUG_LAST447DEBUGGING=$DEBUG_SKIP448;;449esac450}451452453