5

I want to print previous line,every time a match was found. I know about grep -A and -B options. But my Solaris 5.10 machine doesn't support those options.

I want solution using only sed.

Foo.txt:

Name is : sara
age is : 10
Name is : john
age is : 20
Name is : Ron
age is : 10
Name is : peggy
age is : 30

Out.txt:

Name is : sara
Name is : Ron

Pattern I am trying to match was age is : 10.

My environment, Solaris 5.10.

16
$ 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 space

    p : print the prior line

    d : delete the pattern space and start processing next line

  • x

    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.

  • How can i do an opposite of this search?.Print previous line when "age is : not 10" ? – ayrton_senna Jun 1 '15 at 20:18
  • @ayrton_senna Easy enough. I just added a solution for the opposite search to the answer. – John1024 Jun 1 '15 at 20:26
  • What about printing a range from the line prior to the match, through the line matching the second pattern? For example, the line prior to RETCODE through the line including the error MESSAGE. I tried this but got an error: sed -n '/^RETCODE/N,/^MESSAGE/p' logfile – Luv2code Sep 26 '16 at 16:58
  • To find the previous line of a pattern much is: sed -n '/Age is : 10/{n;p;}' Foo.txt – typelogic Sep 14 '18 at 15:45
  • @John1024 and if I want to get two lines before a match, how I can do this? – Gabriel Hardoim Mar 12 at 13:52
4

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
2

The hold 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 Hold commands - just appending - but every time you exchange 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 hold-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 Next input line following an embedded \newline 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 Prints only up to the first \newline in pattern space - so only the preceding line. Last, it Deletes up to the first \newline 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 look 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 look command - which renders an escaped version of pattern space to stdout. The lines which do not end in $ are those which would otherwise be Printed. As you can see, the previous line is only Printed when the second line in pattern space - the Next line as just pulled in and which follows the \newline 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 \newline followed by the Next input line if pattern space begins with the string Name, and only prints a line to output if pattern space does not end with the string 10 and if sed can successfully s///ubstitute away a \newline followed by the string age and all that follows until the tail of pattern space. Because there cannot be a \newline in pattern space except as the result of an edit command - such as Next - 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.

  • Does \n need in pattern /\nage.*: 10/? – cuonglm Jun 2 '15 at 8:01
  • @cuonglm - Yes. It ensures I'm matching the line just pulled in - the second line in pattern space. If I didn't use it I would print the age lines, too. You can see how it works really clearly with l. If you put a l; just before the D you'll see how it slides over the file - it advances two lines, removes one line the whole time. – mikeserv Jun 2 '15 at 8:04
  • The look seem to be broken in heirloom sed. It printed the lines as-is, not escaped sequence line \n or special like $. Thanks for l, it's worth. – cuonglm Jun 2 '15 at 8:20
  • @cuonglm - in heirloom it will work - there are actually three different seds in the heirloom package. The default one will not escape newlines - but it will escape most other things. Print some other non-printables at it. Now if you use the Susv4 version you'll even get NULs - and it will escape the newlines as well. look is how I learned to sed. – mikeserv Jun 2 '15 at 8:34
  • 1
    Efficiency aside, this is also better because it will work with arbitrary input (not only with the sample input where pattern is on every other line). If the input starts with a line matching pattern this won't print an empty line as first line of output and also if there are consecutive lines matching pattern this will print them (except the last one) which is correct as those are lines preceding a line matching pattern . ++ – don_crissti Jun 2 '15 at 20:54

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.