#!/bin/bash
-# Copyright (C) 2019 Ian Kelling
+# Bash Error Handler
+# Copyright (C) 2020 Ian Kelling <ian@iankelling.org>
# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# This is a single file library, just source this file. When an error
+# happens, we print a stack trace then exit. In an interactive shell, we
+# return from functions instead of exiting. If err-cleanup is a command,
+# it runs before the stack trace. Functions are documented inline below
+# for additional use cases.
+#
+# Note: occasionally the line numbers are off a bit (at least in Bash
+# 5.0). This appears to be a bash bug. I plan to report it next time it
+# happens to me.
+#
+# Please email me if you use this or have anything to contribute. I'm
+# not aware of any users yet Ian Kelling <ian@iankelling.org>.
+#
+# Tested on bash 4.4.20(1)-release (x86_64-pc-linux-gnu) and
+# 5.0.17(1)-release (x86_64-pc-linux-gnu).
+#
+# Related: see my bash script template repo at https://iankelling.org/git.
+
+
+# TODO: investigate to see if we can format output betting in case of
+# subshell failure. Right now, we get independent trace from inside and
+# outside of the subshell. Note, errexit + inherit_errexit doesn't have
+# any smarts around this either.
-# Commentary: Print stack trace and exit/return on errors, or use
-# functions below for for more details and manual error handling. See
-# end of file for credits etc.
+if ! test "$BASH_VERSION"; then echo "error: shell is not bash" >&2; exit 1; fi
#######################################
# err-catch: Setup trap on ERR to print stack trace and exit (or return
# Note: In interactive shell, stack calling line number is not
# available, so we print function definition lines.
#
+# Note: This works like set -e, which has one unintuitive feature: If
+# you use a function as part of a conditional, eg: func && come_cmd, a
+# failed command within func won't trigger an error.
+#
# Globals
#
# err_catch_ignore Array containing glob patterns to test against
)
fi
declare -i _err_func_last=0
- shopt -s extdebug
+ if [[ $- != *c* ]]; then
+ shopt -s extdebug
+ fi
# shellcheck disable=SC2154
- trap '_err-bash-trace-interactive $? "$BASH_COMMAND" ${BASH_ARGC[0]} "${BASH_ARGV[@]}" || return $?' ERR
+ trap '_err-bash-trace-interactive $? "${PIPESTATUS[*]}" "$BASH_COMMAND" ${BASH_ARGC[0]} "${BASH_ARGV[@]}" || return $?' ERR
else
# Man bash on exdebug: "If set at shell invocation, arrange to
# execute the debugger". We want to avoid that, but I want this file
#
#######################################
err-exit() {
- local err=$?
+ # vars have _ prefix so that we can inspect existing set vars without
+ # too much overwriting of them.
+ local _err=$? _pipestatus="${_pipestatus[*]}"
+
# This has to come before most things or vars get changed
- local msg="${BASH_SOURCE[1]}:${BASH_LINENO[0]}: \`$BASH_COMMAND' returned $err"
+ local _msg="${BASH_SOURCE[1]}:${BASH_LINENO[0]}: \`$BASH_COMMAND' returned $_err"
+ local _cmdr="$BASH_COMMAND" # command right. we chop of the left, keep the right.
+
+ if [[ $_pipestatus != "$_err" ]]; then
+ _msg+=", PIPESTATUS: $_pipestatus"
+ fi
set +x
if [[ $1 == -* ]]; then
- err=${1#-}
+ _err=${1#-}
shift
- elif (( ! err )); then
- err=1
+ elif (( ! _err )); then
+ _err=1
fi
if [[ $1 ]]; then
- msg="$1"
+ _msg="$1"
+ fi
+
+ ## Begin printing vars from within BASH_COMMAND ##
+ local _var _chars _l
+ local -A _vars
+ while [[ $_cmdr ]]; do
+ _chars="${#_cmdr}"
+ _cmdr="${_cmdr#*$}"
+ _cmdr="${_cmdr#{}"
+ if (( _chars == ${#_cmdr} )); then
+ break
+ fi
+ _var="${_cmdr%%[^a-zA-Z0-9_]*}"
+ if [[ ! $_var || $_var == [0-9]* ]]; then
+ continue
+ fi
+ _vars[${_var}]=t
+ done
+ #echo "iank ${_vars[*]}"
+ #set |& grep ^password
+ # in my small test, this took 50% longer than piping to grep.
+ # That seems a small enough penalty to stay in bash here.
+ if (( ${#_vars[@]} )); then
+ set |& while read -r _l; do
+ for _var in "${!_vars[@]}"; do
+ case $_l in
+ ${_var}=*) printf "%s\n" "$_l" >&2 ;;
+ esac
+ done
+ done
fi
- printf "%s\n" "$msg" >&2
+ ## End printing vars from within BASH_COMMAND ##
+
+ printf "%s\n" "$_msg" >&2
err-bash-trace 2
set -e # err trap does not work within an error trap
if type -t err-cleanup >/dev/null; then
err-cleanup
fi
- printf "%s: exiting with status %s\n" "$0" "$err" >&2
- exit $err
+ printf "%s: exiting with status %s\n" "$0" "$_err" >&2
+ exit $_err
}
#######################################
# We have these passed to us because they are lost inside the
# function.
ret=$1
- bash_command="$2"
- argc=$(( $3 - 1 ))
- shift 3
+ pipestatus="$2"
+ bash_command="$3"
+ argc=$(( $4 - 1 ))
+ shift 4
argv=("$@")
# The trap returns a nonzero, then gets called again. This condition
- # tells us if we are the first.
- if (( _err_func_last > last )); then
- printf "ERR: \`%s\' returned %s\n" "$bash_command" $ret >&2
+ # tells us if is that has happened by checking if we've gone down a
+ # stack level.
+ if (( _err_func_last >= last )); then
+ printf "ERR: \`%s\' returned %s" "$bash_command" $ret >&2
+ if [[ $pipestatus != "$ret" ]]; then
+ printf ", PIPESTATUS: %s" "$pipestatus" >&2
+ fi
+ echo >&2
fi
printf " from \`%s" "${FUNCNAME[1]}" >&2
if shopt extdebug >/dev/null; then
return 0
fi
}
-
-# Credits etc:
-#
-# Related: see my bash script template repo at https://iankelling.org/git.
-#
-#
-# Please email me if you have a patches, bugs, feedback, or if you use
-# it or republish it since I'm not aware of any users yet
-# Ian Kelling <ian@iankelling.org>.
-#
-# Tested on bash 4.4.20(1)-release (x86_64-pc-linux-gnu). If you test