⚠️ Exit Is Not as Strong as You Think when it comes to stopping ⚠️
You might think that exit 1 will do a hard stop on your script.
And while it will be a hard stop on the particular process that you are running in, that does not mean it will halt your script.
An example to illustrate this is as follows:
foo() {
echo "foo: I am foo and I am exiting!!! [\$\$=$$/$BASHPID]"
exit 1
}
export -f foo
main() {
echo "MAIN: [\$\$=$$/$BASHPID]"
local foo_result
foo_result="$(foo)"
echo "MAIN: And I don't care that you exited I am going to keep going"
echo "MAIN: You said: [${foo_result:?}], but I am 'main' and had other plans."
return 0
}
main "${@}" || exit 1
The output is as follows, where exit 1 did not stop main from continuing.
MAIN: [$$=3006467/3006467]
MAIN: And I don't care that you exited I am going to keep going
MAIN: You said: [foo: I am foo and I am exiting!!! [$$=3006467/3006472]], but I am 'main' and had other plans.
The reason is that $() spawned a new process (see Parenthesis Spawn a Subshell/New Process: $() AND ()). You can see this by the different $BASHPID that correctly identified the process id. So exit 1 only exited 3006472 PID and not 3006467.
In a subshell created by $(), exit 1 behaves similarly to return 1 in that it doesn’t propagate beyond that subshell.
Example how main should have collaborated, if we are using exit 1/return 1
foo() {
echo "foo: I am foo and I am exiting!!! [\$\$=$$/$BASHPID]"
exit 1
}
export -f foo
main() {
echo "MAIN: [\$\$=$$/$BASHPID]"
local foo_result
foo_result="$(foo)" || {
echo "MAIN: I will respect your wishes to exit..."
exit 1
}
echo "Continuing... with happy foo result: ${foo_result:?}"
return 0
}
main "${@}" || exit 1
PS
⚠️ One might think 'set -e' would save us in the first example but it does not ⚠️
set -e does not save us
If we set -e (See Flag for Exiting on non-zero (set -e)).
We could expect the first example to halt, due to non zero code returned by $(foo)
set -e
foo() {
echo "foo: I am foo and I am exiting!!! [\$\$=$$/$BASHPID]"
exit 1
}
export -f foo
main() {
echo "MAIN: [\$\$=$$/$BASHPID]"
local foo_result
foo_result="$(foo)"
echo "MAIN: And I don't care that you exited I am going to keep going"
echo "MAIN: You said: [${foo_result:?}], but I am 'main' and had other plans."
return 0
}
main "${@}" || exit 1
But the output will be still the same
MAIN: [$$=3012359/3012359]
MAIN: And I don't care that you exited I am going to keep going
MAIN: You said: [foo: I am foo and I am exiting!!! [$$=3012359/3012361]], but I am 'main' and had other plans.
The reason for that is the following line
main "${@}" || exit 1
And this behavior of set -e: ❌ Checking for failure/success, even up the chain prevents 'set -e' from triggering.❌