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