For those who have a program that is continuously writing to stdout, all you need to do is pipe it to grep with the 'single match' option. Once grep finds the matching string, it will exit, which closes stdout on the process that is being piped to grep. This event should naturally cause the program to gracefully exit so long as the process writes again.
What will happen is that the process will receive a SIGPIPE when it tries writing to closed stdout after grep has exited. Here is an example with ping, which would otherwise run indefinitely:
$ ping superuser.com | grep -m 1 "icmp_seq"
This command will match the first successful 'pong', and then exit the next time ping
tries writing to stdout.
However,
It's not always guaranteed that the process will write to stdout again and therefore might not cause a SIGPIPE to be raised (e.g., this can happen when tailing a log file). The best solution i've managed to come up with for this scenario involves writing to a file; please comment if you think you can improve:
$ { tail -f log_file & echo $! > pid; } | { grep -m1 "find_me" && kill -9 $(cat pid) && rm pid; }
Breaking this down:
tail -f log_file & echo $! > pid
- tails a file, attaches process to background, and saves the PID ($!
) to a file. I tried exporting the PID to a variable instead, but it seems there's a race condition between here and when the PID is used again.
{ ... ;}
- group these commands together so we can pipe the output to grep while keeping the current context (helps when saving and reusing variables, but wasn't able to get that part working)
|
- pipe left side's stdout to right side's stdin
grep -m1 "find_me"
- find the target string
&& kill -9 $(cat pid)
- force kill (SIGKILL) the tail
process after grep
exits once it finds the matching string
&& rm pid
- remove the file we created
tail -f
a program output just as well as a file... Am I wrong?