From bash, spawn two processes and exit both if either sibling exits












5















From bash, I am spawning two processes. These two processes depend on each other. I want both to exit if either one dies. What is the cleanest way to do that? Currently I have the following:



# start process a
/bin/program_a;
a_pid=$!

# start process b
/bin/program_b;
b_pid=$!

# kill process b if process a exits
wait $a_pid
echo "a_pid died, killing process b"
kill -9 $b_pid


But this only helps process b exit if process a dies. How to I also make process a exit if process b dies?










share|improve this question























  • a third process checking the alive status of the mentioned 2 processes and killing the remaining process if one of them go away for any reason. You can build this function into both programs and have them poll each other and die gracefully if the other one disappears, but this is something your programs will have to do, while doing their actual jobs. Hence my suggestion about a separate process

    – MelBurslan
    Feb 2 '16 at 20:29











  • You can use kill -0 $PID to determine if a process is still alive. Knowing this, you can then: while /bin/true; do if ! kill -0 $pid_a; then kill -9 $pid_b; exit; fi; elif ! kill -0 $pid_b; then kill -9 $pid_a; exit; fi; done. That said, you really should not ever have to use kill -9; you should send SIGTERM rather than SIGKILL to allow the process to clean up after itself.

    – DopeGhoti
    Feb 2 '16 at 20:46













  • Use SIGCHLD. trap '"$@"' CHLD; job1&set kill "$!" && job2 &set "$@" "$!"; wait

    – mikeserv
    Feb 2 '16 at 20:47











  • @mikeserv, that would work with zsh, not bash.

    – Stéphane Chazelas
    Feb 2 '16 at 21:07











  • @StéphaneChazelas - seems like it should... Whatd i get wrong? I figured the wait was enough to hold it over to get both... Just cuffed it from a phone in a waiting room though... And if you say so i believe it... I am curious about why though...

    – mikeserv
    Feb 2 '16 at 21:09


















5















From bash, I am spawning two processes. These two processes depend on each other. I want both to exit if either one dies. What is the cleanest way to do that? Currently I have the following:



# start process a
/bin/program_a;
a_pid=$!

# start process b
/bin/program_b;
b_pid=$!

# kill process b if process a exits
wait $a_pid
echo "a_pid died, killing process b"
kill -9 $b_pid


But this only helps process b exit if process a dies. How to I also make process a exit if process b dies?










share|improve this question























  • a third process checking the alive status of the mentioned 2 processes and killing the remaining process if one of them go away for any reason. You can build this function into both programs and have them poll each other and die gracefully if the other one disappears, but this is something your programs will have to do, while doing their actual jobs. Hence my suggestion about a separate process

    – MelBurslan
    Feb 2 '16 at 20:29











  • You can use kill -0 $PID to determine if a process is still alive. Knowing this, you can then: while /bin/true; do if ! kill -0 $pid_a; then kill -9 $pid_b; exit; fi; elif ! kill -0 $pid_b; then kill -9 $pid_a; exit; fi; done. That said, you really should not ever have to use kill -9; you should send SIGTERM rather than SIGKILL to allow the process to clean up after itself.

    – DopeGhoti
    Feb 2 '16 at 20:46













  • Use SIGCHLD. trap '"$@"' CHLD; job1&set kill "$!" && job2 &set "$@" "$!"; wait

    – mikeserv
    Feb 2 '16 at 20:47











  • @mikeserv, that would work with zsh, not bash.

    – Stéphane Chazelas
    Feb 2 '16 at 21:07











  • @StéphaneChazelas - seems like it should... Whatd i get wrong? I figured the wait was enough to hold it over to get both... Just cuffed it from a phone in a waiting room though... And if you say so i believe it... I am curious about why though...

    – mikeserv
    Feb 2 '16 at 21:09
















5












5








5


2






From bash, I am spawning two processes. These two processes depend on each other. I want both to exit if either one dies. What is the cleanest way to do that? Currently I have the following:



# start process a
/bin/program_a;
a_pid=$!

# start process b
/bin/program_b;
b_pid=$!

# kill process b if process a exits
wait $a_pid
echo "a_pid died, killing process b"
kill -9 $b_pid


But this only helps process b exit if process a dies. How to I also make process a exit if process b dies?










share|improve this question














From bash, I am spawning two processes. These two processes depend on each other. I want both to exit if either one dies. What is the cleanest way to do that? Currently I have the following:



# start process a
/bin/program_a;
a_pid=$!

# start process b
/bin/program_b;
b_pid=$!

# kill process b if process a exits
wait $a_pid
echo "a_pid died, killing process b"
kill -9 $b_pid


But this only helps process b exit if process a dies. How to I also make process a exit if process b dies?







bash process






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Feb 2 '16 at 20:18









peskalpeskal

24719




