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