From bash, spawn two processes and exit both if either sibling exits
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
|
show 4 more comments
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
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 usekill -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 usekill -9
; you should sendSIGTERM
rather thanSIGKILL
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 withzsh
, notbash
.
– Stéphane Chazelas
Feb 2 '16 at 21:07
@StéphaneChazelas - seems like it should... Whatd i get wrong? I figured thewait
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
|
show 4 more comments
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
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
bash process
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 usekill -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 usekill -9
; you should sendSIGTERM
rather thanSIGKILL
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 withzsh
, notbash
.
– Stéphane Chazelas
Feb 2 '16 at 21:07
@StéphaneChazelas - seems like it should... Whatd i get wrong? I figured thewait
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
|
show 4 more comments
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 usekill -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 usekill -9
; you should sendSIGTERM
rather thanSIGKILL
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 withzsh
, notbash
.
– Stéphane Chazelas
Feb 2 '16 at 21:07
@StéphaneChazelas - seems like it should... Whatd i get wrong? I figured thewait
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
|
show 4 more comments
2 Answers
2
active
oldest
votes
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
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 atat
.
– mikeserv
Feb 2 '16 at 22:34
add a comment |
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
add a comment |
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
});
}
});
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%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
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
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 atat
.
– mikeserv
Feb 2 '16 at 22:34
add a comment |
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
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 atat
.
– mikeserv
Feb 2 '16 at 22:34
add a comment |
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
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
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 atat
.
– mikeserv
Feb 2 '16 at 22:34
add a comment |
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 atat
.
– 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
add a comment |
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
add a comment |
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
add a comment |
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
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
answered Feb 3 '16 at 4:23
mikeservmikeserv
45.9k668160
45.9k668160
add a comment |
add a comment |
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.
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%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
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
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 usekill -9
; you should sendSIGTERM
rather thanSIGKILL
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
, notbash
.– 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