First, for an excellent introduction to why error handling in Bash is not straightforward at all, I recommend reading this article by Dirk Avery.
Secondly, his final solution can be improved upon by adding a stack trace. Here is what I use and some example code to demonstrate how it works:
#!/bin/bash
set -Eeuo pipefail
trap 'catch' ERR
catch() {
local exit_code=$?
local cmd="$BASH_COMMAND"
local i
# Check if exit was due to error
if [ "$exit_code" != "0" ]; then
echo ""
echo "[-] ERROR $exit_code during command: $cmd"
echo "Call stack:"
for ((i=1; i<${#FUNCNAME[@]}; i++)); do
local func="${FUNCNAME[$i]}"
local src="${BASH_SOURCE[$i]}"
local line="${BASH_LINENO[$((i - 1))]}"
# Added to track nested function calls from functions
# Extract the actual line of code (skip if file inaccessible)
if [[ -r "$src" ]]; then
local code_line
code_line=$(sed -n "${line}s/^[[:space:]]*/ /p" "$src")
echo " at $src:$line in function $func:"
echo "$code_line"
else
echo " at $src:$line in function $func (source not readable)"
fi
done
echo "Tip: You can put any exception handling code here"
fi
}
test_function() {
cat nonexistent_file
}
test_function
Here is what the output looks like:
user@machine:~$ bash test.sh
cat: nonexistent_file: No such file or directory
[-] ERROR 1 during command: cat nonexistent_file
Call stack:
at test.sh:38 in function test_function:
cat nonexistent_file
at test.sh:41 in function main:
test_function
Tip: You can put any exception handling code here
Note: The set -E flag is important because it allows the ERR trap to be inherited by functions.
set -e, because the way it works is just confusing and unhelpful, and when you're at the point where you want stack traces, it might just be best to switch to another programming language with better safety and better debugging tooling.sh, but it’s generally already installed on >95% of systems you’re probably going to be working on, is trivially available for a majority of those that don’t already have it installed, and if you stick to the standard library is arguably more portable than shell script.