The script below is a minimal (albeit artificial) illustration of the problem.
## demo.sh
exitfn () {
printf -- 'exitfn PID: %d\n' "$$" >&2
exit 1
}
printf -- 'script PID: %d\n' "$$" >&2
exitfn | :
printf -- 'SHOULD NEVER SEE THIS (0)\n' >&2
exitfn
printf -- 'SHOULD NEVER SEE THIS (1)\n' >&2
In this example script, exitfn
stands for a function whose job entails terminating the current process1.
Unfortunately, as implemented, exitfn
does not accomplish this mission reliably.
If one runs this script, the output looks like this:
% bash ./demo.sh
script PID: 26731
exitfn PID: 26731
SHOULD NEVER SEE THIS (0)
exitfn PID: 26731
(Of course, the value shown for PID will be different with each invocation.)
The key point here is that, on the first invocation of the exitfn
function, the exit 1
command in its body fails to terminate the execution of the enclosing script (as evidenced by the execution of the first printf
command immediately following). In contrast, on the second invocation of exitfn
, this exit 1
command does bring the script's execution to an end (as evidenced by the fact that the second printf
command is not executed).
The only difference between the two invocations of exitfn
is that the first one occurs as the first component of a two-component pipeline, while the second one is a "standalone" invocation.
I am puzzled by this. I had expected that exit
would have the effect of killing the current process (i.e. the one with PID given by $$
). Obviously, this is not always true.
Be that as it may, is there a way to write exitfn
so that it exits the surrounding script even when invoked within a pipeline?
Incidentally, the script above is also a valid zsh script, and produces the same result:
% zsh ./demo.sh
script PID: 26799
exitfn PID: 26799
SHOULD NEVER SEE THIS (0)
exitfn PID: 26799
I'd be interested in the answer to this question for zsh as well.
Finally, I should point out that implementing exitfn
like this does not work at all:
exitfn () {
printf -- 'exitfn PID: %d\n' "$$" >&2
exit 1
kill -9 "$$"
}
...because, under all circumstances, the exit 1
command is always the last line of that function that gets executed. (Replacing exit 1
with kill -9 $$
is not acceptable: I want to control the script's exit status, and its output to stderr.)
1In practice, such a function would perform other tasks, such as diagnostics logging, or cleanup operations, before terminating the current process.