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.










share|improve this question




















  • 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















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.










share|improve this question




















  • 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













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.










share|improve this question















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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














  • 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










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.






share|improve this answer























    Your Answer








    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "106"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    convertImagesToLinks: false,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














     

    draft saved


    draft discarded


















    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

























    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.






    share|improve this answer



























      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.






      share|improve this answer

























        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.






        share|improve this answer















        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.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 23 at 22:24









        Stéphane Chazelas

        294k54555898




        294k54555898










        answered Nov 23 at 22:13









        Gilles

        522k12610401572




        522k12610401572






























             

            draft saved


            draft discarded



















































             


            draft saved


            draft discarded














            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





















































            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







            Popular posts from this blog

            サソリ

            広島県道265号伴広島線

            Accessing regular linux commands in Huawei's Dopra Linux