Reputation: 1237
I'm starting 2 processes, where one pipes into the other and then background them both. I want to wait until the second process finishes to then decide the fate of the first one.
The following snippet gives the output I'm expecting ("exit 1"). However, wait
seems to wait for both processes to be finished (so 5 seconds), which is not what I want.
What have I misunderstood about wait
?
How can I make it wait for just the second process in the pipeline and decide to wait for the first one (or kill it) later?
#!/bin/sh
{
sleep 5
exit 5
} | {
sleep 1
exit 1
} &
wait $!
echo "exit $?"
Upvotes: 1
Views: 4042
Reputation: 531165
When you write {...} | {...} &
, there are at least three processes involved:
|
|
&
is a list terminator. It does not apply to the previous simple or compound command, or even the previous pipeline. This entire command is executed in a single background process:
a | b || c | d && e | f &
&
does not apply only to f
, or e | f
, or even c | d && e | f
. It applies to the entire sequence of pipelines.
@KamilCuk mentions named pipes. I would prefer them over process substitution because it allows you to treat both processes uniformly, at least as far as syntax is concerned. (Also, named pipes work in any POSIX-compliant shell, not just bash
.)
mk_fifo p1
{ sleep 5; exit 5; } > p1 & p1_pid=$!
{ sleep 1; exit 1; } < p1 & p2_pid=$!
wait $p2_pid
# Now, kill or wait for the first process as desired
Upvotes: 2
Reputation: 2562
You don't have misunderstood the wait
builtin behavior. You have misunderstood the shell pipe processing instead.
The wait
command behaves as expected, it waits for the end of the subshell from the right part of the pipe (the sleep 1
) and it waits for five seconds because this right part doesn't effectively terminate until its pipe input is closed. Its pipe input is closed only when the left part of the pipe closes it on its termination (the sleep 5
), thus after five seconds.
To wait for the right part of the pipe, you can use signal to know when the right part is about to finish like that :
#!/bin/dash
{
sleep 5
exit 5
} | {
sleep 1
kill -s USR1 $$
exit 1
} &
trap "echo \"Second shell finished\";" USR1
wait $!
echo "exit $?"
This way, you'll get a signal from the right part and the wait
command will then only wait until it get these signal (the wait
command wait for any signal state change, not only termination). The kill -s USR1 $$
send the USR1 signal to the parent shell. Beware that it can imply termination for the process of the current subshell, you have to know how the process will handle USR1 (i guess sleep
ignores it).
However, you can't then get the right exit code from the shell of the right part of the pipe, as it is not effectively terminated until its pipe input is closed. To be more precise, you'll get here the exit status 138 which is the the signal status USR1 (value 10) plus the POSIX signal base (128), not the subshell exit status. You can use different signals (USR1, USR2 (POSIX), non POSIX: .. USR64) to signify an exit status or use the subshell STDOUT or an environment variable to transmit an exit status instead if required.
Upvotes: 3
Reputation: 141000
Instead of using a pipe, you can create fifo
s and control each process yourself.
The easiest would be just to use process substitution.
{
cmd2
} < <(cmd1) &
Or in the same fashion, using coproc
ess for the first command if explicit lifetime control isneeded.
Another solution would be to make interprocess communication between the shell in the pipeline and parent shell, be it a signal or just a file, or pid
could be sent to parent process so that parent can while kill -0
on just the pid of last pipeline.
: > file
... | {
sleep 1
echo end > file
} &
while [[ $(cat file) != "end" ]]; do
sleep 0.1
done
Upvotes: 1