24719













  • a third process checking the alive status of the mentioned 2 processes and killing the remaining process if one of them go away for any reason. You can build this function into both programs and have them poll each other and die gracefully if the other one disappears, but this is something your programs will have to do, while doing their actual jobs. Hence my suggestion about a separate process

    – MelBurslan
    Feb 2 '16 at 20:29











  • You can use kill -0 $PID to determine if a process is still alive. Knowing this, you can then: while /bin/true; do if ! kill -0 $pid_a; then kill -9 $pid_b; exit; fi; elif ! kill -0 $pid_b; then kill -9 $pid_a; exit; fi; done. That said, you really should not ever have to use kill -9; you should send SIGTERM rather than SIGKILL to allow the process to clean up after itself.

    – DopeGhoti
    Feb 2 '16 at 20:46













  • Use SIGCHLD. trap '"$@"' CHLD; job1&set kill "$!" && job2 &set "$@" "$!"; wait

    – mikeserv
    Feb 2 '16 at 20:47











  • @mikeserv, that would work with zsh, not bash.

    – Stéphane Chazelas
    Feb 2 '16 at 21:07











  • @StéphaneChazelas - seems like it should... Whatd i get wrong? I figured the wait was enough to hold it over to get both... Just cuffed it from a phone in a waiting room though... And if you say so i believe it... I am curious about why though...

    – mikeserv
    Feb 2 '16 at 21:09





















  • a third process checking the alive status of the mentioned 2 processes and killing the remaining process if one of them go away for any reason. You can build this function into both programs and have them poll each other and die gracefully if the other one disappears, but this is something your programs will have to do, while doing their actual jobs. Hence my suggestion about a separate process

    – MelBurslan
    Feb 2 '16 at 20:29











  • You can use kill -0 $PID to determine if a process is still alive. Knowing this, you can then: while /bin/true; do if ! kill -0 $pid_a; then kill -9 $pid_b; exit; fi; elif ! kill -0 $pid_b; then kill -9 $pid_a; exit; fi; done. That said, you really should not ever have to use kill -9; you should send SIGTERM rather than SIGKILL to allow the process to clean up after itself.

    – DopeGhoti
    Feb 2 '16 at 20:46













  • Use SIGCHLD. trap '"$@"' CHLD; job1&set kill "$!" && job2 &set "$@" "$!"; wait

    – mikeserv
    Feb 2 '16 at 20:47











  • @mikeserv, that would work with zsh, not bash.

    – Stéphane Chazelas
    Feb 2 '16 at 21:07











  • @StéphaneChazelas - seems like it should... Whatd i get wrong? I figured the wait was enough to hold it over to get both... Just cuffed it from a phone in a waiting room though... And if you say so i believe it... I am curious about why though...

    – mikeserv
    Feb 2 '16 at 21:09



















a third process checking the alive status of the mentioned 2 processes and killing the remaining process if one of them go away for any reason. You can build this function into both programs and have them poll each other and die gracefully if the other one disappears, but this is something your programs will have to do, while doing their actual jobs. Hence my suggestion about a separate process

– MelBurslan
Feb 2 '16 at 20:29





a third process checking the alive status of the mentioned 2 processes and killing the remaining process if one of them go away for any reason. You can build this function into both programs and have them poll each other and die gracefully if the other one disappears, but this is something your programs will have to do, while doing their actual jobs. Hence my suggestion about a separate process

– MelBurslan
Feb 2 '16 at 20:29













You can use kill -0 $PID to determine if a process is still alive. Knowing this, you can then: while /bin/true; do if ! kill -0 $pid_a; then kill -9 $pid_b; exit; fi; elif ! kill -0 $pid_b; then kill -9 $pid_a; exit; fi; done. That said, you really should not ever have to use kill -9; you should send SIGTERM rather than SIGKILL to allow the process to clean up after itself.

– DopeGhoti
Feb 2 '16 at 20:46







You can use kill -0 $PID to determine if a process is still alive. Knowing this, you can then: while /bin/true; do if ! kill -0 $pid_a; then kill -9 $pid_b; exit; fi; elif ! kill -0 $pid_b; then kill -9 $pid_a; exit; fi; done. That said, you really should not ever have to use kill -9; you should send SIGTERM rather than SIGKILL to allow the process to clean up after itself.

– DopeGhoti
Feb 2 '16 at 20:46















Use SIGCHLD. trap '"$@"' CHLD; job1&set kill "$!" && job2 &set "$@" "$!"; wait

– mikeserv
Feb 2 '16 at 20:47





Use SIGCHLD. trap '"$@"' CHLD; job1&set kill "$!" && job2 &set "$@" "$!"; wait

– mikeserv
Feb 2 '16 at 20:47













@mikeserv, that would work with zsh, not bash.

– Stéphane Chazelas
Feb 2 '16 at 21:07





@mikeserv, that would work with zsh, not bash.

– Stéphane Chazelas
Feb 2 '16 at 21:07













@StéphaneChazelas - seems like it should... Whatd i get wrong? I figured the wait was enough to hold it over to get both... Just cuffed it from a phone in a waiting room though... And if you say so i believe it... I am curious about why though...

– mikeserv
Feb 2 '16 at 21:09







@StéphaneChazelas - seems like it should... Whatd i get wrong? I figured the wait was enough to hold it over to get both... Just cuffed it from a phone in a waiting room though... And if you say so i believe it... I am curious about why though...

– mikeserv
Feb 2 '16 at 21:09












2 Answers
2






active

oldest

votes


















