Created
February 4, 2023 00:11
-
-
Save Alphadelta14/0d9175767b406a6d3d402099f134d816 to your computer and use it in GitHub Desktop.
Bash Traceback
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# exit on error (errexit) | |
set -e | |
# pass ERR trap to subshells (errtrace) | |
set -E | |
# Callback function for when set -e (ERREXIT) is triggered) | |
# This shows a small stacktrace for the current shell along with the failing | |
# function call. | |
function _show_traceback() { | |
# The very first line captures the return code in $?. | |
local rc=$? | |
# We pass "${BASH_SOURCE[0]}" and $LINENO (note: not $BASH_LINENO) from | |
# the trap so they refer to the failing call before this is run. | |
# Otherwise, they refer to this _show_traceback itself. | |
local fail_file=$1 | |
local file_line=$2 | |
# traceback locals | |
local line_idx; | |
local filename; | |
# FUNCNAME is an array of functions | |
local frame=${#FUNCNAME[@]} | |
# skip "main" frame | |
frame=$(( frame - 1 )) | |
_warning "Subcommand failed with code=$rc. Bash traceback:" | |
# Roughly an enumeration like:: | |
# for frame_id, func in reversed(enumerate(FUNCNAME[1:])): | |
# filename = BASH_SOURCE[frame_id+1] | |
# line_idx = BASH_LINENO[frame_id] | |
while [ "$frame" -gt 1 ]; do | |
# BASH_SOURCE is +1 from BASH_LINENO and FUNCNAME index | |
filename="${BASH_SOURCE[$frame]:-"<script>"}" | |
# decrement after getting source, which is +1 of the rest | |
frame=$(( frame - 1 )) | |
line_idx="${BASH_LINENO[$frame]}" | |
func="${FUNCNAME[$frame]}" | |
_log "In $filename, line $line_idx:" | |
_log "[#$frame]\t$func" | |
done | |
# frame 0 is _show_traceback, so we instead show $1 and $2 from our trap | |
_log "In $fail_file, line $file_line:" | |
_error "\t$BASH_COMMAND" | |
_error "\t^-- returned $rc" | |
_log "" | |
return "$rc" | |
} | |
function _register_traceback() { | |
# Cause shell to exit whenever any command fails. | |
# Allows the ERR trap to be called before exit. (errexit) | |
set -e | |
# Ensure _show_traceback is propagated through functions (errtrace) | |
set -E | |
# Invoke _show_traceback whenever a failure occurs (via set -e) | |
# Pass the current script and lineno (man (1) bash for LINENO usage) | |
trap '_show_traceback "${BASH_SOURCE[0]}" "$LINENO"' ERR | |
} | |
# Writes a message to stderr | |
# $* is the message to be echo'd. | |
function _log() { | |
# >&2 means that the default (stdout) is being redirected (>) to &2 (stderr) | |
echo -e "$@" >&2 | |
} | |
# Write a message to stderr with Yellow foreground. WARNING: will be prepended with a red background. | |
# $* - Message | |
function _warning() { | |
_log "\\033[1;33m\\033[41mWARNING\\033[49m: $*\\033[0m" | |
} | |
# Write a message to stderr using Red foreground. | |
# It will be prefixed with ERROR: automatically. | |
# $* - Message | |
function _error() { | |
_log "\\033[1;91mERROR: $*\\033[0m" | |
} | |
function _test_something_that_fails() { | |
false # a failing exit code | |
} | |
function _test_subfunction() { | |
_test_something_that_fails # this is where we fail | |
echo "success" # we shouldn't get here | |
} | |
# Register our error handler | |
_register_traceback | |
# Should generate a traceback: | |
# WARNING: Subcommand failed with code=1. Bash traceback: | |
# In bash_traceback.sh, line 101: | |
# [#2] _test_subfunction | |
# In bash_traceback.sh, line 86: | |
# [#1] _test_something_that_fails | |
# In bash_traceback.sh, line 82: | |
# ERROR: false | |
# ERROR: ^-- returned 1 | |
_test_subfunction |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment