I need to start a process, lets say foo
. I would like to see the stdout/stderr as normal, but grep
the stderr for string bar
. Once
I actually managed to figure out a way to do this without PID files or co-routines and in a way that should work in all POSIX-compatible shells (I've tried bash
and dash
). At least on systems that support /dev/fd/
, but that should be pretty much all of them.
It is a bit convoluted, though, so I'm not sure if it is to your liking.
( # A
( # B
( /tmp/foo 2>&1 1>&3 & echo $! >&4 ) | # C
( tee /dev/fd/2 | ( grep -q bar && echo fin >&4 ) ) # D and E
) 4>&1 | ( # F
read CHILD
read STATUS
if [ "$STATUS" = fin ]; then
kill $CHILD
fi
)
) 3>&1
To explain the numerous subshells used herein:
The body of A
runs with the normal stdout duplicated to fd 3. It runs the subshells B
and F
with the stdout of B
piped to the stdin of F
.
The body of B
runs with the pipe from A
duplicated on fd 4.
C
runs your actual foo command, with its stderr connected to a pipe from C
to D
and its stdout duplicated from fd 3; that is, restored to the global stdout. It then writes the PID of foo
to fd 4; that is, to the pipe that subshell F
has on its stdin.
D
runs a tee
command receiving, from the pipe, whatever foo
prints on its stderr. It copies that output to both /dev/fd/2
(in order to have it displayed on the global stderr) and to a pipe connected to subshell E
.
E
greps for bar and then, when found, writes fin
on fd 4, that is, to the pipe that F
has on its stdin. Note the &&
, making sure that no fin
is written if grep encounters EOF without having found bar
.
F
, then, reads the PID from C
and the fin
terminator from E
. If the fin
terminator was properly output, it kills foo
.
EDIT: Fixed the missing tee
to copy foo's stderr to the real stderr.