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