Phil Poore
Phil Poore

Reputation: 2256

'tail -f' doesn't give single lines when piped through grep'

I am launching a website, and I wanted to setup a Bash one-liner so when someone hits the site it would make a beep using the internal buzzer.

So far it's working using the following.

tail -f access_log | while read x ; do echo -ne '\007' $x '\n' ; done

Tail follows the access_log and dumps to STDOUT, get STDOUT line at a time, echo the line with '\007' "internal beep hex code", and done...

This works like a beauty... Every hit shows the line from the log and beeps... However, it got annoying very quickly, so ideally I wanted to filter the tail -f /access/log before it's piped into the while so that read only gets lines I care about. I was thinking grep "/index.php" would be a good indication of visitors...

This is where the issue is...

I can do...

tail -f access_log | while read x ; do echo -ne '\007' $x '\n' ; done

beeps on everything and i can do...

tail -f access_log | grep "/index.php"

and pages are shown with no beep, but when i do

tail -f access_log | grep "/index.php" | while read x ; do echo -ne '\007' $x '\n' ; done

Nothing happens, no line from log, no beep.

I think the grep is messing it up somewhere, but I can't figure out where. I'd love it to be a one liner, and I know it should really be done in a script and would be easier, but it doesn't explain why the above, which I think should work, isn't.

Upvotes: 8

Views: 1644

Answers (3)

F. Hauri  - Give Up GitHub
F. Hauri - Give Up GitHub

Reputation: 70812

Using sed -u for unbuffered:

Lighter than awk and grep, using sed could be simple, quick and efficient:

tail -f access.log | sed -une "s@/index.php@&\o7@p"

sed will replace /index.php by found string & plus beep: \o7, then print lines where something was replaced. With -u, sed will read lines by lines, unbuffered.

path='/index.php'
tail -f access.log | sed -une "s@${path}@&\o7@p"

Upvotes: 0

John Kugelman
John Kugelman

Reputation: 361625

Grep's output is buffered when it's used in a pipe. Use --line-buffered to force it to use line buffering so it outputs lines immediately.

tail -f access_log | grep --line-buffered "/index.php" | while read x ; do echo -ne '\007' $x '\n' ; done

You could also combine the grep and while loop into a single awk call:

tail -f access_log | awk '/\/index.php/ { print "\007" $0 }'

Upvotes: 15

cdhowie
cdhowie

Reputation: 169018

Grep buffers output when standard output is not a terminal. You need to pass the --line-buffered switch to grep to force it to flush standard output whenever it writes a line.

Upvotes: 7

Related Questions