Skip to content

Instantly share code, notes, and snippets.

@slowpeek
Last active August 31, 2022 17:03
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slowpeek/7e2731b620d558f44841da6772743d38 to your computer and use it in GitHub Desktop.
Save slowpeek/7e2731b620d558f44841da6772743d38 to your computer and use it in GitHub Desktop.
locate-exit.sh
# -*- mode: sh; sh-shell: bash; -*-
# shellcheck shell=bash
# MIT license (c) 2021 https://github.com/slowpeek
# Homepage: https://gist.github.com/slowpeek/7e2731b620d558f44841da6772743d38
# This script can help you find out which line and why (end of code,
# literal exit, 'set -e' violation, bash error) your bash script
# finished execution.
#
# Source it in the head of your code (after 'set -e' if you're using
# it). You can deactivate it with LOCATE_EXIT=n env var. The report is
# printed to stderr.
#
# Your code should NOT:
# - trap EXIT and ERR
# - override 'exit' builtin
#
# SAMPLE REPORT
#
# --- locate-exit report ---
# Code: 4
# Reason: 'set -e' violation
#
# Context:
# ./2.sh:8 helper_1
# ./2.sh:14 do_something
# ./2.sh:18
# ---
#
# KNOWN PROBLEMS
#
# If you dont use 'set -e' and the last command in your script returns
# a non-zero status, it would be reported as 'bash error' even though
# it was end of code actually.
[[ ! ${LOCATE_EXIT-} == n ]] || return 0
if [[ $- == *e* ]]; then
set -E
trap 'locate_exit_trap err' ERR
fi
trap 'locate_exit_trap exit' EXIT
# This override is a trick to catch lineno on literal exit.
exit () {
builtin exit "$@"
}
locate_exit_trap () {
local st=$?
[[ $$ == "$BASHPID" ]] || return 0 # Ignore subshells.
trap EXIT
{
echo -ne "\n--- locate-exit report ---\nCode: $st\nReason: "
local context=y skip=1 lineno=("${BASH_LINENO[@]}")
if [[ $1 == exit ]]; then
if [[ ${FUNCNAME[1]} == exit ]]; then
skip=2
echo 'literal exit'
else
if [[ $st == 0 ]]; then
echo 'end of code'
context=n
else
echo 'bash error'
# On bash error it always reports line#1 no matter
# where it actually happened.
lineno[0]=\?
fi
fi
else
echo "'set -e' violation"
fi
if [[ $context == y ]]; then
echo -e '\nContext:'
local n=${#FUNCNAME[@]} i=$skip s
for ((; i<n; i++)); do
s="${BASH_SOURCE[i]}:${lineno[i-1]}"
((i >= n-1)) || [[ ${FUNCNAME[i]} == source ]] ||
s+=" ${FUNCNAME[i]}"
printf '%s\n' "$s"
done
fi
echo '---'
} >&2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment