Make interactive catch function ignore bash-completion
[errhandle.git] / err
1 #!/bin/bash
2 # Copyright 2018 Ian Kelling
3
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7
8 # http://www.apache.org/licenses/LICENSE-2.0
9
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16
17 # Commentary: Bash stack trace and error handling functions. This file
18 # is meant to be sourced. It loads some functions which you may want to
19 # call manually (see the comments at the start of each one), and then
20 # runs err-catch. See the README file for a slightly longer explanation.
21
22
23 #######################################
24 # Print stack trace
25 #
26 # usage: err-bash-trace [MESSAGE]
27 #
28 # This function is called by the other functions which print stack
29 # traces.
30 #
31 # It does not show function args unless you first run:
32 # shopt -s extdebug
33 # which err-catch & err-print do for you.
34 #
35 # MESSAGE Message to print just before the stack trace.
36 #
37 # _frame_start Optional variable to set before calling. The frame to
38 # start printing on. default=1. Useful when printing from
39 # an ERR trap function to avoid printing that function.
40 #######################################
41 err-bash-trace() {
42 local -i argc_index=0 frame i start=${_frame_start:-1}
43 local source
44 if [[ $1 ]]; then
45 printf "%s\n" "$1"
46 fi
47 for ((frame=0; frame < ${#FUNCNAME[@]}; frame++)); do
48 argc=${BASH_ARGC[frame]}
49 argc_index+=$argc
50 ((frame < start)) && continue
51 if (( ${#BASH_SOURCE[@]} > 1 )); then
52 source="${BASH_SOURCE[frame]}:${BASH_LINENO[frame-1]}:"
53 fi
54 printf " from %sin \`%s" "$source" "${FUNCNAME[frame]}"
55 if shopt extdebug >/dev/null; then
56 for ((i=argc_index-1; i >= argc_index-argc; i--)); do
57 printf " %s" "${BASH_ARGV[i]}"
58 done
59 fi
60 echo \'
61 done
62 return 0
63 }
64
65 #######################################
66 # On error print stack trace and exit
67 #
68 # Globals:
69 # ${_errcatch_cleanup[@]} Optional command & args that will run before exiting
70 #######################################
71 err-catch() {
72 set -E; shopt -s extdebug
73 _err-trap() {
74 err=$?
75 exec >&2
76 set +x
77 local msg="${BASH_SOURCE[1]}:${BASH_LINENO[0]}: \`$BASH_COMMAND' returned $err"
78 if (( ${#FUNCNAME[@]} > 2 )); then
79 local _frame_start=2
80 err-bash-trace "$msg"
81 else
82 echo "$msg"
83 fi
84 set -e # err trap does not work within an error trap
85 # note :-: makes this compatible with set -u, assigns : if unset, but shellcheck
86 # doesn't understand that.
87 # shellcheck disable=SC2154
88 "${_errcatch_cleanup[@]:-:}"
89 echo "$0: exiting with status $err"
90 exit $err
91 }
92 trap _err-trap ERR
93 set -o pipefail
94 }
95
96
97 #######################################
98 # For interactive shells: on error, print stack trace and return
99 #
100 # Globals:
101 # err_catch_ignore Array containing glob patterns to test against filenames to ignore
102 # errors from. Initialized to ignore bash-completion scripts on debian
103 # based systems.
104 # _err_func_last Used internally.
105 # _err_catch_err Used internally.
106 # _err_catch_i Used internally.
107 # _err_catch_ignore Used internally.
108 #
109 # misc: All shellcheck disables for this function are false positives.
110 #######################################
111 # shellcheck disable=SC2120
112 err-catch-interactive() {
113 err_catch_ignore=(
114 '/etc/bash_completion.d/*'
115 )
116 # shellcheck disable=SC2034
117 declare -i _err_func_last=0
118 set -E; shopt -s extdebug
119 # shellcheck disable=SC2154
120 trap '_err_catch_err=$? _trap_bc="$BASH_COMMAND"
121 _err_catch_ignore=false
122 for _err_catch_i in "${err_catch_ignore[@]}"; do
123 if [[ ${BASH_SOURCE[0]} == $_err_catch_i ]]; then
124 _err_catch_ignore=true
125 break
126 fi
127 done
128 if ! $_err_catch_ignore; then
129 if (( ${#FUNCNAME[@]} > _err_func_last )); then
130 echo ERR: \`$_trap_bc'"\'"' returned $_err_catch_err
131 fi
132 _err_func_last=${#FUNCNAME[@]}
133 if (( _err_func_last )); then
134 printf " from %s:%s:in \`%s" "${BASH_SOURCE[0]}" "$(declare -F "${FUNCNAME[0]}"|awk "{print \$2}")" "${FUNCNAME[0]}"
135 if shopt extdebug >/dev/null; then
136 for ((_err_catch_i=${BASH_ARGC[0]}-1; _err_catch_i >= 0; _err_catch_i--)); do
137 printf " %s" "${BASH_ARGV[_err_catch_i]}"
138 done
139 fi
140 echo '"\'"'
141 return $_err_catch_err
142 fi
143 fi' ERR
144 set -o pipefail
145 }
146
147
148 #######################################
149 # Undoes err-catch/err-catch-interactive
150 #######################################
151 err-allow() {
152 shopt -u extdebug
153 set +E +o pipefail
154 trap ERR
155 }
156
157 #######################################
158 # On error, print stack trace
159 #######################################
160 err-print() {
161 # help: on errors: print stack trace
162 #
163 # This function depends on err-bash-trace.
164
165 set -E; shopt -s extdebug
166 _err-trap() {
167 err=$?
168 exec >&2
169 set +x
170 echo "${BASH_SOURCE[1]}:${BASH_LINENO[0]}: \`$BASH_COMMAND' returned $err"
171 err-bash-trace 2
172 }
173 trap _err-trap ERR
174 set -o pipefail
175 }
176
177
178 #######################################
179 # Print stack trace and exit
180 #
181 # Use this instead of the exit command to be more informative.
182 #
183 # usage: err-exit [EXIT_CODE] [MESSAGE]
184 #
185 # EXIT_CODE Default is 1.
186 # MESSAGE Print MESSAGE to stderr. If only one of EXIT_CODE
187 # and MESSAGE is given, we consider it to be an
188 # exit code if it is a number.
189 #######################################
190 err-exit() {
191 exec >&2
192 code=1
193 if [[ "$*" ]]; then
194 if [[ ${1/[^0-9]/} == "$1" ]]; then
195 code=$1
196 if [[ $2 ]]; then
197 printf '%s\n' "$2" >&2
198 fi
199 else
200 printf '%s\n' "$0: $1" >&2
201 fi
202 fi
203 echo "${BASH_SOURCE[1]}:${BASH_LINENO[0]}"
204 err-bash-trace 2
205 echo "$0: exiting with code $code"
206 exit $err
207 }
208
209 # We want this more often than not, so run it now.
210 if [[ $- == *i* ]]; then
211 err-catch-interactive
212 else
213 err-catch
214 fi