8














With zsh:



pids=()
trap '
trap - CHLD
(($#pids)) && kill $pids 2> /dev/null
' CHLD

sleep 2 & pids+=$!
sleep 1 & pids+=$!
sleep 3 & pids+=$!

wait


(here using sleep as test commands).



With bash it would seem the CHLD trap is only run when the m option is on. You don't want to start your jobs under that option though as that would run them in separate process groups. Also note that resetting the handler within the handler doesn't seem to work with bash. So the bash equivalent would be something like:



pids=()
gotsigchld=false
trap '
if ! "$gotsigchld"; then
gotsigchld=true
((${#pids[@]})) && kill "${pids[@]}" 2> /dev/null
fi
' CHLD

sleep 2 & pids+=("$!")
sleep 1 & pids+=("$!")
sleep 3 & pids+=("$!")

set -m
wait
set +m





share|improve this answer


























  • I was hesitant to use job control after reading stackoverflow.com/questions/690266/…, but this solution of turning job control on and off around the wait is perfect

    – peskal
    Feb 2 '16 at 21:46











  • @pesckal - you only need to worry about if your script ought to be backgroundable for the most part. If so, look at at.

    – mikeserv
    Feb 2 '16 at 22:34



















0














Of those I tested, and as near as I can tell, three shells do pretty much the right thing with regards to SIGCHLD and wait: yash, dash, and mksh. You see, wait is supposed to be interruptible; when setting up a signal handler you need that handler either to be doing a wait(), a sleep(), or a read() portably (though apparently sleep() might behave strangely if the interruption comes of a previous call to alarm()). Any (not blocked/ignored) signal should stop a wait().



The shell implementations of such things shouldn't differ terribly in my opinion, but... some do. Particularly bash behaves the worst of any of bash, ksh93, dash, mksh, yash, or zsh. zsh and ksh93 almost get the following sequence right, but they fail to preserve the exit status of the first process to exit. It's not terrible - though zsh does also complain about being asked to wait on the most recently exited pid anyway.



Here's what I did:



unset IFS
script=$(cat <<""
PS4="$0 + "
trap ' for p ### loop over bgd pids
do shift ### clear current pid
if kill -0 "$p" 2>/dev/null ### still running?
then set -- "$@" "$p" ### then append again
else wait "$p" ### else get return
exit "$(kill "$@")$?" ### kill others; exit
fi
done' CHLD ### wait til CHLD
for n in $(shuf -i 3-7) ### randomize order
do (sleep "$n";exit "$n")& set "$@" "$!" ### sleep 3 exits 3
done; set -x; wait ### debug, wait

)


The above should work not only to kill all remaining backgrounded children of a shell as soon as one returns, but also to propagate the first returned child's exit code to that of the parent shell. It should work because wait should return immediately with a backgrounded process's exit status if called for a child process which has not yet been waited upon. And because the SIGCHLD is what terminates the first wait the second wait should mark the first time the first returned child is actually waited. At least, simply put it should be. The more complicated the shell implementation, though, the less reliable such logic proves to be, it would seem.



That is the $script each of the shells ran when I did...



for sh in yash zsh ksh bash mksh dash
do time "$sh" +m -c "$script" ### no job control
done


bash is the only shell which does not exit within three seconds. zsh and ksh93 both (in my opinion, incorrectly) exit 0, but otherwise do quit within three seconds. The others exit 3 within 3 seconds. Here are the test results:



yash + wait
yash + shift
yash + wait 19111
yash + kill 19112 19113 19116 19117
yash + exit 3

real 0m3.013s
user 0m0.007s
sys 0m0.000s

zsh + wait
zsh + p=19124
zsh + shift
zsh + kill -0 19124
zsh + set -- 19125 19127 19129 19132 19124
zsh + p=19125
zsh + shift
zsh + kill -0 19125
zsh + wait 19125
zsh:wait:12: pid 19125 is not a child of this shell
zsh + kill 19127 19129 19132 19124
zsh + exit 0

real 0m3.023s
user 0m0.017s
sys 0m0.000s

ksh + wait
ksh + shift
ksh + kill -0 19137
ksh + 2> /dev/null
ksh + set -- 19138 19139 19140 19141 19137
ksh + shift
ksh + kill -0 19138
ksh + 2> /dev/null
ksh + wait 19138
ksh + kill 19139 19140 19141 19137
ksh + exit 0

real 0m3.018s
user 0m0.000s
sys 0m0.010s

bash + wait

real 0m7.018s
user 0m0.007s
sys 0m0.007s

mksh + wait
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19157
mksh + set -- 19158 19159 19160 19161 19157
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19158
mksh + set -- 19159 19160 19161 19157 19158
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19159
mksh + set -- 19160 19161 19157 19158 19159
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19160
mksh + set -- 19161 19157 19158 19159 19160
mksh + shift
mksh + 2>/dev/null
mksh + kill -0 19161
mksh + wait 19161
mksh + kill 19157 19158 19159 19160
mksh + exit 3

real 0m3.022s
user 0m0.003s
sys 0m0.000s

dash + wait
dash + shift
dash + kill -0 19165
dash + set -- 19166 19168 19170 19173 19165
dash + shift
dash + kill -0 19166
dash + wait 19166
dash + kill 19168 19170 19173 19165
dash + exit 3

real 0m3.008s
user 0m0.000s
sys 0m0.000s





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',
    autoActivateHeartbeat: false,
    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%2f259413%2ffrom-bash-spawn-two-processes-and-exit-both-if-either-sibling-exits%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    8














    With zsh:



    pids=()
    trap '
    trap - CHLD
    (($#pids)) && kill $pids 2> /dev/null
    ' CHLD

    sleep 2 & pids+=$!
    sleep 1 & pids+=$!
    sleep 3 & pids+=$!

    wait


    (here using sleep as test commands).



    With bash it would seem the CHLD trap is only run when the m option is on. You don't want to start your jobs under that option though as that would run them in separate process groups. Also note that resetting the handler within the handler doesn't seem to work with bash. So the bash equivalent would be something like:



    pids=()
    gotsigchld=false
    trap '
    if ! "$gotsigchld"; then
    gotsigchld=true
    ((${#pids[@]})) && kill "${pids[@]}" 2> /dev/null
    fi
    ' CHLD

    sleep 2 & pids+=("$!")
    sleep 1 & pids+=("$!")
    sleep 3 & pids+=("$!")

    set -m
    wait
    set +m





    share|improve this answer


























    • I was hesitant to use job control after reading stackoverflow.com/questions/690266/…, but this solution of turning job control on and off around the wait is perfect

      – peskal
      Feb 2 '16 at 21:46











    • @pesckal - you only need to worry about if your script ought to be backgroundable for the most part. If so, look at at.

      – mikeserv
      Feb 2 '16 at 22:34
















    8














    With zsh:



    pids=()
    trap '
    trap - CHLD
    (($#pids)) && kill $pids 2> /dev/null
    ' CHLD

    sleep 2 & pids+=$!
    sleep 1 & pids+=$!
    sleep 3 & pids+=$!

    wait


    (here using sleep as test commands).



    With bash it would seem the CHLD trap is only run when the m option is on. You don't want to start your jobs under that option though as that would run them in separate process groups. Also note that resetting the handler within the handler doesn't seem to work with bash. So the bash equivalent would be something like:



    pids=()
    gotsigchld=false
    trap '
    if ! "$gotsigchld"; then
    gotsigchld=true
    ((${#pids[@]})) && kill "${pids[@]}" 2> /dev/null
    fi
    ' CHLD

    sleep 2 & pids+=("$!")
    sleep 1 & pids+=("$!")
    sleep 3 & pids+=("$!")

    set -m
    wait
    set +m





    share|improve this answer


























    • I was hesitant to use job control after reading stackoverflow.com/questions/690266/…, but this solution of turning job control on and off around the wait is perfect

      – peskal
      Feb 2 '16 at 21:46











    • @pesckal - you only need to worry about if your script ought to be backgroundable for the most part. If so, look at at.

      – mikeserv
      Feb 2 '16 at 22:34














    8












    8








    8







    With zsh:



    pids=()
    trap '
    trap - CHLD
    (($#pids)) && kill $pids 2> /dev/null
    ' CHLD

    sleep 2 & pids+=$!
    sleep 1 & pids+=$!
    sleep 3 & pids+=$!

    wait


    (here using sleep as test commands).



    With bash it would seem the CHLD trap is only run when the m option is on. You don't want to start your jobs under that option though as that would run them in separate process groups. Also note that resetting the handler within the handler doesn't seem to work with bash. So the bash equivalent would be something like:



    pids=()
    gotsigchld=false
    trap '
    if ! "$gotsigchld"; then
    gotsigchld=true
    ((${#pids[@]})) && kill "${pids[@]}" 2> /dev/null
    fi
    ' CHLD

    sleep 2 & pids+=("$!")
    sleep 1 & pids+=("$!")
    sleep 3 & pids+=("$!")

    set -m
    wait
    set +m





    share|improve this answer















    With zsh:



    pids=()
    trap '
    trap - CHLD
    (($#pids)) && kill $pids 2> /dev/null
    ' CHLD

    sleep 2 & pids+=$!
    sleep 1 & pids+=$!
    sleep 3 & pids+=$!

    wait


    (here using sleep as test commands).



    With bash it would seem the CHLD trap is only run when the m option is on. You don't want to start your jobs under that option though as that would run them in separate process groups. Also note that resetting the handler within the handler doesn't seem to work with bash. So the bash equivalent would be something like:



    pids=()
    gotsigchld=false
    trap '
    if ! "$gotsigchld"; then
    gotsigchld=true
    ((${#pids[@]})) && kill "${pids[@]}" 2> /dev/null
    fi
    ' CHLD

    sleep 2 & pids+=("$!")
    sleep 1 & pids+=("$!")
    sleep 3 & pids+=("$!")

    set -m
    wait
    set +m






    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited 7 hours ago

























    answered Feb 2 '16 at 21:31









    Stéphane ChazelasStéphane Chazelas

    309k57582942




    309k57582942













    • I was hesitant to use job control after reading stackoverflow.com/questions/690266/…, but this solution of turning job control on and off around the wait is perfect

      – peskal
      Feb 2 '16 at 21:46











    • @pesckal - you only need to worry about if your script ought to be backgroundable for the most part. If so, look at at.

      – mikeserv
      Feb 2 '16 at 22:34



















    • I was hesitant to use job control after reading stackoverflow.com/questions/690266/…, but this solution of turning job control on and off around the wait is perfect

      – peskal
      Feb 2 '16 at 21:46











    • @pesckal - you only need to worry about if your script ought to be backgroundable for the most part. If so, look at at.

      – mikeserv
      Feb 2 '16 at 22:34

















    I was hesitant to use job control after reading stackoverflow.com/questions/690266/…, but this solution of turning job control on and off around the wait is perfect

    – peskal
    Feb 2 '16 at 21:46





    I was hesitant to use job control after reading stackoverflow.com/questions/690266/…, but this solution of turning job control on and off around the wait is perfect

    – peskal
    Feb 2 '16 at 21:46













    @pesckal - you only need to worry about if your script ought to be backgroundable for the most part. If so, look at at.

    – mikeserv
    Feb 2 '16 at 22:34





    @pesckal - you only need to worry about if your script ought to be backgroundable for the most part. If so, look at at.

    – mikeserv
    Feb 2 '16 at 22:34













    0














    Of those I tested, and as near as I can tell, three shells do pretty much the right thing with regards to SIGCHLD and wait: yash, dash, and mksh. You see, wait is supposed to be interruptible; when setting up a signal handler you need that handler either to be doing a wait(), a sleep(), or a read() portably (though apparently sleep() might behave strangely if the interruption comes of a previous call to alarm()). Any (not blocked/ignored) signal should stop a wait().



    The shell implementations of such things shouldn't differ terribly in my opinion, but... some do. Particularly bash behaves the worst of any of bash, ksh93, dash, mksh, yash, or zsh. zsh and ksh93 almost get the following sequence right, but they fail to preserve the exit status of the first process to exit. It's not terrible - though zsh does also complain about being asked to wait on the most recently exited pid anyway.



    Here's what I did:



    unset IFS
    script=$(cat <<""
    PS4="$0 + "
    trap ' for p ### loop over bgd pids
    do shift ### clear current pid
    if kill -0 "$p" 2>/dev/null ### still running?
    then set -- "$@" "$p" ### then append again
    else wait "$p" ### else get return
    exit "$(kill "$@")$?" ### kill others; exit
    fi
    done' CHLD ### wait til CHLD
    for n in $(shuf -i 3-7) ### randomize order
    do (sleep "$n";exit "$n")& set "$@" "$!" ### sleep 3 exits 3
    done; set -x; wait ### debug, wait

    )


    The above should work not only to kill all remaining backgrounded children of a shell as soon as one returns, but also to propagate the first returned child's exit code to that of the parent shell. It should work because wait should return immediately with a backgrounded process's exit status if called for a child process which has not yet been waited upon. And because the SIGCHLD is what terminates the first wait the second wait should mark the first time the first returned child is actually waited. At least, simply put it should be. The more complicated the shell implementation, though, the less reliable such logic proves to be, it would seem.



    That is the $script each of the shells ran when I did...



    for sh in yash zsh ksh bash mksh dash
    do time "$sh" +m -c "$script" ### no job control
    done


    bash is the only shell which does not exit within three seconds. zsh and ksh93 both (in my opinion, incorrectly) exit 0, but otherwise do quit within three seconds. The others exit 3 within 3 seconds. Here are the test results:



    yash + wait
    yash + shift
    yash + wait 19111
    yash + kill 19112 19113 19116 19117
    yash + exit 3

    real 0m3.013s
    user 0m0.007s
    sys 0m0.000s

    zsh + wait
    zsh + p=19124
    zsh + shift
    zsh + kill -0 19124
    zsh + set -- 19125 19127 19129 19132 19124
    zsh + p=19125
    zsh + shift
    zsh + kill -0 19125
    zsh + wait 19125
    zsh:wait:12: pid 19125 is not a child of this shell
    zsh + kill 19127 19129 19132 19124
    zsh + exit 0

    real 0m3.023s
    user 0m0.017s
    sys 0m0.000s

    ksh + wait
    ksh + shift
    ksh + kill -0 19137
    ksh + 2> /dev/null
    ksh + set -- 19138 19139 19140 19141 19137
    ksh + shift
    ksh + kill -0 19138
    ksh + 2> /dev/null
    ksh + wait 19138
    ksh + kill 19139 19140 19141 19137
    ksh + exit 0

    real 0m3.018s
    user 0m0.000s
    sys 0m0.010s

    bash + wait

    real 0m7.018s
    user 0m0.007s
    sys 0m0.007s

    mksh + wait
    mksh + shift
    mksh + 2>/dev/null
    mksh + kill -0 19157
    mksh + set -- 19158 19159 19160 19161 19157
    mksh + shift
    mksh + 2>/dev/null
    mksh + kill -0 19158
    mksh + set -- 19159 19160 19161 19157 19158
    mksh + shift
    mksh + 2>/dev/null
    mksh + kill -0 19159
    mksh + set -- 19160 19161 19157 19158 19159
    mksh + shift
    mksh + 2>/dev/null
    mksh + kill -0 19160
    mksh + set -- 19161 19157 19158 19159 19160
    mksh + shift
    mksh + 2>/dev/null
    mksh + kill -0 19161
    mksh + wait 19161
    mksh + kill 19157 19158 19159 19160
    mksh + exit 3

    real 0m3.022s
    user 0m0.003s
    sys 0m0.000s

    dash + wait
    dash + shift
    dash + kill -0 19165
    dash + set -- 19166 19168 19170 19173 19165
    dash + shift
    dash + kill -0 19166
    dash + wait 19166
    dash + kill 19168 19170 19173 19165
    dash + exit 3

    real 0m3.008s
    user 0m0.000s
    sys 0m0.000s





    share|improve this answer




























      0














      Of those I tested, and as near as I can tell, three shells do pretty much the right thing with regards to SIGCHLD and wait: yash, dash, and mksh. You see, wait is supposed to be interruptible; when setting up a signal handler you need that handler either to be doing a wait(), a sleep(), or a read() portably (though apparently sleep() might behave strangely if the interruption comes of a previous call to alarm()). Any (not blocked/ignored) signal should stop a wait().



      The shell implementations of such things shouldn't differ terribly in my opinion, but... some do. Particularly bash behaves the worst of any of bash, ksh93, dash, mksh, yash, or zsh. zsh and ksh93 almost get the following sequence right, but they fail to preserve the exit status of the first process to exit. It's not terrible - though zsh does also complain about being asked to wait on the most recently exited pid anyway.



      Here's what I did:



      unset IFS
      script=$(cat <<""
      PS4="$0 + "
      trap ' for p ### loop over bgd pids
      do shift ### clear current pid
      if kill -0 "$p" 2>/dev/null ### still running?
      then set -- "$@" "$p" ### then append again
      else wait "$p" ### else get return
      exit "$(kill "$@")$?" ### kill others; exit
      fi
      done' CHLD ### wait til CHLD
      for n in $(shuf -i 3-7) ### randomize order
      do (sleep "$n";exit "$n")& set "$@" "$!" ### sleep 3 exits 3
      done; set -x; wait ### debug, wait

      )


      The above should work not only to kill all remaining backgrounded children of a shell as soon as one returns, but also to propagate the first returned child's exit code to that of the parent shell. It should work because wait should return immediately with a backgrounded process's exit status if called for a child process which has not yet been waited upon. And because the SIGCHLD is what terminates the first wait the second wait should mark the first time the first returned child is actually waited. At least, simply put it should be. The more complicated the shell implementation, though, the less reliable such logic proves to be, it would seem.



      That is the $script each of the shells ran when I did...



      for sh in yash zsh ksh bash mksh dash
      do time "$sh" +m -c "$script" ### no job control
      done


      bash is the only shell which does not exit within three seconds. zsh and ksh93 both (in my opinion, incorrectly) exit 0, but otherwise do quit within three seconds. The others exit 3 within 3 seconds. Here are the test results:



      yash + wait
      yash + shift
      yash + wait 19111
      yash + kill 19112 19113 19116 19117
      yash + exit 3

      real 0m3.013s
      user 0m0.007s
      sys 0m0.000s

      zsh + wait
      zsh + p=19124
      zsh + shift
      zsh + kill -0 19124
      zsh + set -- 19125 19127 19129 19132 19124
      zsh + p=19125
      zsh + shift
      zsh + kill -0 19125
      zsh + wait 19125
      zsh:wait:12: pid 19125 is not a child of this shell
      zsh + kill 19127 19129 19132 19124
      zsh + exit 0

      real 0m3.023s
      user 0m0.017s
      sys 0m0.000s

      ksh + wait
      ksh + shift
      ksh + kill -0 19137
      ksh + 2> /dev/null
      ksh + set -- 19138 19139 19140 19141 19137
      ksh + shift
      ksh + kill -0 19138
      ksh + 2> /dev/null
      ksh + wait 19138
      ksh + kill 19139 19140 19141 19137
      ksh + exit 0

      real 0m3.018s
      user 0m0.000s
      sys 0m0.010s

      bash + wait

      real 0m7.018s
      user 0m0.007s
      sys 0m0.007s

      mksh + wait
      mksh + shift
      mksh + 2>/dev/null
      mksh + kill -0 19157
      mksh + set -- 19158 19159 19160 19161 19157
      mksh + shift
      mksh + 2>/dev/null
      mksh + kill -0 19158
      mksh + set -- 19159 19160 19161 19157 19158
      mksh + shift
      mksh + 2>/dev/null
      mksh + kill -0 19159
      mksh + set -- 19160 19161 19157 19158 19159
      mksh + shift
      mksh + 2>/dev/null
      mksh + kill -0 19160
      mksh + set -- 19161 19157 19158 19159 19160
      mksh + shift
      mksh + 2>/dev/null
      mksh + kill -0 19161
      mksh + wait 19161
      mksh + kill 19157 19158 19159 19160
      mksh + exit 3

      real 0m3.022s
      user 0m0.003s
      sys 0m0.000s

      dash + wait
      dash + shift
      dash + kill -0 19165
      dash + set -- 19166 19168 19170 19173 19165
      dash + shift
      dash + kill -0 19166
      dash + wait 19166
      dash + kill 19168 19170 19173 19165
      dash + exit 3

      real 0m3.008s
      user 0m0.000s
      sys 0m0.000s





      share|improve this answer


























        0












        0








        0







        Of those I tested, and as near as I can tell, three shells do pretty much the right thing with regards to SIGCHLD and wait: yash, dash, and mksh. You see, wait is supposed to be interruptible; when setting up a signal handler you need that handler either to be doing a wait(), a sleep(), or a read() portably (though apparently sleep() might behave strangely if the interruption comes of a previous call to alarm()). Any (not blocked/ignored) signal should stop a wait().



        The shell implementations of such things shouldn't differ terribly in my opinion, but... some do. Particularly bash behaves the worst of any of bash, ksh93, dash, mksh, yash, or zsh. zsh and ksh93 almost get the following sequence right, but they fail to preserve the exit status of the first process to exit. It's not terrible - though zsh does also complain about being asked to wait on the most recently exited pid anyway.



        Here's what I did:



        unset IFS
        script=$(cat <<""
        PS4="$0 + "
        trap ' for p ### loop over bgd pids
        do shift ### clear current pid
        if kill -0 "$p" 2>/dev/null ### still running?
        then set -- "$@" "$p" ### then append again
        else wait "$p" ### else get return
        exit "$(kill "$@")$?" ### kill others; exit
        fi
        done' CHLD ### wait til CHLD
        for n in $(shuf -i 3-7) ### randomize order
        do (sleep "$n";exit "$n")& set "$@" "$!" ### sleep 3 exits 3
        done; set -x; wait ### debug, wait

        )


        The above should work not only to kill all remaining backgrounded children of a shell as soon as one returns, but also to propagate the first returned child's exit code to that of the parent shell. It should work because wait should return immediately with a backgrounded process's exit status if called for a child process which has not yet been waited upon. And because the SIGCHLD is what terminates the first wait the second wait should mark the first time the first returned child is actually waited. At least, simply put it should be. The more complicated the shell implementation, though, the less reliable such logic proves to be, it would seem.



        That is the $script each of the shells ran when I did...



        for sh in yash zsh ksh bash mksh dash
        do time "$sh" +m -c "$script" ### no job control
        done


        bash is the only shell which does not exit within three seconds. zsh and ksh93 both (in my opinion, incorrectly) exit 0, but otherwise do quit within three seconds. The others exit 3 within 3 seconds. Here are the test results:



        yash + wait
        yash + shift
        yash + wait 19111
        yash + kill 19112 19113 19116 19117
        yash + exit 3

        real 0m3.013s
        user 0m0.007s
        sys 0m0.000s

        zsh + wait
        zsh + p=19124
        zsh + shift
        zsh + kill -0 19124
        zsh + set -- 19125 19127 19129 19132 19124
        zsh + p=19125
        zsh + shift
        zsh + kill -0 19125
        zsh + wait 19125
        zsh:wait:12: pid 19125 is not a child of this shell
        zsh + kill 19127 19129 19132 19124
        zsh + exit 0

        real 0m3.023s
        user 0m0.017s
        sys 0m0.000s

        ksh + wait
        ksh + shift
        ksh + kill -0 19137
        ksh + 2> /dev/null
        ksh + set -- 19138 19139 19140 19141 19137
        ksh + shift
        ksh + kill -0 19138
        ksh + 2> /dev/null
        ksh + wait 19138
        ksh + kill 19139 19140 19141 19137
        ksh + exit 0

        real 0m3.018s
        user 0m0.000s
        sys 0m0.010s

        bash + wait

        real 0m7.018s
        user 0m0.007s
        sys 0m0.007s

        mksh + wait
        mksh + shift
        mksh + 2>/dev/null
        mksh + kill -0 19157
        mksh + set -- 19158 19159 19160 19161 19157
        mksh + shift
        mksh + 2>/dev/null
        mksh + kill -0 19158
        mksh + set -- 19159 19160 19161 19157 19158
        mksh + shift
        mksh + 2>/dev/null
        mksh + kill -0 19159
        mksh + set -- 19160 19161 19157 19158 19159
        mksh + shift
        mksh + 2>/dev/null
        mksh + kill -0 19160
        mksh + set -- 19161 19157 19158 19159 19160
        mksh + shift
        mksh + 2>/dev/null
        mksh + kill -0 19161
        mksh + wait 19161
        mksh + kill 19157 19158 19159 19160
        mksh + exit 3

        real 0m3.022s
        user 0m0.003s
        sys 0m0.000s

        dash + wait
        dash + shift
        dash + kill -0 19165
        dash + set -- 19166 19168 19170 19173 19165
        dash + shift
        dash + kill -0 19166
        dash + wait 19166
        dash + kill 19168 19170 19173 19165
        dash + exit 3

        real 0m3.008s
        user 0m0.000s
        sys 0m0.000s





        share|improve this answer













        Of those I tested, and as near as I can tell, three shells do pretty much the right thing with regards to SIGCHLD and wait: yash, dash, and mksh. You see, wait is supposed to be interruptible; when setting up a signal handler you need that handler either to be doing a wait(), a sleep(), or a read() portably (though apparently sleep() might behave strangely if the interruption comes of a previous call to alarm()). Any (not blocked/ignored) signal should stop a wait().



        The shell implementations of such things shouldn't differ terribly in my opinion, but... some do. Particularly bash behaves the worst of any of bash, ksh93, dash, mksh, yash, or zsh. zsh and ksh93 almost get the following sequence right, but they fail to preserve the exit status of the first process to exit. It's not terrible - though zsh does also complain about being asked to wait on the most recently exited pid anyway.



        Here's what I did:



        unset IFS
        script=$(cat <<""
        PS4="$0 + "
        trap ' for p ### loop over bgd pids
        do shift ### clear current pid
        if kill -0 "$p" 2>/dev/null ### still running?
        then set -- "$@" "$p" ### then append again
        else wait "$p" ### else get return
        exit "$(kill "$@")$?" ### kill others; exit
        fi
        done' CHLD ### wait til CHLD
        for n in $(shuf -i 3-7) ### randomize order
        do (sleep "$n";exit "$n")& set "$@" "$!" ### sleep 3 exits 3
        done; set -x; wait ### debug, wait

        )


        The above should work not only to kill all remaining backgrounded children of a shell as soon as one returns, but also to propagate the first returned child's exit code to that of the parent shell. It should work because wait should return immediately with a backgrounded process's exit status if called for a child process which has not yet been waited upon. And because the SIGCHLD is what terminates the first wait the second wait should mark the first time the first returned child is actually waited. At least, simply put it should be. The more complicated the shell implementation, though, the less reliable such logic proves to be, it would seem.



        That is the $script each of the shells ran when I did...



        for sh in yash zsh ksh bash mksh dash
        do time "$sh" +m -c "$script" ### no job control
        done


        bash is the only shell which does not exit within three seconds. zsh and ksh93 both (in my opinion, incorrectly) exit 0, but otherwise do quit within three seconds. The others exit 3 within 3 seconds. Here are the test results:



        yash + wait
        yash + shift
        yash + wait 19111
        yash + kill 19112 19113 19116 19117
        yash + exit 3

        real 0m3.013s
        user 0m0.007s
        sys 0m0.000s

        zsh + wait
        zsh + p=19124
        zsh + shift
        zsh + kill -0 19124
        zsh + set -- 19125 19127 19129 19132 19124
        zsh + p=19125
        zsh + shift
        zsh + kill -0 19125
        zsh + wait 19125
        zsh:wait:12: pid 19125 is not a child of this shell
        zsh + kill 19127 19129 19132 19124
        zsh + exit 0

        real 0m3.023s
        user 0m0.017s
        sys 0m0.000s

        ksh + wait
        ksh + shift
        ksh + kill -0 19137
        ksh + 2> /dev/null
        ksh + set -- 19138 19139 19140 19141 19137
        ksh + shift
        ksh + kill -0 19138
        ksh + 2> /dev/null
        ksh + wait 19138
        ksh + kill 19139 19140 19141 19137
        ksh + exit 0

        real 0m3.018s
        user 0m0.000s
        sys 0m0.010s

        bash + wait

        real 0m7.018s
        user 0m0.007s
        sys 0m0.007s

        mksh + wait
        mksh + shift
        mksh + 2>/dev/null
        mksh + kill -0 19157
        mksh + set -- 19158 19159 19160 19161 19157
        mksh + shift
        mksh + 2>/dev/null
        mksh + kill -0 19158
        mksh + set -- 19159 19160 19161 19157 19158
        mksh + shift
        mksh + 2>/dev/null
        mksh + kill -0 19159
        mksh + set -- 19160 19161 19157 19158 19159
        mksh + shift
        mksh + 2>/dev/null
        mksh + kill -0 19160
        mksh + set -- 19161 19157 19158 19159 19160
        mksh + shift
        mksh + 2>/dev/null
        mksh + kill -0 19161
        mksh + wait 19161
        mksh + kill 19157 19158 19159 19160
        mksh + exit 3

        real 0m3.022s
        user 0m0.003s
        sys 0m0.000s

        dash + wait
        dash + shift
        dash + kill -0 19165
        dash + set -- 19166 19168 19170 19173 19165
        dash + shift
        dash + kill -0 19166
        dash + wait 19166
        dash + kill 19168 19170 19173 19165
        dash + exit 3

        real 0m3.008s
        user 0m0.000s
        sys 0m0.000s






        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Feb 3 '16 at 4:23









        mikeservmikeserv

        45.9k668160




        45.9k668160






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Unix & Linux Stack Exchange!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f259413%2ffrom-bash-spawn-two-processes-and-exit-both-if-either-sibling-exits%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

            Loup dans la culture

            ASUS Zenbook UX433/UX333 — Configure Touchpad-embedded numpad on Linux

            How to solve the problem of ntp “Unable to contact time server” from KDE?