Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

I would like to follow changes to a file via tail -f and then print out the first line that matches a grep command before terminating. What's the simplest way to do this? So far I've been experimenting with things like:

tail -f foo.log | grep 'bar' | head -1

However, the pipeline just hangs, presumably due to buffering. I also tried

tail -f foo.log | grep --line-buffered 'bar' | head -1

This prints out the line, but the process does not terminate unless I hit ^C, presumably because a second line of input is needed to terminate head -1. What's the best way to solve this problem?

share|improve this question
add comment

5 Answers

up vote 6 down vote accepted
tail -f foo.log | grep -m 1 bar

if the file foo.log is writen rarily to, you can do:

grep -m 1 bar <( tail -f foo.log )

It should be noted that the tail -f will stay in background until it will get another line to output. If this takes long time it might be problematic. solution in such case is:

grep -m 1 bar <( exec tail -f foo.log ); kill $! 2> /dev/null

kill will kill leftover tail -f process, and we hide errors, because it's possible that the tail will be gone by the time kill will be invoked.

share|improve this answer
    
Doesn't work for me. –  PriceChild Apr 27 '11 at 10:43
    
What do you mean "doesn't work for me". Is there any error message? Anything happens? Does your grep support -m option? –  user7385 Apr 27 '11 at 10:45
    
Bah sorry I was too late on the edit. It doesn't terminate for me. –  PriceChild Apr 27 '11 at 10:52
    
This means that either the grepped string is not there, or the pipe to grep is buffered. test with this: perl -e '$|++; my $i = "a"; while (1) { print $i, "\n"; $i++; select undef, undef, undef, 0.1}' | grep -m 1 aa - every 0.1 second it prints next string, first "a", then "b", ... "z", "aa", "ab" ... - grep finishes after "aa", so it should be ~ 2.7 second after start. and it works. the problem might be if you have something else between tail -f .. and | grep - like another grep, sed or something like this. –  user7385 Apr 27 '11 at 11:23
    
Oh... I've been testing on auth.log, grepping for 'a'. If there's an initial match it seems its not terminating until the file gets a little more appended to.. –  PriceChild Apr 27 '11 at 11:32
show 5 more comments

tail -f waiting for SIGPIPE, which, as already pointed out by depesz, can only be triggered by another write of tail -f to a closed pipe after a successful grep -m 1 match, can be avoided if the tail -f command gets backgrounded and its background pid being sent to the grep subshell, which in turn implements a trap on exit that kills tail -f explicitly.

#(tail -f foo.log & echo ${!} ) |
(tail -f foo.log & echo ${!} && wait ) | 
   (trap 'kill "$tailpid"; trap - EXIT' EXIT; tailpid="$(head -1)"; grep -m 1 bar)
share|improve this answer
add comment

The <() construct works in bash only. My solution is:

sleep $timeout &
pid=$!
tail -n +0 --pid=$pid -F "$file" | ( grep -q -m 1 "$regexp" && kill -PIPE $pid )
wait $pid

Besides it's working in a POSIX shell, one other advantage is that the waiting period can be limited with a timeout value. Note that the result code of this scriptlet is reversed: 0 means the timeout was reached.

share|improve this answer
    
Having read all the answers I think this answer needs to be highlighted a bit. Although the -m is has its uses with grep it's the --pid flag for tail that I use ten times more often. It is a very good tool for cases like these. –  Tommi Kyntola May 26 at 7:30
add comment

Building on answer http://superuser.com/a/275962/37154 , I realized that if the tail would start too late, some instance of the indicated string may have gone by already. The solution is to make tail list the whole file.

grep -m 1 bar <( exec tail -n +1 -f foo.log ); kill $!
share|improve this answer
add comment

I think that applying unbuffer like this,

tail -f foo.log | unbuffer -p grep 'bar' | head -1

would work. I'm not at a system where I can test it, though, and I can't justify why it would work while grep --line-buffered does not. However, I did try this alternative which does seem to work:

tail -f foo.log | sed -n '/bar/{p;q}'

When the match to bar is found, p prints it and q immediately quits.

share|improve this answer
    
Neither work for me. (They don't terminate) –  PriceChild Apr 27 '11 at 10:50
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.