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.35 2024/02/03 19:04:47 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
96DEBUGGING=
97DEBUG_DO=:
98DEBUG_SKIP=
99export DEBUGGING DEBUG_DO DEBUG_SKIP
100
101_debugOn() {
102	DEBUG_OFF=
103	DEBUG_DO=
104	DEBUG_SKIP=:
105	DEBUG_X=-x
106	set -x
107	DEBUG_ON=$1
108}
109
110_debugOff() {
111	DEBUG_OFF=$1
112	set +x
113	DEBUG_ON=$2
114	DEBUG_DO=:
115	DEBUG_SKIP=
116	DEBUG_X=
117}
118
119DebugEcho() {
120	$DEBUG_DO echo "$@"
121}
122
123Debugging() {
124	test "$DEBUG_SKIP"
125}
126
127DebugLog() {
128	$DEBUG_SKIP return 0
129	echo `date '+@ %s [%Y-%m-%d %H:%M:%S %Z]'` "$@"
130}
131
132# something hard to miss when wading through huge -x output
133DebugTrace() {
134	$DEBUG_SKIP return 0
135	set +x
136	echo "@ ==================== [ $DEBUG_ON ] ===================="
137	DebugLog "$@"
138	echo "@ ==================== [ $DEBUG_ON ] ===================="
139	set -x
140}
141
142# Turn on debugging if appropriate
143DebugOn() {
144	_rc=0			# avoid problems with set -e
145	_off=:
146	while :
147	do
148		case "$1" in
149		-e) _rc=1; shift;; # caller ok with return 1
150		-o) _off=; shift;; # off unless we have a match
151		*) break;;
152		esac
153	done
154	case ",${DEBUG_SH:-$DEBUG}," in
155	,,)	return $_rc;;
156	*,[Dd]ebug,*) ;;
157	*) $DEBUG_DO set +x;;		# reduce the noise
158	esac
159	_match=
160	# if debugging is off because of a !e
161	# don't add 'all' to the On list.
162	case "$_off$DEBUG_OFF" in
163	:)	_e=all;;
164	*)	_e=;;
165	esac
166	for _e in ${*:-$Myname} $_e
167	do
168		: $_e in ,${DEBUG_SH:-$DEBUG},
169		case ",${DEBUG_SH:-$DEBUG}," in
170		*,!$_e,*|*,!$Myname:$_e,*)
171			# only turn it off if it was on
172			_rc=0
173			$DEBUG_DO _debugOff $_e $DEBUG_ON
174			break
175			;;
176		*,$_e,*|*,$Myname:$_e,*)
177			# only turn it on if it was off
178			_rc=0
179			_match=$_e
180			$DEBUG_SKIP _debugOn $_e
181			break
182			;;
183		esac
184	done
185	if test -z "$_off$_match"; then
186		# off unless explicit match, but
187		# only turn it off if it was on
188		$DEBUG_DO _debugOff $_e $DEBUG_ON
189	fi
190	DEBUGGING=$DEBUG_SKIP	# backwards compatability
191	$DEBUG_DO set -x	# back on if needed
192	$DEBUG_DO set -x	# make sure we see it in trace
193	return $_rc
194}
195
196# Only turn debugging off if one of our args was the reason it
197# was turned on.
198# We normally return 0, but caller can pass rc=$? as first arg
199# so that we preserve the status of last statement.
200DebugOff() {
201	case ",${DEBUG_SH:-$DEBUG}," in
202	*,[Dd]ebug,*) ;;
203	*) $DEBUG_DO set +x;;		# reduce the noise
204	esac
205	_rc=0			# always happy
206	while :
207	do
208		case "$1" in
209		-[eo]) shift;;	# ignore it
210		rc=*) eval "_$1"; shift;;
211		*) break;;
212		esac
213	done
214	for _e in $*
215	do
216		: $_e==$DEBUG_OFF DEBUG_OFF
217		case "$DEBUG_OFF" in
218		"")	break;;
219		$_e)	_debugOn $DEBUG_ON; return $_rc;;
220		esac
221	done
222	for _e in $*
223	do
224		: $_e==$DEBUG_ON DEBUG_ON
225		case "$DEBUG_ON" in
226		"")	break;;
227		$_e)	_debugOff; return $_rc;;
228		esac
229	done
230	DEBUGGING=$DEBUG_SKIP	# backwards compatability
231	$DEBUG_DO set -x	# back on if needed
232	$DEBUG_DO set -x	# make sure we see it in trace
233	return $_rc
234}
235
236_TTY=${_TTY:-`test -t 0 && tty`}; export _TTY
237
238# override this if you like
239_debugShell() {
240	{
241		echo DebugShell "$@"
242		echo "Type 'exit' to continue..."
243	} > $_TTY
244	${DEBUG_SHELL:-${SHELL:-/bin/sh}} < $_TTY > $_TTY 2>&1
245}
246
247# Run an interactive shell if appropriate
248# Note: you can use $DEBUG_SKIP DebugShell ... to skip unless debugOn
249DebugShell() {
250	case "$_TTY%${DEBUG_INTERACTIVE}" in
251	*%|%*) return 0;;	# no tty or no spec
252	esac
253	for _e in ${*:-$Myname} all
254	do
255		case ",${DEBUG_INTERACTIVE}," in
256		*,!$_e,*|*,!$Myname:$_e,*)
257			return 0
258			;;
259		*,$_e,*|*,$Myname:$_e,*)
260			# Provide clues as to why/where
261			_debugShell "$_e: $@"
262			return $?
263			;;
264		esac
265	done
266	return 0
267}
268
269# For backwards compatability
270Debug() {
271	case "${DEBUG_SH:-$DEBUG}" in
272	"")	;;
273	*)	DEBUG_ON=${DEBUG_ON:-_Debug}
274		DebugOn -e $* || DebugOff $DEBUG_LAST
275		DEBUGGING=$DEBUG_SKIP
276		;;
277	esac
278}
279