1

So here is my requirement.

I am tailing a log file and grepping on it.
I want to get some context on every grep result..
But the context should be "till a pattern is matched" and not the number of lines (which is what grep -A/-B/-C offer).

For example
Say here is my log..

[l] is prefixed to every log line. Also there will be prefix of [logTypeA] or [logTypeB]

[l][logTypeA] - Log line 1
[l][logTypeB] - Log line 2 
[l][logTypeA] - Log line 3 
.... 

Random data about Log line 3
....

[l][logTypeB] - Log line 4

Now my if my tail command was tail -f log_file.log | grep "[logTypeA]", I'd get an output of

[l][logTypeA] - Log line 1
[l][logTypeA] - Log line 3 

But I need contextual information for my grep result, and that context is NOT some number of lines, but rather till a particular pattern is matched (in this case [l])..

In the example I want my grep result to be

[l][logTypeA] - Log line 1
[l][logTypeA] - Log line 3 
.... 

Random data about Log line 3
....

From here (How to show lines after each grep match until other specific match?), I tried sed command on my tail like

tail -f log_file.log | sed '/\[logTypeA\]/,/\[l\]/p'

But that doesn't seem to work.

Any ideas?

7
  • 1
    tail -f gives buffered output and can not play with "past" and "future" range Commented Jun 17, 2017 at 14:53
  • 1
    In broad terms, write a simple script (I suggest in Awk) which groups related lines into "records" and then prints those records which match a particular pattern. Your question is too generic for it to make sense to show you a working script, but this is an extremely common task with Awk. Spend 30 minutes on a basic tutorial if you are unfamiliar with it, and you should be all set to solve the problem yourself.
    – tripleee
    Commented Jun 17, 2017 at 17:04
  • @RomanPerekhrest what about tail -f with grep -A, which gives me 10 lines after.. How is my requirement different from this?
    – SatheeshJM
    Commented Jun 17, 2017 at 20:00
  • @tripleee Writing an awk script was my last resort.. Was just wondering if there was some existing command.
    – SatheeshJM
    Commented Jun 17, 2017 at 20:02
  • Your requirements are unclear. Your sed script looks like you want to print lines with [l] and all lines between them ... but without any further conditions, that's simply equivalent to all lines, to my understanding. I guess you want all [l] lines which match a particular additional pattern and all non-[l] lines after a match, but without further details, there are too many variables and guesses to write a useful script. It could probably done in sed instead if that's your preference, though Awk seems particularly suitable here.
    – tripleee
    Commented Jun 18, 2017 at 12:58

2 Answers 2

1

This is an extremely common pattern with Awk. Collect related lines into "records", print the record when you have collected all of it if it matches a particular condition.

tail -f file |
awk '/\[l]/ { if (p && stored) print stored; stored = ""; p=0 }
    /\[logTypeA]/ { p=1 }
    { stored = stored (stored ? ORS : "") $0 }
    END { if (p) print stored }'

The END condition doesn't really make sense with a never-ending stream from tail -f but I include it for good measure, and to avoid pesky test failures when the last record you want to test with should be printed, but won't be without the END clause.

1

I would probably end up doing this with a perl one-liner. You can do it in sed but it feels like it's beginning to hit up against the "unintentional turing-completeness" part of sed.

Perl one-liner (the perl -ne is really the take away here)

echo  '[l] boring\nboring data\n[l] boring\n[l]interesting\ndata\ndata\n[l]boring' |  perl -ne '
    if (/\[l\].*interesting/ ) { print $_; $collect=1 ; }
    elsif (/\[l\]/) {$collect=0 }
    elsif ($collect) {print $_}'

For reference, here is the sed one-liner that does the same thing. (Features of sed used: b to achieve a switch, T to achieve conditional execution).

echo  '[l] boring\nboring data\n[l] boring\n[l\]interesting\ndata\ndata\n[l]boring' |  sed -nE '/\[l\].*interesting/ {
p;
s/.*/collect/ ; x ; # store collect marker in the pattern space
b; # terminate processing
}

/\[l\]/ {
s/.*// ; x; # clear hold flag
b
}

/./ {
x;
s/collect/collect/;
T; x; p # print if we are collecting
}
'
4
  • The thing in the square brackets is a lowercase L, not the number 1.
    – tripleee
    Commented Jun 19, 2017 at 17:02
  • The Perl script assumes that the interesting string occurs on the same line as the [l]. I don't know if that covers the OP's use case; my Awk script copes with matches in the subsequent related lines, too. (It's not very hard to make the Perl script also support this, though.)
    – tripleee
    Commented Jun 19, 2017 at 17:05
  • @triplee good point. It makes one-liner a lot simpler though :).
    – Att Righ
    Commented Jun 19, 2017 at 21:38
  • I've fixed the 1 to be a lowercase L (though haven't confirmed that the code runs)
    – Att Righ
    Commented Jun 19, 2017 at 21:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .