Commit | Line | Data |
---|---|---|
78a1a75c | 1 | #!/bin/bash |
fc2d9041 IK |
2 | # Copyright (C) 2019 Ian Kelling |
3 | # SPDX-License-Identifier: GPL-3.0-or-later | |
acd03e1e | 4 | |
acd03e1e IK |
5 | # Commentary: Bash stack trace and error handling functions. This file |
6 | # is meant to be sourced. It loads some functions which you may want to | |
7 | # call manually (see the comments at the start of each one), and then | |
8 | # runs err-catch. See the README file for a slightly longer explanation. | |
9 | ||
a5c2ac43 | 10 | |
a5c2ac43 IK |
11 | ####################################### |
12 | # Print stack trace | |
13 | # | |
5a600375 | 14 | # usage: err-bash-trace [MESSAGE] |
a5c2ac43 | 15 | # |
62aed8de IK |
16 | # This function is called by the other functions which print stack |
17 | # traces. | |
18 | # | |
a5c2ac43 IK |
19 | # It does not show function args unless you first run: |
20 | # shopt -s extdebug | |
62aed8de | 21 | # which err-catch & err-print do for you. |
a5c2ac43 | 22 | # |
5a600375 IK |
23 | # MESSAGE Message to print just before the stack trace. |
24 | # | |
25 | # _frame_start Optional variable to set before calling. The frame to | |
26 | # start printing on. default=1. Useful when printing from | |
27 | # an ERR trap function to avoid printing that function. | |
a5c2ac43 IK |
28 | ####################################### |
29 | err-bash-trace() { | |
5a600375 | 30 | local -i argc_index=0 frame i start=${_frame_start:-1} |
acd03e1e | 31 | local source |
5a600375 IK |
32 | if [[ $1 ]]; then |
33 | printf "%s\n" "$1" | |
acd03e1e | 34 | fi |
5a600375 | 35 | for ((frame=0; frame < ${#FUNCNAME[@]}; frame++)); do |
acd03e1e IK |
36 | argc=${BASH_ARGC[frame]} |
37 | argc_index+=$argc | |
fb79b8bd | 38 | ((frame < start)) && continue |
acd03e1e | 39 | if (( ${#BASH_SOURCE[@]} > 1 )); then |
5a600375 | 40 | source="${BASH_SOURCE[frame]}:${BASH_LINENO[frame-1]}:" |
acd03e1e | 41 | fi |
5a600375 IK |
42 | printf " from %sin \`%s" "$source" "${FUNCNAME[frame]}" |
43 | if shopt extdebug >/dev/null; then | |
acd03e1e IK |
44 | for ((i=argc_index-1; i >= argc_index-argc; i--)); do |
45 | printf " %s" "${BASH_ARGV[i]}" | |
46 | done | |
78a1a75c | 47 | fi |
acd03e1e IK |
48 | echo \' |
49 | done | |
a5c2ac43 | 50 | return 0 |
78a1a75c | 51 | } |
364203b2 | 52 | |
a5c2ac43 IK |
53 | ####################################### |
54 | # On error print stack trace and exit | |
55 | # | |
56 | # Globals: | |
fc2d9041 | 57 | # errcatch-cleanup If set, this command will run just before exiting. |
a5c2ac43 | 58 | ####################################### |
997eb0be | 59 | err-catch() { |
fc2d9041 IK |
60 | set -E; |
61 | # This condition avoids starting the bash debugger in the case that | |
62 | # this is sourced from a startup file, and you use a login shell to | |
63 | # run a command. Avoid doing that if you want function arguments in | |
64 | # your trace. | |
5f3350ec | 65 | if [[ $- != *c* ]] || ! shopt login_shell >/dev/null; then |
fc2d9041 IK |
66 | shopt -s extdebug |
67 | fi | |
acd03e1e IK |
68 | _err-trap() { |
69 | err=$? | |
70 | exec >&2 | |
71 | set +x | |
5a600375 IK |
72 | local msg="${BASH_SOURCE[1]}:${BASH_LINENO[0]}: \`$BASH_COMMAND' returned $err" |
73 | if (( ${#FUNCNAME[@]} > 2 )); then | |
74 | local _frame_start=2 | |
75 | err-bash-trace "$msg" | |
76 | else | |
77 | echo "$msg" | |
78 | fi | |
9ce3dca1 | 79 | set -e # err trap does not work within an error trap |
fc2d9041 IK |
80 | if type -t errcatch-cleanup >/dev/null; then |
81 | errcatch-cleanup | |
82 | fi | |
5a600375 | 83 | echo "$0: exiting with status $err" |
acd03e1e IK |
84 | exit $err |
85 | } | |
86 | trap _err-trap ERR | |
87 | set -o pipefail | |
78a1a75c | 88 | } |
364203b2 | 89 | |
5a600375 | 90 | |
5f3350ec IK |
91 | ####################################### |
92 | # Internal function for err-catch-interactive. | |
93 | # Prints stack trace from interactive shell trap. | |
94 | # Usage: see err-catch-interactive | |
95 | ####################################### | |
96 | ||
97 | _err-bash-trace-interactive() { | |
98 | local ret bash_command argc pattern i | |
99 | # We have these passed to us because they are lost inside the | |
100 | # function. | |
101 | ret=$1 | |
102 | bash_command="$2" | |
103 | argc=$(( $3 - 1 )) | |
104 | shift 3 | |
105 | argv=("$@") | |
106 | for pattern in "${err_catch_ignore[@]}"; do | |
107 | # shellcheck disable=SC2053 | |
108 | if [[ ${BASH_SOURCE[0]} == $pattern ]]; then | |
109 | return 0 | |
110 | fi | |
111 | done | |
112 | if (( ${#FUNCNAME[@]} > _err_func_last )); then | |
113 | echo ERR: \`$bash_command\' returned $ret | |
114 | fi | |
115 | _err_func_last=${#FUNCNAME[@]} | |
116 | if (( _err_func_last > 1 )); then | |
117 | printf " from \`%s" "${FUNCNAME[1]}" | |
118 | if shopt extdebug >/dev/null; then | |
119 | for ((i=argc; i >= 0; i--)); do | |
120 | printf " %s" "${argv[i]}" | |
121 | done | |
122 | fi | |
123 | printf "\' defined at %s:%s\n" "${BASH_SOURCE[1]}" "$(declare -F "${FUNCNAME[1]}"|awk "{print \$2}")" | |
124 | return $ret | |
125 | fi | |
126 | } | |
127 | ||
5a600375 IK |
128 | ####################################### |
129 | # For interactive shells: on error, print stack trace and return | |
130 | # | |
5f3350ec IK |
131 | # Note: calling line number is not available, so we print function |
132 | # definition lines. | |
133 | # | |
5a600375 | 134 | # Globals: |
dc069500 IK |
135 | # err_catch_ignore Array containing glob patterns to test against filenames to ignore |
136 | # errors from. Initialized to ignore bash-completion scripts on debian | |
137 | # based systems. | |
5f3350ec | 138 | # _err_func_last Used internally in err-bash-trace-interactive |
5a600375 | 139 | # |
5a600375 | 140 | ####################################### |
5a600375 | 141 | err-catch-interactive() { |
5f3350ec IK |
142 | if ! test ${err_catch_ignore+defined}; then |
143 | err_catch_ignore=( | |
144 | '/etc/bash_completion.d/*' | |
145 | '*/bash-completion/*' | |
146 | ) | |
147 | fi | |
5a600375 IK |
148 | declare -i _err_func_last=0 |
149 | set -E; shopt -s extdebug | |
150 | # shellcheck disable=SC2154 | |
5f3350ec | 151 | trap '_err-bash-trace-interactive $? "$BASH_COMMAND" ${BASH_ARGC[0]} "${BASH_ARGV[@]}"' ERR |
5a600375 IK |
152 | set -o pipefail |
153 | } | |
154 | ||
155 | ||
62aed8de | 156 | ####################################### |
dc069500 | 157 | # Undoes err-catch/err-catch-interactive |
62aed8de IK |
158 | ####################################### |
159 | err-allow() { | |
dc069500 IK |
160 | shopt -u extdebug |
161 | set +E +o pipefail | |
162 | trap ERR | |
62aed8de IK |
163 | } |
164 | ||
a5c2ac43 IK |
165 | ####################################### |
166 | # On error, print stack trace | |
167 | ####################################### | |
9ce3dca1 IK |
168 | err-print() { |
169 | # help: on errors: print stack trace | |
170 | # | |
171 | # This function depends on err-bash-trace. | |
172 | ||
173 | set -E; shopt -s extdebug | |
174 | _err-trap() { | |
175 | err=$? | |
176 | exec >&2 | |
177 | set +x | |
178 | echo "${BASH_SOURCE[1]}:${BASH_LINENO[0]}: \`$BASH_COMMAND' returned $err" | |
179 | err-bash-trace 2 | |
180 | } | |
181 | trap _err-trap ERR | |
182 | set -o pipefail | |
183 | } | |
184 | ||
185 | ||
a5c2ac43 | 186 | ####################################### |
62aed8de | 187 | # Print stack trace and exit |
a5c2ac43 IK |
188 | # |
189 | # Use this instead of the exit command to be more informative. | |
190 | # | |
191 | # usage: err-exit [EXIT_CODE] [MESSAGE] | |
192 | # | |
193 | # EXIT_CODE Default is 1. | |
194 | # MESSAGE Print MESSAGE to stderr. If only one of EXIT_CODE | |
195 | # and MESSAGE is given, we consider it to be an | |
196 | # exit code if it is a number. | |
197 | ####################################### | |
997eb0be | 198 | err-exit() { |
acd03e1e IK |
199 | exec >&2 |
200 | code=1 | |
41be570a | 201 | if [[ "$*" ]]; then |
acd03e1e IK |
202 | if [[ ${1/[^0-9]/} == "$1" ]]; then |
203 | code=$1 | |
204 | if [[ $2 ]]; then | |
a5c2ac43 | 205 | printf '%s\n' "$2" >&2 |
acd03e1e IK |
206 | fi |
207 | else | |
a5c2ac43 | 208 | printf '%s\n' "$0: $1" >&2 |
78a1a75c | 209 | fi |
acd03e1e IK |
210 | fi |
211 | echo "${BASH_SOURCE[1]}:${BASH_LINENO[0]}" | |
212 | err-bash-trace 2 | |
213 | echo "$0: exiting with code $code" | |
214 | exit $err | |
78a1a75c | 215 | } |
364203b2 | 216 | |
a5c2ac43 | 217 | # We want this more often than not, so run it now. |
5a600375 IK |
218 | if [[ $- == *i* ]]; then |
219 | err-catch-interactive | |
220 | else | |
221 | err-catch | |
222 | fi |