#!/usr/bin/env bash # constants MIAOU_BASH_ERROR=$(mktemp -t "$(basename "$0").XXXXXXXX" --tmpdir="/run/user/$(id -u)") MIAOU_DEBUG=${MIAOU_DEBUG:-false} readonly MIAOU_BASH_ERROR MIAOU_DEBUG ## functions function is_true { [[ "${1:-}" == 'true' ]] } # FIXME: define an alias to bypass ARG evaluation when unnecessary! # example: # alias toto="bash -c \"[[ \\\$MIAOU_DEBUG == 'true' ]] && echo yes \\\$* || echo nope\"" function miaou_debug { is_true "${MIAOU_DEBUG}" && >/dev/tty builtin echo -e "\e[90mDEBUG <==> \e[33m$*\e[0m" || true } function tty_ansi { fg="${1:-}" [[ -n $fg ]] && >/dev/tty echo -ne "\e[${fg}m" || true } function on_error { local code code="${1:-100}" >/dev/tty printf '\r' # force carriage return explicitly miaou_debug "on_error: $code ($$ -- $BASHPID)" if [[ $$ == "$BASHPID" ]]; then >&2 echo "${BASH_SOURCE[1]}: line ${BASH_LINENO[0]}: command_error: ${code}" failure_type="COMMAND_ERROR" error_source=("${BASH_SOURCE[@]}") error_lineno=("${BASH_LINENO[@]}") error_funcname=("${FUNCNAME[@]}") unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]' unset 'error_source[0]' 'error_lineno[-1]' 'error_funcname[0]' error_funcname[-1]='
' error_source=("${error_source[@]}") error_lineno=("${error_lineno[@]}") error_funcname=("${error_funcname[@]}") exit "$code" else >&2 echo "${BASH_SOURCE[1]}: line ${BASH_LINENO[0]}: subshell_error: ${code}" kill -PIPE $$ fi } function dump_stderr { miaou_debug --stderr-- mapfile -t tmp_error <"$MIAOU_BASH_ERROR" tty_ansi 94 for i in "${tmp_error[@]}"; do echo "$i"; done tty_ansi 0 miaou_debug --end of stderr-- } function dump_failure { miaou_debug --error box-- tty_ansi 94 echo -n "FAILURE TYPE = " tty_ansi 95 echo "$failure_type" tty_ansi 0 miaou_debug --end of error box-- } function dump_stacktrace { miaou_debug --stacktrace ${#error_source[@]}-- tty_ansi 94 local i source lineno funcname for i in "${!error_source[@]}"; do source="${error_source[$i]}" lineno="${error_lineno[$i]}" funcname="${error_funcname[$i]}" printf "%40s:%.4s \t%s\n" "$source" "$lineno" "$funcname" done tty_ansi 0 miaou_debug --end of stacktrace-- } function on_exit { code="${1:-0}" 2>&3 >&3 dump_stderr if [[ -v failure_type ]]; then 2>&3 >&3 dump_failure 2>&3 >&3 dump_stacktrace fi miaou_debug "on_exit $code" } function on_pipe { local code tmp_error regex error_source=("${BASH_SOURCE[@]}") error_lineno=("${BASH_LINENO[@]}") error_funcname=("${FUNCNAME[@]}") unset 'error_source[0]' 'error_lineno[0]' 'error_funcname[0]' unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]' unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]' mapfile -t tmp_error <"$MIAOU_BASH_ERROR" last_error="${tmp_error[-1]}" regex='(.*): line ([0-9]+): subshell_error: ([0-9]+)' if [[ $last_error =~ $regex ]]; then source=${BASH_REMATCH[1]} lineno=${BASH_REMATCH[2]} code=${BASH_REMATCH[3]} failure_type="SUBSHELL_ERROR" error_source=("$source" "${error_source[@]}") error_lineno=("$lineno" "${error_lineno[@]}") error_funcname=("${error_funcname[@]}" '
') else code=141 failure_type="PIPE_ERROR" fi # miaou_debug on_pipe: "${code}" exit "$code" } ## main set -euo pipefail set -E # HALT on subshell error # set -T # DEBUG RETURN [[ $# -lt 1 ]] && >&2 echo 'ERROR: script expected!' && builtin exit 1 BASH_ARGV0="$1" && shift # to pretend script runs by itself trap 'on_error $?' ERR trap 'on_exit $?' EXIT trap 'on_pipe' PIPE miaou_debug "--source from ${BASHPID}" exec 3>/dev/stderr # shellcheck source=/dev/null 2>"$MIAOU_BASH_ERROR" source "$BASH_ARGV0" exec 3>- miaou_debug '--source success'