Given the pipeline
a | b | c
how might I alter b
so that it aborts the pipeline if b
generates an error or matches a particular pattern in the input stream?
Given the pipeline
a | b | c
how might I alter b
so that it aborts the pipeline if b
generates an error or matches a particular pattern in the input stream?
@mosvy's very helpful answer was mostly correct, but has the problem that b()
always aborts the pipeline whether or not sed /die/q
encounters "die":
Input stream contains "die"
$ b(){ sed /die/q && kill "$BASHPID"; }; printf '%s\n' pass die oops | b | cat; echo "${PIPESTATUS[@]}"
pass
die
0 143 0
Input stream does not contain "die"
$ b(){ sed /die/q && kill "$BASHPID"; }; printf '%s\n' pass oops | b | cat; echo "${PIPESTATUS[@]}"
pass
oops
0 143 0
In @mosvy's version, b()
always aborts the pipeline because sed /die/q
returns exit code 0 (success) if it encounters "die" or reaches the end of the input stream and so b()
always invokes kill "$BASHPID"
.
In the following version, I correct @mosvy's answer so that b()
aborts the pipeline only when it encounters "die" in the input stream:
Input stream contains "die"
b() {
sed '/die/{q 2}' || kill "$BASHPID"
}
# Send "die" to b.
printf '%s\n' pass die oops | b | cat
echo "${PIPESTATUS[@]}"
Output:
pass
die
0 2 0
Input stream does not contain "die"
b() {
sed '/die/{q 2}' || kill "$BASHPID"
}
# Do not send "die" to b.
printf '%s\n' pass oops | b | cat
echo "${PIPESTATUS[@]}"
Output:
pass
oops
0 0 0
Note that in this version of b()
, if sed
encounters "die", it invokes command q 2
which causes sed
to terminate immediately with exit code 2 (failure), and then ||
to invoke kill "$BASHPID"
which terminates b()
's process in the pipeline and aborts the pipeline. (Note that this version requires GNU sed
which extends command q
so that it accepts an exit code.)
As @mosvy mentions, instead of committing "ritual suicide", b()
may simply exit
from the process:
b() {
sed '/die/{q 2}' || exit 3
}
# Send "die" to b.
printf '%s\n' pass die oops | b | cat
echo "${PIPESTATUS[@]}"
Output:
pass
die
0 3 0
b
terminate.
– Dec 09 '19 at 03:00a
will be killed by aSIGPIPE
signal when trying to write to the left pipe, andc
will get an EOF when trying to read from the right pipe. Inbash
(but not in the shell in general), you can get the exit status ofb
from thePIPESTATUS
array.b
terminate? – Derek Mahar Dec 09 '19 at 03:13exit
from within it. Or let it commit ritual suicide:b(){ sed /die/q && kill "$BASHPID"; }; printf '%s\n' pass die oops | b | cat; echo "${PIPESTATUS[@]}"
;-) – Dec 09 '19 at 03:30b()
aborts the pipeline using the ritual suicide operationkill "$BASHPID"
or withexit 1
. – Derek Mahar Dec 09 '19 at 04:25b
terminating would not terminate the pipeline in the (very degenerate and unlikely) case where there is no actual I/O between the processes in the pipeline. – Kusalananda Dec 09 '19 at 07:03cat | cat | pkill -g0 | cat | cat
will kill all 4 cats before them being killed bySIGPIPE
when trying to write to pipe with no reader, or exiting with status 0 because of EOF.ps -ho pgrp "$BASHPID"
will tell you the process group$BASHPID
is in. You can also get the same info directly from/proc/<pid>/stat{,us}
. – Dec 09 '19 at 15:41bash
will always use separate processes for(...)
subshells, andpkill
andpgrep
are able to find processes by their parent. – Dec 09 '19 at 15:42c
only if the input stream froma
does not contain "die"? I tried using an intermediate "sponge" that https://unix.stackexchange.com/questions/337055/a-program-that-could-buffer-stdin-or-file describes followingsed /die/{q 1} || pkill -g0
, but the pipeline is subject to race conditions where sometimes it terminates and discards the input stream while other timesc
receives some input. – Derek Mahar Dec 09 '19 at 22:47printf '%s\n' pass die oops | { file=$(mktemp); trap "rm $file" EXIT; sed '/die/{q 1}' > $file && cat $file || exit 2; } | cat; echo "${PIPESTATUS[@]}";
is an extension of your solution where nodeb
in the pipeline discards the entire input stream if it encounters string "die". – Derek Mahar Dec 10 '19 at 17:11printf '%s\n' pass die oops | { input=$(sed '/die/{q 1}') && echo "$input" || exit 2; } | cat; echo "${PIPESTATUS[@]}";
– Derek Mahar Dec 10 '19 at 17:24