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 bar
is found in the
stderr foo
should be killed.
Is this possible?
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 bar
is found in the
stderr foo
should be killed.
Is this possible?
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
anddash
). 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.
To explain the numerous subshells used herein:
The body of
A
runs with the normal stdout duplicated to fd 3. It runs the subshellsB
andF
with the stdout ofB
piped to the stdin ofF
.The body of
B
runs with the pipe fromA
duplicated on fd 4.C
runs your actual foo command, with its stderr connected to a pipe fromC
toD
and its stdout duplicated from fd 3; that is, restored to the global stdout. It then writes the PID offoo
to fd 4; that is, to the pipe that subshellF
has on its stdin.D
runs atee
command receiving, from the pipe, whateverfoo
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 subshellE
.E
greps for bar and then, when found, writesfin
on fd 4, that is, to the pipe thatF
has on its stdin. Note the&&
, making sure that nofin
is written if grep encounters EOF without having foundbar
.F
, then, reads the PID fromC
and thefin
terminator fromE
. If thefin
terminator was properly output, it killsfoo
.EDIT: Fixed the missing
tee
to copy foo's stderr to the real stderr.I initially wrote a way to do this that involved stream swizzling, but it wasn't very good. Some of the comments relate to that version. Check the history if you're curious.
Here's a way to do this:
Good old-fashioned nightmare fuel. What's happening here?
mktemp
, and call itPIDFILE
PIDFILE
, again for hygeinefoo
; this is done in a compound statement so that&
binds tofoo
and not to the whole preceding pipelinefoo
's standard error into a process substitution which waits forbar
to appear and then killsfoo
(of which more later)foo
's PID into a variable, write it to the file named byPIDFILE
, then wait for it, so that the whole command waits forfoo
to exit before itself exiting; the|| true
discards the error exit status offoo
when that happens.The code inside the process substitution works as follows:
tee
the input (foo
's standard error), redirectingtee
's standard output to standard error, so thatfoo
's standard error does indeed appear on standard errorgrep -q
on the input, which looks for the specified pattern, and exits as soon as it finds it (or when it reaches the end of the stream), without printing anything, after which (if it found the string and exited successfully) the shell goes on to ...kill
the process whose PID is captured in the file named byPIDFILE
, namelyfoo
Tom Anderson’s answer is quite good, but the
kill $(cat $PIDFILE)
will only happen on my system iffoo
terminated on its own, or through Ctrl-C. The following solution works for meUse Expect to Monitor Standard Error
Expect is designed for taking actions based on output from a process. The simplest solution is to simply let Expect start the process, then exit when it sees the expected output. For example:
If the spawned process ends normally (e.g. before "foo" is seen), then the Expect script will exit, too.
Just as an alternative to the other answer, one way would be to use bash's
coproc
facility:That's clearly bash-specific, though.