Print previous line after a pattern match using sed?
$ sed -n '/age is : 10/{x;p;d;}; x' Foo.txt
Name is : sara
Name is : Ron
The above was tested on GNU sed. If Solaris' sed does not support chaining commands together with semicolons, try:
$ sed -n -e '/age is : 10/{x;p;d;}' -e x Foo.txt
Name is : sara
Name is : Ron
How it works
sed has a hold space and a pattern space. Newlines are read into the pattern space. The idea of this script is that the previous line is saved in the hold space.
/age is : 10/{x;p;d;}
If the current line contains
age is : 10
, then do:x
: swap the pattern and hold space so that the prior line is in the pattern spacep
: print the prior lined
: delete the pattern space and start processing next linex
This is executed only on lines which do not contain
age is : 10
. In this case, it saves the current line in the hold space.
Doing the opposite
Suppose that we want to print the names for people whose age is not 10:
$ sed -n -e '/age is : 10/{x;d}' -e '/age is :/{x;p;d;}' -e x Foo.txt
Name is : john
Name is : peggy
The above adds a command to the beginning, /age is : 10/{x;d}
, to ignore any age-10 people. The command which follows, /age is :/{x;p;d;}
, now accepts all the remaining ages.
POSIXly:
$ sed -n '/age is : 10/{g
1!p
}
h
' file
For printing previous line if current line does not match age is : 10
:
$ sed -n '
$!N
/age is : 10/d
P
' file
The h
old buffer is good for storing a line (or group of lines) until some later test proves true. In other words, it is good for handling sequences of data which you want sequential but are not yet sequential - because it enables you to stick them together. But it also requires a lot of copies between the two buffers. This isn't so bad if you're building up a series of lines with H
old commands - just appending - but every time you ex
change buffers you copy the whole of one to the other and vice-versa.
When you're working with a series of lines which are already sequential, and you want to prune them based on context, then the better way to go is with
look-ahead - as opposed to the h
old-buffer's look-behind. cuonglm does this for the second half of his answer already - but you can use that logic for either form.
sed '$!N;/\nage.*: 10/P;D' <infile >outfile
See, that will append the N
ext input line following an embedded \n
ewline delimiter to the current pattern space on every line which is !
not the $
last. It then checks if the line just pulled matches a pattern, and, if so it P
rints only up to the first \n
ewline in pattern space - so only the preceding line. Last, it D
eletes up to the first \n
ewline in pattern space and starts the cycle again. So throughout the file you maintain a one-line look-ahead without unnecessarily swapping buffers.
If I alter the command only a little you can see specifically how it works - by sliding over the file with a two-line window throughout. I'll add a l
ook command just before the D
:
sed '$!N;/\nage.*: 10/P;l;D'
Name is : sara
Name is : sara\nage is : 10$
age is : 10\nName is : john$
Name is : john\nage is : 20$
age is : 20\nName is : Ron$
Name is : Ron
Name is : Ron\nage is : 10$
age is : 10\nName is : peggy$
Name is : peggy\nage is : 30$
age is : 30$
That's its output. The lines which end in $
are the result of the l
ook command - which renders an escaped version of pattern space to stdout. The lines which do not end in $
are those which would otherwise be P
rinted. As you can see, the previous line is only P
rinted when the second line in pattern space - the N
ext line as just pulled in and which follows the \n
ewline in pattern space - matches your pattern.
Besides the solutions already offered you, another way you might go for printing only Name lines preceding an age line which does not end in 10:
sed -n '/^Name/N;/ 10$/!s/\nage.*//p'
...which only appends a \n
ewline followed by the N
ext input line if pattern space begins with the string Name, and only p
rints a line to output if pattern space does not end with the string 10 and if sed
can successfully s///
ubstitute away a \n
ewline followed by the string age and all that follows until the tail of pattern space. Because there cannot be a \n
ewline in pattern space except as the result of an edit command - such as N
ext - the ensures that the only Name lines printed are those immediately preceding an age line which does not end in the string 10.
All of the syntax used in the above answer is POSIX standard - it should work as written with any sed
which supports the standard.