Flag for Exiting on non-zero (set -e)
TLDR: set -e
is NOT full proof and comes with caveats.
Arguments against set -e
Example
✅ Basic example works well
set -e
foo() {
echo "This is foo; returns 1 (unhappy)"
return 1
}
main() {
foo
echo.green "MAIN FINISHED! (after foo calls)"
}
main "${@}"
m:mac d:kotlin-mp b:master ○❯sr
This is foo; returns 1 (unhappy)
m:mac d:kotlin-mp b:master ○❯
❌ Checking for failure/success, even up the chain prevents 'set -e' from triggering.❌
From ❌ Checking for failure/success, even up the chain prevents 'set -e' from triggering.❌
Go to text →
To show in an example, how set -e
is prevented from triggering if up the chain we have a success or error branch.
Let's say we have the following code.
#!/usr/bin/env bash
set -e
echo_with_pid(){
echo "[\$\$=$$/$BASHPID] $*"
}
foo2() {
echo_with_pid "foo2-enter"
i_dont_exist
echo_with_pid "foo2-exit"
}
foo1() {
echo_with_pid "foo1-enter"
foo2
echo_with_pid "foo1-exit"
}
main() {
echo_with_pid "START"
foo1
echo_with_pid "DONE"
}
# If we just call main, `set -e` works as one would expect
main
Aborts execution and giving us the following output:
[$$=37290/37290] START
[$$=37290/37290] foo1-enter
[$$=37290/37290] foo2-enter
/tmp/scratch.sh: line 11: i_dont_exist: command not found
BUT, if we were to check the return code of main
, the behavior becomes quite un-intuitive.
# If we run with having just a success branch (no failure branch), the not-found command get's masked.
if main; then
echo "Main succeeded"
fi
❯/tmp/scratch.sh
[$$=38049/38049] START
[$$=38049/38049] foo1-enter
[$$=38049/38049] foo2-enter
/tmp/scratch.sh: line 11: i_dont_exist: command not found
[$$=38049/38049] foo2-exit
[$$=38049/38049] foo1-exit
[$$=38049/38049] DONE
Main succeeded
command_not_found_handle does not have this issue and is able to catch
⚠️ 'local' used on the same line will prevent 'set -e' from aborting
From ⚠️ 'local' used on the same line with function call will prevent 'set -e' from aborting script
Go to text →
set -e
foo() {
echo "This is foo; returns 1 (unhappy)"
return 1
}
main() {
echo "Calling foo()"
# We succeed because of 'local' being used on the same line as the function call.
local foo_capture="$(foo)"
echo "After foo()"
echo.green "MAIN FINISHED! (after foo calls), foo_capture=[${foo_capture}]"
}
main "${@}"
Output:
m:mac d:kotlin-mp b:master ○❯sr
Calling foo()
After foo()
MAIN FINISHED! (after foo calls), foo_capture=[This is foo; returns 1 (unhappy)]
m:mac d:kotlin-mp b:master ○❯
IF we were to split up declaration of foo_capture
into
local foo_capture
foo_capture="$(foo)"
Then set -e
would trigger and script would stop.
⚠️ Some commands return non-zero in non-error cases - By default with 0 visibility
#!/usr/bin/env bash
set -e
echo_with_pid(){
echo "[\$\$=$$/$BASHPID] $*"
}
main() {
echo_with_pid "START"
echo "1" > /tmp/1
echo "2" > /tmp/2
# Diff will return 1 if files are not the same, this will abort the script.
#
# In this case we would have to adjust the code to be
# diff /tmp/1 /tmp/2 || true
#
# For the script to NOT abort.
diff /tmp/1 /tmp/2
echo ""
echo_with_pid "DONE"
}
main "${@}"
Output
[$$=26314/26314] START
1c1
< 1
---
> 2
The more annoying part is not having to write
diff /tmp/1 /tmp/2 || true
But that when set -e
halts it does NOT provide any debugging info on WHY it stopped.
For Searchability
For Searchability
set -o pipefail
Children
- ⚠️ 'local' used on the same line with function call will prevent 'set -e' from aborting script
- ❌ Checking for failure/success, even up the chain prevents 'set -e' from triggering.❌
Backlinks