How to trap ERR when using 'set -e' in Bash
Olivia Zamora
I have a simple script :
#!/bin/bash
set -e
trap "echo BOO!" ERR
function func(){ ls /root/
}
funcI would like to trap ERR if my script fails (as it will here b/c I do not have the permissions to look into /root). However, when using set -e it is not trapped. Without set -e ERR is trapped.
According to the bash man page, for set -e :
... A trap on ERR, if set, is executed before the shell exits. ...
Why isn't my trap executed? From the man page it seems like it should.
14 Answers
chepner's answer is the best solution: If you want to combine set -e (same as: set -o errexit) with an ERR trap, also use set -o errtrace (same as: set -E).
In short: use set -eE in lieu of just set -e:
#!/bin/bash
set -eE # same as: `set -o errexit -o errtrace`
trap 'echo BOO!' ERR
function func(){ ls /root/
}
# Thanks to -E / -o errtrace, this still triggers the trap,
# even though the failure occurs *inside the function*.
func A more sophisticated example trap example that prints the message in red and also prints the exit code:trap 'printf "\e[31m%s: %s\e[m\n" "BOO!" $?' ERR
man bash says about set -o errtrace / set -E:
If set, any trap on ERR is inherited by shell functions, command substitutions, and commands executed in a subshell environment. The ERR trap is normally not inherited in such cases.
What I believe is happening:
Without
-e: Thelscommand fails inside your function, and, due to being the last command in the function, the function reportsls's nonzero exit code to the caller, your top-level script scope. In that scope, theERRtrap is in effect, and it is invoked (but note that execution will continue, unless you explicitly callexitfrom the trap).With
-e(but without-E): Thelscommand fails inside your function, and becauseset -eis in effect, Bash instantly exits, directly from the function scope - and since there is noERRtrap in effect there (because it wasn't inherited from the parent scope), your trap is not called.
While the man page is not incorrect, I agree that this behavior is not exactly obvious - you have to infer it.
You need to use set -o errtrace for the function to inherit the trap.
Replace ERR with EXIT and it will work.
The syntax of the trap command is: trap [COMMANDS] [SIGNALS]
For more info, please read
1We have these options for debugging:
-eExit immediately on failure-EIf set, any trap on ERR is inherited by shell functions-uExit when there is an unbound variable-oGive a option-name to set- pipefail The return values of last (rightmost) command (exit code)
-vPrint all shell input lines as they are read-xPrint trace of commands
For handling the errors we can catch directory with trap
trap 'echo >&2 "Error - exited with status $? at line $LINENO' ERROr a better version ref :
trap 'echo >&2 "Error - exited with status $? at line $LINENO:"; pr -tn $0 | tail -n+$((LINENO - 3)) | head -n7' ERROr a function:
function __error_handing__(){ local last_status_code=$1; local error_line_number=$2; echo 1>&2 "Error - exited with status $last_status_code at line $error_line_number"; perl -slne 'if($.+5 >= $ln && $.-4 <= $ln){ $_="$. $_"; s/$ln/">" x length($ln)/eg; s/^\D+.*?$/\e[1;31m$&\e[0m/g; print}' -- -ln=$error_line_number $0
}and call it this way:
trap '__error_handing__ $? $LINENO' ERR