3

I want Print nth line before the matched line, Matching line and nth line from the matched line where "n" is greater than 2.

Here's an example of my data file (the line numbers below are not part of the data and just for identification), The pattern that I am searching for is "blah", in the example.txt file.

$ cat example.txt 
 1. a
 2. b
 3. c
 4. d
 5. blah
 6. e
 7. f
 8. g
 9. h
 10. blah
 11. i
 12. f
 13. g
 14. h

And I want the Output as:

 1. b
 2. blah
 3. g
 4. f
 5. blah
 6. g

Please suggest any one liner!

  • What if the ranges overlap? (the 3rd line before the second match comes before or is the same as the 3rd line after the first match) – rici Oct 1 '13 at 16:25
  • Are the numbers really supposed to change like that? – Chris Down Oct 1 '13 at 16:38
  • @ChrisDown I guess the numbers are not part of the file but are just line numbers. – terdon Oct 1 '13 at 16:41
  • @terdon Well, the OP put cat ... in the original question to make it look like the verbatim output from a command, so I'm not totally sure here. If that wasn't there, I'd have more confidence about it. – Chris Down Oct 1 '13 at 16:41
  • @ChrisDown I'm not sure either, you're quite right, it's not clear. It's just the only reason I can think why the line numbers would change. – terdon Oct 1 '13 at 16:43
2

Here's a perl one-liner:

$ perl -ne '$n=3;push @lines,$_; END{for($i=0;$i<=$#lines;$i++){
  if ($lines[$i]=~/blah/){
    print $lines[$i-$n],$lines[$i],$lines[$i+$n]}}
 }' example.txt 
b
blah
g
f
blah
g

To change the number of surrounding lines, change $n=3; to $n=N where N is the desired number. To change the matched pattern, change if ($lines[$i]=~/blah/) to if ($lines[$i]=~/PATTERN/).

If the numbers are actually part of the file, you can do something like this:

$ perl -ne '$n=3;push @lines,$_; END{for($i=0;$i<=$#lines;$i++){
      if ($lines[$i]=~/blah/){
        print $lines[$i-$n],$lines[$i],$lines[$i+$n]}}
     }' example.txt | perl -pne 's/\d+/$./'
1. b
2. blah
3. g
4. f
5. blah
6. g
  • This works Perfectly. – Kumaran Oct 9 '13 at 7:03
  • Thanks @Terdon. For your wonderful sharing of knowledge. – Kumaran Oct 24 '16 at 17:59
4
awk -vn=3 '/blah/{print l[NR%n];print;p[NR+n]};(NR in p);{l[NR%n]=$0}'

That assumes there's no overlap. If there's overlap, all the relevant lines will be printed, but possibly several times and not necessarily in the same order as in the input.

To avoid that, you could write it instead like:

awk -vn=3 '/blah/{p[NR-n]p[NR]p[NR+n]};(NR-n in p){print l[NR%n]}
  {l[NR%n]=$0};END{for(i=NR-n+1;i<=NR;i++)if (i in p) print l[i%n]}'

On an input like:

1
2
3
4
blah1
5
6
blah2
blah3
7
8
9
10

The first one would give:

2
blah1
blah1
blah2
blah2
5
blah3
8
9

While the second would print:

2
blah1
5
blah2
blah3
8
9
  • Thanks for your reponse! can you please explain me how both the queries work! – Kumaran Oct 2 '13 at 11:05
  • Hi Stephane, Your answer worked out for me! Thanks!! Can you please tell me ,from where can I learn queries like the one that you have given above with examples, particularly "AWK" commands for text processing ..(using array's) – Kumaran Oct 3 '13 at 14:21
  • Thanks Everyone. For your wonderful sharing of knowledge. I glad to be part of this group. – Kumaran Oct 24 '16 at 17:56
2

Here's a similar answer to @terdon's, but it only keeps the 2n+1 relevant lines in memory:

my $n = shift;
my $pattern = shift;
my @lines = ("\n") x (2*$n+1);
while (<>) {
    shift @lines;
    push @lines, $_;
    if ($lines[$n] =~ m/$pattern/) {
        print $lines[0], $lines[$n], $lines[-1];
    }
}

And you'd run it like: perl example.pl 3 blah example.txt

  • Thanks Glenn. For your wonderful sharing of knowledge. I glad to be part of this group. – Kumaran Oct 24 '16 at 17:57
1
egrep -B 2 blah example.txt

grep is family:

1. grep 
2. egrep  # for regular expression
3. fgrep  # fast grep

The given option:

-B == Before 
-A == After 
-C == -B + -C

Of course, above options are gnu extension you don't have them in Solaris.

  • 3
    That was my first thought too but the OP wants only the n-2 line, grep will also output the n-1. – terdon Oct 1 '13 at 16:41
  • Thanks PersianGulf. For your wonderful sharing of knowledge. – Kumaran Oct 24 '16 at 17:58
0

Not terribly efficient. Grab line numbers using grep, print line numbers using sed.

for n in `grep -n blah example.txt | sed -e s/:.*//`
do
    sed -n -e "$[$n-3]p" -e "$[$n]p" -e "$[$n+3]p" example.txt
done

results in

 2. b
 5. blah
 8. g
 7. f
 10. blah
 13. g

It would probably fail if any of those numbers ended up being out of range.

  • Thanks @Frostschutz. For your wonderful sharing of knowledge. – Kumaran Oct 24 '16 at 17:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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