Printing with sed or awk a line following a matching pattern
AwkSedAwk Problem Overview
Question: I'd like to print a single line directly following a line that contains a matching pattern.
My version of sed
will not take the following syntax (it bombs out on +1p
) which would seem like a simple solution:
sed -n '/ABC/,+1p' infile
I assume awk
would be better to do multiline processing, but I am not sure how to do it.
Awk Solutions
Solution 1 - Awk
Never use the word "pattern" as is it highly ambiguous. Always use "string" or "regexp" (or in shell "globbing pattern"), whichever it is you really mean.
The specific answer you want is:
awk 'f{print;f=0} /regexp/{f=1}' file
or specializing the more general solution of the Nth record after a regexp (idiom "c" below):
awk 'c&&!--c; /regexp/{c=1}' file
The following idioms describe how to select a range of records given a specific regexp to match:
a) Print all records from some regexp:
awk '/regexp/{f=1}f' file
b) Print all records after some regexp:
awk 'f;/regexp/{f=1}' file
c) Print the Nth record after some regexp:
awk 'c&&!--c;/regexp/{c=N}' file
d) Print every record except the Nth record after some regexp:
awk 'c&&!--c{next}/regexp/{c=N}1' file
e) Print the N records after some regexp:
awk 'c&&c--;/regexp/{c=N}' file
f) Print every record except the N records after some regexp:
awk 'c&&c--{next}/regexp/{c=N}1' file
g) Print the N records from some regexp:
awk '/regexp/{c=N}c&&c--' file
I changed the variable name from "f" for "found" to "c" for "count" where appropriate as that's more expressive of what the variable actually IS.
f
is short for found
. Its a boolean flag that I'm setting to 1 (true) when I find a string matching the regular expression regexp in the input (/regexp/{f=1}
). The other place you see f
on it's own in each script it's being tested as a condition and when true causes awk to execute its default action of printing the current record. So input records only get output after we see regexp and set f
to 1/true.
c && c-- { foo }
means "if c
is non-zero then decrement it and if it's still non-zero then execute foo
" so if c
starts at 3 then it'll be decremented to 2 and then foo
executed, and on the next input line c
is now 2 so it'll be decremented to 1 and then foo
executed again, and on the next input line c
is now 1 so it'll be decremented to 0 but this time foo
will not be executed because 0 is a false condition. We do c && c--
instead of just testing for c-- > 0
so we can't run into a case with a huge input file where c
hits zero and continues getting decremented so often it wraps around and becomes positive again.
Solution 2 - Awk
It's the line after that match that you're interesting in, right? In sed, that could be accomplished like so:
sed -n '/ABC/{n;p}' infile
Alternatively, grep's A option might be what you're looking for.
-A NUM, Print NUM lines of trailing context after matching lines.
For example, given the following input file:
foo
bar
baz
bash
bongo
You could use the following:
$ grep -A 1 "bar" file
bar
baz
$ sed -n '/bar/{n;p}' file
baz
Hope that helps.
Solution 3 - Awk
I needed to print ALL lines after the pattern ( ok Ed, REGEX ), so I settled on this one:
sed -n '/pattern/,$p' # prints all lines after ( and including ) the pattern
But since I wanted to print all the lines AFTER ( and exclude the pattern )
sed -n '/pattern/,$p' | tail -n+2 # all lines after first occurrence of pattern
I suppose in your case you can add a head -1
at the end
sed -n '/pattern/,$p' | tail -n+2 | head -1 # prints line after pattern
And I really should include tlwhitec's comment in this answer (since their sed-strict approach is the more elegant than my suggestions):
sed '0,/pattern/d'
The above script deletes every line starting with the first and stopping with (and including) the line that matches the pattern. All lines after that are printed.
Solution 4 - Awk
awk Version:
awk '/regexp/ { getline; print $0; }' filetosearch
Solution 5 - Awk
If pattern match, copy next line into the pattern buffer, delete a return, then quit -- side effect is to print.
sed '/pattern/ { N; s/.*\n//; q }; d'
Solution 6 - Awk
Actually sed -n '/pattern/{n;p}' filename
will fail if the pattern
match continuous
lines:
$ seq 15 |sed -n '/1/{n;p}'
2
11
13
15
The expected answers should be:
2
11
12
13
14
15
My solution is:
$ sed -n -r 'x;/_/{x;p;x};x;/pattern/!s/.*//;/pattern/s/.*/_/;h' filename
For example:
$ seq 15 |sed -n -r 'x;/_/{x;p;x};x;/1/!s/.*//;/1/s/.*/_/;h'
2
11
12
13
14
15
Explains:
x;
: at the beginning of each line from input, usex
command to exchange the contents inpattern space
&hold space
./_/{x;p;x};
: ifpattern space
, which is thehold space
actually, contains_
(this is just aindicator
indicating if last line matched thepattern
or not), then usex
to exchange the actual content ofcurrent line
topattern space
, usep
to printcurrent line
, andx
to recover this operation.x
: recover the contents inpattern space
andhold space
./pattern/!s/.*//
: ifcurrent line
does NOT matchpattern
, which means we should NOT print the NEXT following line, then uses/.*//
command to delete all contents inpattern space
./pattern/s/.*/_/
: ifcurrent line
matchespattern
, which means we should print the NEXT following line, then we need to set aindicator
to tellsed
to print NEXT line, so uses/.*/_/
to substitute all contents inpattern space
to a_
(the second command will use it to judge if last line matched thepattern
or not).h
: overwrite thehold space
with the contents inpattern space
; then, the content inhold space
is^_$
which meanscurrent line
matches thepattern
, or^$
, which meanscurrent line
does NOT match thepattern
.- the fifth step and sixth step can NOT exchange, because after
s/.*/_/
, thepattern space
can NOT match/pattern/
, so thes/.*//
MUST be executed!
Solution 7 - Awk
This might work for you (GNU sed):
sed -n ':a;/regexp/{n;h;p;x;ba}' file
Use seds grep-like option -n
and if the current line contains the required regexp replace the current line with the next, copy that line to the hold space (HS), print the line, swap the pattern space (PS) for the HS and repeat.
Solution 8 - Awk
Piping some greps can do it (it runs in POSIX shell and under BusyBox):
cat my-file | grep -A1 my-regexp | grep -v -- '--' | grep -v my-regexp
-v
will show non-matching lines- -- is printed by grep to separate each match, so we skip that too