How to write a function that reliably exits (with a specified status) the current process?
up vote
1
down vote
favorite
The script below is a minimal (albeit artificial) illustration of the problem.
## demo.sh
exitfn () {
printf -- 'exitfn PID: %dn' "$$" >&2
exit 1
}
printf -- 'script PID: %dn' "$$" >&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: %dn' "$$" >&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.
bash shell-script scripting zsh
add a comment |
up vote
1
down vote
favorite
The script below is a minimal (albeit artificial) illustration of the problem.
## demo.sh
exitfn () {
printf -- 'exitfn PID: %dn' "$$" >&2
exit 1
}
printf -- 'script PID: %dn' "$$" >&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: %dn' "$$" >&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.
bash shell-script scripting zsh
1
Related, if not a dupe: exit shell script from a subshell
– Kusalananda
Nov 23 at 18:40
Also related: What is the differences between "kill -PIPE $$" vs. "exit 1"?
– Kusalananda
Nov 23 at 18:41
add a comment |
up vote
1
down vote
favorite
up vote
1
down vote
favorite
The script below is a minimal (albeit artificial) illustration of the problem.
## demo.sh
exitfn () {
printf -- 'exitfn PID: %dn' "$$" >&2
exit 1
}
printf -- 'script PID: %dn' "$$" >&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: %dn' "$$" >&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.
bash shell-script scripting zsh
The script below is a minimal (albeit artificial) illustration of the problem.
## demo.sh
exitfn () {
printf -- 'exitfn PID: %dn' "$$" >&2
exit 1
}
printf -- 'script PID: %dn' "$$" >&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: %dn' "$$" >&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.
bash shell-script scripting zsh
bash shell-script scripting zsh
edited Nov 23 at 19:19
asked Nov 23 at 18:37
kjo
4,01893663
4,01893663
1
Related, if not a dupe: exit shell script from a subshell
– Kusalananda
Nov 23 at 18:40
Also related: What is the differences between "kill -PIPE $$" vs. "exit 1"?
– Kusalananda
Nov 23 at 18:41
add a comment |
1
Related, if not a dupe: exit shell script from a subshell
– Kusalananda
Nov 23 at 18:40
Also related: What is the differences between "kill -PIPE $$" vs. "exit 1"?
– Kusalananda
Nov 23 at 18:41
1
1
Related, if not a dupe: exit shell script from a subshell
– Kusalananda
Nov 23 at 18:40
Related, if not a dupe: exit shell script from a subshell
– Kusalananda
Nov 23 at 18:40
Also related: What is the differences between "kill -PIPE $$" vs. "exit 1"?
– Kusalananda
Nov 23 at 18:41
Also related: What is the differences between "kill -PIPE $$" vs. "exit 1"?
– Kusalananda
Nov 23 at 18:41
add a comment |
1 Answer
1
active
oldest
votes
up vote
3
down vote
accepted
I had expected that
exit
would have the effect of killing the current process (i.e. the one with PID given by$$
)
“The current process” is not the same thing as “the process with PID given by $$
”. exit
exits the current subshell, or the original shell if it isn't called in a subshell.
Certain constructs, such as the content of ( … )
(grouping with subshell), command substitutions ($(…)
or `…`
), and each side of a pipe (or only the left-hand side in some shells), run in a subshell. A subshell behaves as if it was a separate process created with fork()
, and it usually is implemented this way (some shells don't use subprocesses in some circumstances, as a performance optimization). The subshell has its own copy of variables, its own redirections, etc. Calling exit
exits the subshell, just like the exit()
function in the standard C library exits the process. For more about subshells, see What is a subshell (in the context of the documentation of make)?, What is the exact difference between a "subshell" and a "child process"? and Is $() a subshell?.
$$
is always the process ID of the original shell process. It doesn't change in a subshell. Some shells have a variable that changes in a subshell, for example $BASHPID
in bash and mksh, or ${.sh.subshell}
in ksh or $ZSH_SUBSHELL
or $sysparams[pid]
in zsh¹.
is there a way to write
exitfn
so that it exits the surrounding script even when invoked within a pipeline?
Not exactly. You'll need to do more work, either at the point where the script creates subshell, or at the toplevel, or both. See exit shell script from a subshell for approximations.
¹ beware though that contrary to other shells, zsh
performs expansions in pipelines in the parent process (from left to right), not in each of the members of the pipelines: zsh -c 'echo $ZSH_SUBSHELL | cat'
outputs 0
(even though that echo
runs in a child process) and zsh -c 'n=0; echo $((++n)) | echo $((++n))'
outputs 2.
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
3
down vote
accepted
I had expected that
exit
would have the effect of killing the current process (i.e. the one with PID given by$$
)
“The current process” is not the same thing as “the process with PID given by $$
”. exit
exits the current subshell, or the original shell if it isn't called in a subshell.
Certain constructs, such as the content of ( … )
(grouping with subshell), command substitutions ($(…)
or `…`
), and each side of a pipe (or only the left-hand side in some shells), run in a subshell. A subshell behaves as if it was a separate process created with fork()
, and it usually is implemented this way (some shells don't use subprocesses in some circumstances, as a performance optimization). The subshell has its own copy of variables, its own redirections, etc. Calling exit
exits the subshell, just like the exit()
function in the standard C library exits the process. For more about subshells, see What is a subshell (in the context of the documentation of make)?, What is the exact difference between a "subshell" and a "child process"? and Is $() a subshell?.
$$
is always the process ID of the original shell process. It doesn't change in a subshell. Some shells have a variable that changes in a subshell, for example $BASHPID
in bash and mksh, or ${.sh.subshell}
in ksh or $ZSH_SUBSHELL
or $sysparams[pid]
in zsh¹.
is there a way to write
exitfn
so that it exits the surrounding script even when invoked within a pipeline?
Not exactly. You'll need to do more work, either at the point where the script creates subshell, or at the toplevel, or both. See exit shell script from a subshell for approximations.
¹ beware though that contrary to other shells, zsh
performs expansions in pipelines in the parent process (from left to right), not in each of the members of the pipelines: zsh -c 'echo $ZSH_SUBSHELL | cat'
outputs 0
(even though that echo
runs in a child process) and zsh -c 'n=0; echo $((++n)) | echo $((++n))'
outputs 2.
add a comment |
up vote
3
down vote
accepted
I had expected that
exit
would have the effect of killing the current process (i.e. the one with PID given by$$
)
“The current process” is not the same thing as “the process with PID given by $$
”. exit
exits the current subshell, or the original shell if it isn't called in a subshell.
Certain constructs, such as the content of ( … )
(grouping with subshell), command substitutions ($(…)
or `…`
), and each side of a pipe (or only the left-hand side in some shells), run in a subshell. A subshell behaves as if it was a separate process created with fork()
, and it usually is implemented this way (some shells don't use subprocesses in some circumstances, as a performance optimization). The subshell has its own copy of variables, its own redirections, etc. Calling exit
exits the subshell, just like the exit()
function in the standard C library exits the process. For more about subshells, see What is a subshell (in the context of the documentation of make)?, What is the exact difference between a "subshell" and a "child process"? and Is $() a subshell?.
$$
is always the process ID of the original shell process. It doesn't change in a subshell. Some shells have a variable that changes in a subshell, for example $BASHPID
in bash and mksh, or ${.sh.subshell}
in ksh or $ZSH_SUBSHELL
or $sysparams[pid]
in zsh¹.
is there a way to write
exitfn
so that it exits the surrounding script even when invoked within a pipeline?
Not exactly. You'll need to do more work, either at the point where the script creates subshell, or at the toplevel, or both. See exit shell script from a subshell for approximations.
¹ beware though that contrary to other shells, zsh
performs expansions in pipelines in the parent process (from left to right), not in each of the members of the pipelines: zsh -c 'echo $ZSH_SUBSHELL | cat'
outputs 0
(even though that echo
runs in a child process) and zsh -c 'n=0; echo $((++n)) | echo $((++n))'
outputs 2.
add a comment |
up vote
3
down vote
accepted
up vote
3
down vote
accepted
I had expected that
exit
would have the effect of killing the current process (i.e. the one with PID given by$$
)
“The current process” is not the same thing as “the process with PID given by $$
”. exit
exits the current subshell, or the original shell if it isn't called in a subshell.
Certain constructs, such as the content of ( … )
(grouping with subshell), command substitutions ($(…)
or `…`
), and each side of a pipe (or only the left-hand side in some shells), run in a subshell. A subshell behaves as if it was a separate process created with fork()
, and it usually is implemented this way (some shells don't use subprocesses in some circumstances, as a performance optimization). The subshell has its own copy of variables, its own redirections, etc. Calling exit
exits the subshell, just like the exit()
function in the standard C library exits the process. For more about subshells, see What is a subshell (in the context of the documentation of make)?, What is the exact difference between a "subshell" and a "child process"? and Is $() a subshell?.
$$
is always the process ID of the original shell process. It doesn't change in a subshell. Some shells have a variable that changes in a subshell, for example $BASHPID
in bash and mksh, or ${.sh.subshell}
in ksh or $ZSH_SUBSHELL
or $sysparams[pid]
in zsh¹.
is there a way to write
exitfn
so that it exits the surrounding script even when invoked within a pipeline?
Not exactly. You'll need to do more work, either at the point where the script creates subshell, or at the toplevel, or both. See exit shell script from a subshell for approximations.
¹ beware though that contrary to other shells, zsh
performs expansions in pipelines in the parent process (from left to right), not in each of the members of the pipelines: zsh -c 'echo $ZSH_SUBSHELL | cat'
outputs 0
(even though that echo
runs in a child process) and zsh -c 'n=0; echo $((++n)) | echo $((++n))'
outputs 2.
I had expected that
exit
would have the effect of killing the current process (i.e. the one with PID given by$$
)
“The current process” is not the same thing as “the process with PID given by $$
”. exit
exits the current subshell, or the original shell if it isn't called in a subshell.
Certain constructs, such as the content of ( … )
(grouping with subshell), command substitutions ($(…)
or `…`
), and each side of a pipe (or only the left-hand side in some shells), run in a subshell. A subshell behaves as if it was a separate process created with fork()
, and it usually is implemented this way (some shells don't use subprocesses in some circumstances, as a performance optimization). The subshell has its own copy of variables, its own redirections, etc. Calling exit
exits the subshell, just like the exit()
function in the standard C library exits the process. For more about subshells, see What is a subshell (in the context of the documentation of make)?, What is the exact difference between a "subshell" and a "child process"? and Is $() a subshell?.
$$
is always the process ID of the original shell process. It doesn't change in a subshell. Some shells have a variable that changes in a subshell, for example $BASHPID
in bash and mksh, or ${.sh.subshell}
in ksh or $ZSH_SUBSHELL
or $sysparams[pid]
in zsh¹.
is there a way to write
exitfn
so that it exits the surrounding script even when invoked within a pipeline?
Not exactly. You'll need to do more work, either at the point where the script creates subshell, or at the toplevel, or both. See exit shell script from a subshell for approximations.
¹ beware though that contrary to other shells, zsh
performs expansions in pipelines in the parent process (from left to right), not in each of the members of the pipelines: zsh -c 'echo $ZSH_SUBSHELL | cat'
outputs 0
(even though that echo
runs in a child process) and zsh -c 'n=0; echo $((++n)) | echo $((++n))'
outputs 2.
edited Nov 23 at 22:24


Stéphane Chazelas
294k54555898
294k54555898
answered Nov 23 at 22:13


Gilles
522k12610401572
522k12610401572
add a comment |
add a comment |
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f483744%2fhow-to-write-a-function-that-reliably-exits-with-a-specified-status-the-curren%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
Related, if not a dupe: exit shell script from a subshell
– Kusalananda
Nov 23 at 18:40
Also related: What is the differences between "kill -PIPE $$" vs. "exit 1"?
– Kusalananda
Nov 23 at 18:41