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