1: 2# NAME: 3# debug.sh - selectively debug scripts 4# 5# SYNOPSIS: 6# $_DEBUG_SH . debug.sh 7# DebugOn [-eo] "tag" ... 8# DebugOff [-eo] [rc="rc"] "tag" ... 9# Debugging 10# DebugAdd "tag" 11# DebugEcho ... 12# DebugLog ... 13# DebugShell "tag" ... 14# DebugTrace ... 15# Debug "tag" ... 16# 17# $DEBUG_SKIP echo skipped when Debug "tag" is true. 18# $DEBUG_DO echo only done when Debug "tag" is true. 19# 20# DESCRIPTION: 21# debug.sh provides the following functions to facilitate 22# flexible run-time tracing of complicated shell scripts. 23# 24# DebugOn turns tracing on if any "tag" is found in "DEBUG_SH". 25# It turns tracing off if "!tag" is found in "DEBUG_SH". 26# It also sets "DEBUG_ON" to the "tag" that caused tracing to be 27# enabled, or "DEBUG_OFF" if we matched "!tag". 28# If '-e' option given returns 1 if no "tag" matched. 29# If the '-o' flag is given, tracing is turned off unless there 30# was a matched "tag", useful for functions too noisy to tace. 31# 32# Further; when we set "DEBUG_ON" if we find 33# "$DEBUG_ON:debug_add:tag" in "DEBUG_SH" we will 34# add the new "tag" to "DEBUG_SH" so it only has effect after that 35# point. 36# 37# DebugOff turns tracing on if any "tag" matches "DEBUG_OFF" or 38# off if any "tag" matches "DEBUG_ON". This allows nested 39# functions to not interfere with each other. 40# 41# DebugOff accepts but ignores the '-e' and '-o' options. 42# The optional "rc" value will be returned rather than the 43# default of 0. Thus if DebugOff is the last operation in a 44# function, "rc" will be the return code of that function. 45# 46# DebugAdd allows adding a "tag" to "DEBUG_SH" to influence 47# later events, possibly in a child process. 48# 49# DebugEcho is just shorthand for: 50#.nf 51# $DEBUG_DO echo "$@" 52#.fi 53# 54# Debugging returns true if tracing is enabled. 55# It is useful for bounding complex debug actions, rather than 56# using lots of "DEBUG_DO" lines. 57# 58# DebugShell runs an interactive shell if any "tag" is found in 59# "DEBUG_INTERACTIVE", and there is a tty available. 60# The shell used is defined by "DEBUG_SHELL" or "SHELL" and 61# defaults to '/bin/sh'. 62# 63# Debug calls DebugOn and if that does not turn tracing on, it 64# calls DebugOff to turn it off. 65# 66# The variables "DEBUG_SKIP" and "DEBUG_DO" are set so as to 67# enable/disable code that should be skipped/run when debugging 68# is turned on. "DEBUGGING" is the same as "DEBUG_SKIP" for 69# backwards compatability. 70# 71# The use of $_DEBUG_SH is to prevent multiple inclusion, though 72# it does no harm in this case. 73# 74# BUGS: 75# Does not work with some versions of ksh. 76# If a function turns tracing on, ksh turns it off when the 77# function returns - useless. 78# PD ksh works ok ;-) 79# 80# AUTHOR: 81# Simon J. Gerraty <sjg@crufty.net> 82 83# RCSid: 84# $Id: debug.sh,v 1.54 2026/10/13 23:00:48 sjg Exp $ 85# 86# @(#) Copyright (c) 1994-2024 Simon J. Gerraty 87# 88# SPDX-License-Identifier: BSD-2-Clause 89# 90# Please send copies of changes and bug-fixes to: 91# sjg@crufty.net 92# 93 94_DEBUG_SH=: 95 96case $0 in 97-*) ;; 98*) Myname=${Myname:-`basename $0 .sh`};; 99esac 100 101DEBUGGING= 102DEBUG_DO=: 103DEBUG_SKIP= 104export DEBUGGING DEBUG_DO DEBUG_SKIP 105 106# have is handy 107if test -z "$_HAVE_SH"; then 108 _HAVE_SH=: 109 110 ## 111 # have that does not rely on return code of type 112 # 113 have() { 114 case `(type "$1") 2>&1` in 115 *" found") return 1;; 116 esac 117 return 0 118 } 119fi 120 121# does local *actually* work? 122local_works() { 123 local _fu 124} 125 126if local_works > /dev/null 2>&1; then 127 _local=local 128else 129 _local=: 130fi 131# for backwards compatability 132local=$_local 133 134if test -z "$isPOSIX_SHELL"; then 135 if (echo ${PATH%:*}) > /dev/null 2>&1; then 136 # true should be a builtin, : certainly is 137 isPOSIX_SHELL=: 138 else 139 isPOSIX_SHELL=false 140 case `(type false) 2>&1` in 141 *builtin*) ;; 142 *) 143 false() { 144 return 1 145 } 146 ;; 147 esac 148 fi 149fi 150 151is_posix_shell() { 152 $isPOSIX_SHELL 153 return 154} 155 156 157## 158# _debugAdd match 159# 160# Called from _debugOn when $match also appears in $DEBUG_SH with 161# a suffix of :debug_add:tag we will add tag to DEBUG_SH 162# 163_debugAdd() { 164 eval $_local tag 165 166 for tag in `IFS=,; echo $DEBUG_SH` 167 do 168 : tag=$tag 169 case "$tag" in 170 $1:debug_add:*) 171 if is_posix_shell; then 172 tag=${tag#$1:debug_add:} 173 else 174 tag=`expr $tag : '.*:debug_add:\(.*\)'` 175 fi 176 case ",$DEBUG_SH," in 177 *,$tag,*) ;; 178 *) set -x 179 : _debugAdd $1 180 DEBUG_SH=$DEBUG_SH,$tag 181 set +x 182 ;; 183 esac 184 ;; 185 esac 186 done 187 export DEBUG_SH 188} 189 190 191## 192# _debugOn match first 193# 194# Actually turn on tracing, set $DEBUG_ON=$match 195# 196# Check if $DEBUG_SH contains $match:debug_add:* and call _debugAdd 197# to add the suffix to DEBUG_SH. This useful when we only want 198# to trace some script when run under specific circumstances. 199# 200# If we have included hooks.sh $_HOOKS_SH will be set 201# and if $first (the first arg to DebugOn) is suitable as a variable 202# name we will run ${first}_debugOn_hooks. 203# 204# We disable tracing for hooks_run itself but functions can trace 205# if they want based on DEBUG_DO 206# 207_debugOn() { 208 DEBUG_OFF= 209 DEBUG_DO= 210 DEBUG_SKIP=: 211 DEBUG_X=-x 212 # do this first to reduce noise 213 case ",$DEBUG_SH," in 214 *,$1:debug_add:*) _debugAdd $1;; 215 *,$2:debug_add:*) _debugAdd $2;; 216 esac 217 set -x 218 DEBUG_ON=$1 219 case "$_HOOKS_SH,$2" in 220 ,*|:,|:,*[${CASE_CLASS_NEG:-!}A-Za-z0-9_]*) ;; 221 *) # avoid noise from hooks_run 222 set +x 223 hooks_run ${2}_debugOn_hooks 224 set -x 225 ;; 226 esac 227} 228 229## 230# _debugOff match $DEBUG_ON $first 231# 232# Actually turn off tracing, set $DEBUG_OFF=$match 233# 234# If we have included hooks.sh $_HOOKS_SH will be set 235# and if $first (the first arg to DebugOff) is suitable as a variable 236# name we will run ${first}_debugOff_hooks. 237# 238# We do hooks_run after turning off tracing, but before resetting 239# DEBUG_DO so functions can trace if they want 240# 241_debugOff() { 242 $DEBUG_DO set -x # so we see DEBUG_OFF=something in the trace 243 DEBUG_OFF=${1:-$DEBUG_ON} 244 set +x 245 DEBUG_OFF=$1 246 case "$_HOOKS_SH,$3" in 247 ,*|:,|:,*[${CASE_CLASS_NEG:-!}A-Za-z0-9_]*) ;; 248 *) hooks_run ${3}_debugOff_hooks;; 249 esac 250 set +x # just to be sure 251 DEBUG_ON=$2 252 DEBUG_DO=: 253 DEBUG_SKIP= 254 DEBUG_X= 255} 256 257## 258# DebugAdd tag 259# 260# Add tag to DEBUG_SH 261# 262DebugAdd() { 263 DEBUG_SH=${DEBUG_SH:+$DEBUG_SH,}$1 264 export DEBUG_SH 265} 266 267## 268# DebugEcho message 269# 270# Output message if we are debugging 271# 272DebugEcho() { 273 $DEBUG_DO echo "$@" 274} 275 276## 277# Debugging 278# 279# return 0 if we are debugging. 280# 281Debugging() { 282 test "$DEBUG_SKIP" 283} 284 285## 286# DebugLog message 287# 288# Outout message with timestamp if we are debugging 289# 290DebugLog() { 291 $DEBUG_SKIP return 0 292 echo `date '+@ %s [%Y-%m-%d %H:%M:%S %Z]'` "$@" 293} 294 295## 296# DebugTrace message 297# 298# Something hard to miss when wading through huge -x output 299# 300DebugTrace() { 301 $DEBUG_SKIP return 0 302 set +x 303 echo "@ ==================== [ $DEBUG_ON ] ====================" 304 DebugLog "$@" 305 echo "@ ==================== [ $DEBUG_ON ] ====================" 306 set -x 307} 308 309## 310# DebugOn [-e] [-o] match ... 311# 312# Turn on debugging if any $match is found in $DEBUG_SH. 313# 314DebugOn() { 315 eval ${local:-:} _e _match _off _rc 316 _rc=0 # avoid problems with set -e 317 _off=: 318 while : 319 do 320 case "$1" in 321 -e) _rc=1; shift;; # caller ok with return 1 322 -o) _off=; shift;; # off unless we have a match 323 *) break;; 324 esac 325 done 326 case ",${DEBUG_SH:-$DEBUG}," in 327 ,,) return $_rc;; 328 *,[Dd]ebug,*) ;; 329 *) $DEBUG_DO set +x;; # reduce the noise 330 esac 331 _match= 332 # if debugging is off because of a !e 333 # don't add 'all' to the On list. 334 case "$_off$DEBUG_OFF" in 335 :) _e=all;; 336 *) _e=;; 337 esac 338 for _e in ${*:-$Myname} $_e 339 do 340 : $_e in ,${DEBUG_SH:-$DEBUG}, 341 case ",${DEBUG_SH:-$DEBUG}," in 342 *,!$_e,*|*,!$Myname:$_e,*) 343 # only turn it off if it was on 344 _rc=0 345 $DEBUG_DO _debugOff $_e $DEBUG_ON $1 346 break 347 ;; 348 *,$_e,*|*,$Myname:$_e,*) 349 # only turn it on if it was off 350 _rc=0 351 _match=$_e 352 $DEBUG_SKIP _debugOn $_e $1 353 break 354 ;; 355 esac 356 done 357 if test -z "$_off$_match"; then 358 # off unless explicit match, but 359 # only turn it off if it was on 360 $DEBUG_DO _debugOff $_e $DEBUG_ON $1 361 fi 362 DEBUGGING=$DEBUG_SKIP # backwards compatability 363 $DEBUG_DO set -x # back on if needed 364 $DEBUG_DO set -x # make sure we see it in trace 365 return $_rc 366} 367 368## 369# DebugOff [-e] [-o] [rc=$?] match ... 370# 371# Only turn debugging off if one of our args was the reason it 372# was turned on. 373# 374# We normally return 0, but caller can pass rc=$? as first arg 375# so that we preserve the status of last statement. 376# 377# The options '-e' and '-o' are ignored, they just make it easier to 378# keep DebugOn and DebugOff lines in sync. 379# 380DebugOff() { 381 eval ${local:-:} _e _rc 382 case ",${DEBUG_SH:-$DEBUG}," in 383 *,[Dd]ebug,*) ;; 384 *) $DEBUG_DO set +x;; # reduce the noise 385 esac 386 _rc=0 # always happy 387 while : 388 do 389 case "$1" in 390 -[eo]) shift;; # ignore it 391 rc=*) eval "_$1"; shift;; 392 *) break;; 393 esac 394 done 395 for _e in $* 396 do 397 : $_e==$DEBUG_OFF DEBUG_OFF 398 case "$DEBUG_OFF" in 399 "") break;; 400 $_e) _debugOn $DEBUG_ON $1; return $_rc;; 401 esac 402 done 403 for _e in $* 404 do 405 : $_e==$DEBUG_ON DEBUG_ON 406 case "$DEBUG_ON" in 407 "") break;; 408 $_e) _debugOff "" "" $1; return $_rc;; 409 esac 410 done 411 DEBUGGING=$DEBUG_SKIP # backwards compatability 412 $DEBUG_DO set -x # back on if needed 413 $DEBUG_DO set -x # make sure we see it in trace 414 return $_rc 415} 416 417_TTY=${_TTY:-`test -t 0 && tty || :`}; export _TTY 418 419# override this if you like 420_debugShell() { 421 test "x$_TTY" != x || return 0 422 { 423 echo DebugShell "$@" 424 echo "Type 'exit' to continue..." 425 } > $_TTY 426 ${DEBUG_SHELL:-${SHELL:-/bin/sh}} < $_TTY > $_TTY 2>&1 427} 428 429# Run an interactive shell if appropriate 430# Note: you can use $DEBUG_SKIP DebugShell ... to skip unless debugOn 431DebugShell() { 432 eval ${local:-:} _e 433 case "$_TTY%${DEBUG_INTERACTIVE}" in 434 *%|%*) return 0;; # no tty or no spec 435 esac 436 for _e in ${*:-$Myname} all 437 do 438 case ",${DEBUG_INTERACTIVE}," in 439 *,!$_e,*|*,!$Myname:$_e,*) 440 return 0 441 ;; 442 *,$_e,*|*,$Myname:$_e,*) 443 # Provide clues as to why/where 444 _debugShell "$_e: $@" 445 return $? 446 ;; 447 esac 448 done 449 return 0 450} 451 452# For backwards compatibility 453Debug() { 454 case "${DEBUG_SH:-$DEBUG}" in 455 "") ;; 456 *) DEBUG_ON=${DEBUG_ON:-_Debug} 457 DebugOn -e $* || DebugOff $DEBUG_LAST 458 DEBUGGING=$DEBUG_SKIP 459 ;; 460 esac 461} 462