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.47 2025/08/07 21:59:54 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 96Myname=${Myname:-`basename $0 .sh`} 97 98DEBUGGING= 99DEBUG_DO=: 100DEBUG_SKIP= 101export DEBUGGING DEBUG_DO DEBUG_SKIP 102 103# have is handy 104if test -z "$_HAVE_SH"; then 105 _HAVE_SH=: 106 107 ## 108 # have that does not rely on return code of type 109 # 110 have() { 111 case `(type "$1") 2>&1` in 112 *" found") return 1;; 113 esac 114 return 0 115 } 116fi 117 118# does local *actually* work? 119local_works() { 120 local _fu 121} 122 123if local_works > /dev/null 2>&1; then 124 _local=local 125else 126 _local=: 127fi 128# for backwards compatability 129local=$_local 130 131if test -z "$isPOSIX_SHELL"; then 132 if (echo ${PATH%:*}) > /dev/null 2>&1; then 133 # true should be a builtin, : certainly is 134 isPOSIX_SHELL=: 135 else 136 isPOSIX_SHELL=false 137 false() { 138 return 1 139 } 140 fi 141fi 142 143is_posix_shell() { 144 $isPOSIX_SHELL 145 return 146} 147 148 149## 150# _debugAdd match 151# 152# Called from _debugOn when $match also appears in $DEBUG_SH with 153# a suffix of :debug_add:tag we will add tag to DEBUG_SH 154# 155_debugAdd() { 156 eval $_local tag 157 158 for tag in `IFS=,; echo $DEBUG_SH` 159 do 160 : tag=$tag 161 case "$tag" in 162 $1:debug_add:*) 163 if is_posix_shell; then 164 tag=${tag#$1:debug_add:} 165 else 166 tag=`expr $tag : '.*:debug_add:\(.*\)'` 167 fi 168 case ",$DEBUG_SH," in 169 *,$tag,*) ;; 170 *) set -x 171 : _debugAdd $1 172 DEBUG_SH=$DEBUG_SH,$tag 173 set +x 174 ;; 175 esac 176 ;; 177 esac 178 done 179 export DEBUG_SH 180} 181 182 183## 184# _debugOn match first 185# 186# Actually turn on tracing, set $DEBUG_ON=$match 187# 188# Check if $DEBUG_SH contains $match:debug_add:* and call _debugAdd 189# to add the suffix to DEBUG_SH. This useful when we only want 190# to trace some script when run under specific circumstances. 191# 192# If we have included hooks.sh $_HOOKS_SH will be set 193# and if $first (the first arg to DebugOn) is suitable as a variable 194# name we will run ${first}_debugOn_hooks. 195# 196# We disable tracing for hooks_run itself but functions can trace 197# if they want based on DEBUG_DO 198# 199_debugOn() { 200 DEBUG_OFF= 201 DEBUG_DO= 202 DEBUG_SKIP=: 203 DEBUG_X=-x 204 # do this firt to reduce noise 205 case ",$DEBUG_SH," in 206 *,$1:debug_add:*) _debugAdd $1;; 207 *,$2:debug_add:*) _debugAdd $2;; 208 esac 209 set -x 210 DEBUG_ON=$1 211 case "$_HOOKS_SH,$2" in 212 ,*|:,|:,*[${CASE_CLASS_NEG:-!}A-Za-z0-9_]*) ;; 213 *) # avoid noise from hooks_run 214 set +x 215 hooks_run ${2}_debugOn_hooks 216 set -x 217 ;; 218 esac 219} 220 221## 222# _debugOff match $DEBUG_ON $first 223# 224# Actually turn off tracing, set $DEBUG_OFF=$match 225# 226# If we have included hooks.sh $_HOOKS_SH will be set 227# and if $first (the first arg to DebugOff) is suitable as a variable 228# name we will run ${first}_debugOff_hooks. 229# 230# We do hooks_run after turning off tracing, but before resetting 231# DEBUG_DO so functions can trace if they want 232# 233_debugOff() { 234 DEBUG_OFF=$1 235 set +x 236 case "$_HOOKS_SH,$3" in 237 ,*|:,|:,*[${CASE_CLASS_NEG:-!}A-Za-z0-9_]*) ;; 238 *) hooks_run ${3}_debugOff_hooks;; 239 esac 240 set +x # just to be sure 241 DEBUG_ON=$2 242 DEBUG_DO=: 243 DEBUG_SKIP= 244 DEBUG_X= 245} 246 247## 248# DebugAdd tag 249# 250# Add tag to DEBUG_SH 251# 252DebugAdd() { 253 DEBUG_SH=${DEBUG_SH:+$DEBUG_SH,}$1 254 export DEBUG_SH 255} 256 257## 258# DebugEcho message 259# 260# Output message if we are debugging 261# 262DebugEcho() { 263 $DEBUG_DO echo "$@" 264} 265 266## 267# Debugging 268# 269# return 0 if we are debugging. 270# 271Debugging() { 272 test "$DEBUG_SKIP" 273} 274 275## 276# DebugLog message 277# 278# Outout message with timestamp if we are debugging 279# 280DebugLog() { 281 $DEBUG_SKIP return 0 282 echo `date '+@ %s [%Y-%m-%d %H:%M:%S %Z]'` "$@" 283} 284 285## 286# DebugTrace message 287# 288# Something hard to miss when wading through huge -x output 289# 290DebugTrace() { 291 $DEBUG_SKIP return 0 292 set +x 293 echo "@ ==================== [ $DEBUG_ON ] ====================" 294 DebugLog "$@" 295 echo "@ ==================== [ $DEBUG_ON ] ====================" 296 set -x 297} 298 299## 300# DebugOn [-e] [-o] match ... 301# 302# Turn on debugging if any $match is found in $DEBUG_SH. 303# 304DebugOn() { 305 eval ${local:-:} _e _match _off _rc 306 _rc=0 # avoid problems with set -e 307 _off=: 308 while : 309 do 310 case "$1" in 311 -e) _rc=1; shift;; # caller ok with return 1 312 -o) _off=; shift;; # off unless we have a match 313 *) break;; 314 esac 315 done 316 case ",${DEBUG_SH:-$DEBUG}," in 317 ,,) return $_rc;; 318 *,[Dd]ebug,*) ;; 319 *) $DEBUG_DO set +x;; # reduce the noise 320 esac 321 _match= 322 # if debugging is off because of a !e 323 # don't add 'all' to the On list. 324 case "$_off$DEBUG_OFF" in 325 :) _e=all;; 326 *) _e=;; 327 esac 328 for _e in ${*:-$Myname} $_e 329 do 330 : $_e in ,${DEBUG_SH:-$DEBUG}, 331 case ",${DEBUG_SH:-$DEBUG}," in 332 *,!$_e,*|*,!$Myname:$_e,*) 333 # only turn it off if it was on 334 _rc=0 335 $DEBUG_DO _debugOff $_e $DEBUG_ON $1 336 break 337 ;; 338 *,$_e,*|*,$Myname:$_e,*) 339 # only turn it on if it was off 340 _rc=0 341 _match=$_e 342 $DEBUG_SKIP _debugOn $_e $1 343 break 344 ;; 345 esac 346 done 347 if test -z "$_off$_match"; then 348 # off unless explicit match, but 349 # only turn it off if it was on 350 $DEBUG_DO _debugOff $_e $DEBUG_ON $1 351 fi 352 DEBUGGING=$DEBUG_SKIP # backwards compatability 353 $DEBUG_DO set -x # back on if needed 354 $DEBUG_DO set -x # make sure we see it in trace 355 return $_rc 356} 357 358## 359# DebugOff [-e] [-o] [rc=$?] match ... 360# 361# Only turn debugging off if one of our args was the reason it 362# was turned on. 363# 364# We normally return 0, but caller can pass rc=$? as first arg 365# so that we preserve the status of last statement. 366# 367# The options '-e' and '-o' are ignored, they just make it easier to 368# keep DebugOn and DebugOff lines in sync. 369# 370DebugOff() { 371 eval ${local:-:} _e _rc 372 case ",${DEBUG_SH:-$DEBUG}," in 373 *,[Dd]ebug,*) ;; 374 *) $DEBUG_DO set +x;; # reduce the noise 375 esac 376 _rc=0 # always happy 377 while : 378 do 379 case "$1" in 380 -[eo]) shift;; # ignore it 381 rc=*) eval "_$1"; shift;; 382 *) break;; 383 esac 384 done 385 for _e in $* 386 do 387 : $_e==$DEBUG_OFF DEBUG_OFF 388 case "$DEBUG_OFF" in 389 "") break;; 390 $_e) _debugOn $DEBUG_ON $1; return $_rc;; 391 esac 392 done 393 for _e in $* 394 do 395 : $_e==$DEBUG_ON DEBUG_ON 396 case "$DEBUG_ON" in 397 "") break;; 398 $_e) _debugOff "" "" $1; return $_rc;; 399 esac 400 done 401 DEBUGGING=$DEBUG_SKIP # backwards compatability 402 $DEBUG_DO set -x # back on if needed 403 $DEBUG_DO set -x # make sure we see it in trace 404 return $_rc 405} 406 407_TTY=${_TTY:-`test -t 0 && tty`}; export _TTY 408 409# override this if you like 410_debugShell() { 411 test "x$_TTY" != x || return 0 412 { 413 echo DebugShell "$@" 414 echo "Type 'exit' to continue..." 415 } > $_TTY 416 ${DEBUG_SHELL:-${SHELL:-/bin/sh}} < $_TTY > $_TTY 2>&1 417} 418 419# Run an interactive shell if appropriate 420# Note: you can use $DEBUG_SKIP DebugShell ... to skip unless debugOn 421DebugShell() { 422 eval ${local:-:} _e 423 case "$_TTY%${DEBUG_INTERACTIVE}" in 424 *%|%*) return 0;; # no tty or no spec 425 esac 426 for _e in ${*:-$Myname} all 427 do 428 case ",${DEBUG_INTERACTIVE}," in 429 *,!$_e,*|*,!$Myname:$_e,*) 430 return 0 431 ;; 432 *,$_e,*|*,$Myname:$_e,*) 433 # Provide clues as to why/where 434 _debugShell "$_e: $@" 435 return $? 436 ;; 437 esac 438 done 439 return 0 440} 441 442# For backwards compatability 443Debug() { 444 case "${DEBUG_SH:-$DEBUG}" in 445 "") ;; 446 *) DEBUG_ON=${DEBUG_ON:-_Debug} 447 DebugOn -e $* || DebugOff $DEBUG_LAST 448 DEBUGGING=$DEBUG_SKIP 449 ;; 450 esac 451} 452