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
bterminate.
– Dec 09 '19 at 03:00awill be killed by aSIGPIPEsignal when trying to write to the left pipe, andcwill 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 ofbfrom thePIPESTATUSarray.bterminate? – Derek Mahar Dec 09 '19 at 03:13exitfrom 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:25bterminating 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 | catwill kill all 4 cats before them being killed bySIGPIPEwhen 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$BASHPIDis in. You can also get the same info directly from/proc/<pid>/stat{,us}. – Dec 09 '19 at 15:41bashwill always use separate processes for(...)subshells, andpkillandpgrepare able to find processes by their parent. – Dec 09 '19 at 15:42conly if the input stream fromadoes 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 timescreceives 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 nodebin 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