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