ChatGPT解决这个技术问题 Extra ChatGPT

How to show only next line after the matched one?

grep -A1 'blah' logfile

Thanks to this command for every line that has 'blah' in it, I get the output of the line that contains 'blah' and the next line that follows in the logfile. It might be a simple one but I can't find a way to omit the line that has 'blah' and only show next line in the output.

I think many people will come here looking for the -A1 option
Then I use this to get my public IP. :) curl whatismyip.org | grep -A1 'Your IP Address'
Similarly -B1, -B2, -B3, -A1, -A2, -A3 . . .
@shrek curl icanhazip.com (no grep needed) :)
Your question answered my question ... -A1. Thanks!

M
Michał Šrajer

you can try with awk:

awk '/blah/{getline; print}' logfile

For anyone who really wanted a grep -A2 equivalent (which is what I needed), getline just eats the line and goes on to the next. So what worked for me was literally just awk '/blah/{getline; getline; print}' logfile
I loved this getline solution.
WARNING: Assumes you do not get two matching lines next to each other
m
mias

If you want to stick to grep:

grep -A1 'blah' logfile | grep -v "blah"

or alternatively with sed:

sed -n '/blah/{n;p;}' logfile

@Kent, thanks for the tip. From my POV though, grep is much more readable and easy to grasp compared to sed or the awk answer marked as best answer....but it's just me maybe :)
For those curious about what -v does: -v --invert-match Invert the sense of matching, to select non-matching lines. (-v is specified by POSIX.)
I really like this answer, but it won't work if you have two lines in a row containing "blah" and want to get the second one (because it's "the next line after the matched one"). I can't think of any use case for that off the top of my head, but it's worth noting.
The top solution is merely "ok". If the second line happens to have "blah" in it as well, you'll have a real mess on your hands.
WARNING: Assumes you do not get two matching lines next to each other. The "grep will then have extra "--" lines, while the "sed" skips lines. BUT I find this "sed" solution the most useful when that is not the case.
m
martemiev

Piping is your friend...

Use grep -A1 to show the next line after a match, then pipe the result to tail and only grab 1 line,

cat logs/info.log | grep "term" -A1 | tail -n 1

If "term" is on the last line, this will return the line containing "term" instead of nothing, what you would want.
tail -n 1 will only result last line of whole file
s
souter

Great answer from raim, was very useful for me. It is trivial to extend this to print e.g. line 7 after the pattern

awk -v lines=7 '/blah/ {for(i=lines;i;--i)getline; print $0 }' logfile

C
Community

Many good answers have been given to this question so far, but I still miss one with awk not using getline. Since, in general, it is not necessary to use getline, I would go for:

awk ' f && NR==f+1; /blah/ {f=NR}' file  #all matches after "blah"

or

awk '/blah/ {f=NR} f && NR==f+1' file   #matches after "blah" not being also "blah"

The logic always consists in storing the line where "blah" is found and then printing those lines that are one line after.

Test

Sample file:

$ cat a
0
blah1
1
2
3
blah2
4
5
6
blah3
blah4
7

Get all the lines after "blah". This prints another "blah" if it appears after the first one.

$ awk 'f&&NR==f+1; /blah/ {f=NR}' a
1
4
blah4
7

Get all the lines after "blah" if they do not contain "blah" themselves.

$ awk '/blah/ {f=NR} f && NR==f+1' a
1
4
7

This is a good solution that handles consecutive matching lines, in the two possible ways... Up voted for completeness!
j
jww

If that next lines never contain 'blah', you can filter them with:

grep -A1 blah logfile | grep -v blah

The use of cat logfile | ... is not needed.


If you don't like "cat" but want the filename at the start of a complex pipeline, try this <logfile grep -A1 blah | grep -v blah It is perfectly valid BASH!
T
Travis Griggs

In general, I agree you're asking a lot of grep here, and that another tool may be the better solution. But in an embedded environment, I may not want to have sed or awk just to do this. I found the following solution works (as long as they're not contiguous matches):

grep -A1 AT\+CSQ wvdial.out | grep -v AT\+CSQ

Basically, match them, appending 1 line of context for each match, and then pipe that through an inverse match of your original pattern to strip those out. This of course means you can assume that your pattern doesn't show up in the "next" line.


WARNING: It also outputs the '--' seperators on multiple matches.
j
jww

I don't know of any way to do this with grep, but it is possible to use awk to achieve the same result:

awk '/blah/ {getline;print}' < logfile

@jww There is no difference. As you can see from the timestamps that are just minutes apart, it appears that I answered around the same time.
f
filimonov

perl one-liner alert

just for fun... print only one line after match

perl -lne '$. == $next && print; $next = ($.+1) if /match/' data.txt

even more fun... print the next ten lines after match

perl -lne 'push @nexts, (($.+1)..($.+10)) if /match/; $. ~~ @nexts && print' data.txt

kinda cheating though since there's actually two commands


Can you explain $.==$next? Clearly it's more than a numeric comparison of the current line number with $next, but I don't understand how/why it works.
It's not any more than that. $. == $next && print is the same as print if $. == $next
Ah, I see now. Thx! Of the 2 parts, each is true and executes on separate, adjacent iterations of the loop. I suppose if the point was to print any line after any match that you would want to reverse the order (would behave more like grep -A1). If you want to print only next lines but never a line with a match, then you would keep it this order. (just noting how $next would be clobbered for consecutive matches)
s
slm

It looks like you're using the wrong tool there. Grep isn't that sophisticated, I think you want to step up to awk as the tool for the job:

awk '/blah/ { getline; print $0 }' logfile

If you get any problems let me know, I think its well worth learning a bit of awk, its a great tool :)

p.s. This example doesn't win a 'useless use of cat award' ;) http://porkmail.org/era/unix/award.html


BTW, you can skip "$0" in print.
l
lisrael1

you can use grep, then take lines in jumps:
grep -A1 'blah' logfile | awk 'NR%3==2'

you can also take n lines after match, for example:
seq 100 | grep -A3 .2 | awk 'NR%5==4'
15
25
35
45
55
65
75
85
95
explanation -
here we want to grep all lines that are *2 and take 3 lines after it, which is *5.
seq 100 | grep -A3 .2 will give you:
12
13
14
15
--
22
23
24
25
--
...
the number in the modulo (NR%5) is the added rows by grep (here it's 3 by the flag -A3), +2 extra lines because you have current matching line and also the -- line that the grep is adding.


A
Amrit Pal Singh

grep /Pattern/ | tail -n 2 | head -n 1

Tail first 2 and then head last one to get exactly first line after match.


This only handles one match
d
dagelf

You can match multiple lines and output n lines after with:

awk 'c&&!--c;/match1/{c=1};/match2/{c=2}' file

Match multiple items and output n lines after:

#!/bin/sh
[ -z $3 ] && echo $0 file \[match n\]+... \# to output line n after match && exit
f=$1; shift; while [ \! -z $2 ]; do s="$s/$1/{c=$2};"; shift; shift; done
awk 'c&&!--c;'$s $f

So you can save the above as matchn.sh and chmod +x matchn.sh and then if you have a file like this:

line1
line2 
line3
line4
line5
line6
line7

And run:

./matchn.sh file line1 1 line3 2 line6 1

You will get:

line2
line5
line7

This will need a bit more tweaking to support 0 offsets.