5

Good morning, this is extremely similar to the question Grep From the Last Occurrence of a Pattern to Another Pattern (several months old), while adding a little more detail.

I am trying to write a UNIX script for a file with multiple duplicate patterns, followed by the pattern I am looking for. However I do not have 'tac' or 'tail -r' (using the UNIX emulator, MKS Toolkit), and am looking to return the last occurrence of Pattern1 before Pattern2, followed by the data in between Pattern1 and Pattern2, and then Pattern2 also. The Patterns in this case would be 'Condition 1' and 'Condition 2':

output.out:

...
Condition 1: A
data1
Condition 1: B
data2
Condition 2: C
data3
Condition 1: D
data4
Condition 1: E
data5
Condition 2: F
...

I'd like to write an awk (or sed, but figured awk would be the right tool) script to return:

Condition 1: B
data2
Condition 2: C
Condition 1: E
data5
Condition 2: F

I figure it's some form of the line below, but I can't get the syntax right:

awk '/Condition 1/ {acc = $0;} /,/Condition 2/ {print ?}' output.out

Working the '/,/' is where I seem to be having hangups. Was wondering if anyone had any advice, would be much appreciated. Many thanks for any help and time related to this question.

don_crissti
  • 82,805
Henry
  • 109

4 Answers4

5

Try:

$ awk 'f{a=a"\n"$0} /Condition 1/{a=$0; f=1} f && /Condition 2/{print a; f=0}' output.out 
Condition 1: B
data2
Condition 2: C
Condition 1: E
data5
Condition 2: F

How it works

  • f{a=a"\n"$0}

    If the variable f is true (nonzero), then append the current line onto the end of variable a.

  • /Condition 1/{a=$0; f=1}

    If the current line contains Condition 1, then set s to the current line and set variable f to 1.

  • f && /Condition 2/{print a; f=0}

    If f is true and the current line contains Condition 2, then print variable a and set f back to zero.

John1024
  • 74,655
  • 1
    Thank you John, much appreciated. Verified that it gave the same (and correct) results as the sed and perl answers. I did not realize that I was so far off with my thinking, and that it would be more detailed with awk than I thought. Thanks again. – Henry Nov 21 '17 at 21:02
  • I'm not sure what the point of collecting the lines in a is: awk '/Condition 1/ {f=1} f {print} /Condition 2/ {f=0}' accomplishes the same thing. – Paul Sinclair Nov 22 '17 at 04:03
  • 1
    @PaulSinclair It is a subtle point but that would start printing from the first occurrence of Condition 1. The OP wants to print only from the last time that Condition 1 occurs before the line containing Condition 2. – John1024 Nov 22 '17 at 04:26
  • 1
    True. I missed that. Your solution is indeed the way to go. – Paul Sinclair Nov 22 '17 at 04:32
5

When you want reverse addressing in text processing, use ex

It's POSIX specified, and it's the scriptable form of vi (and vi's immediate predecessor)—very flexible.

printf '%s\n' 'g/Condition 2/?Condition 1?,.p' | ex output.out

This means:

For every line (globally) matching the pattern "Condition 2", search backward for the immediately preceding instance of "Condition 1" and print all lines from that line to the current line (.) (which is the line with "Condition 2" on it).

Output on provided input is exactly as you describe.

Wildcard
  • 36,499
  • Thanks Wildcard. When executing this command, it looks like it gives me the same results as the awk sed and perl commands in this thread, but double spaces the results for some reason (in Notepad++). When I try to trim with "sed '/^$/d' file.txt", no effect, so maybe something weird is going on in my environment. This is definitely very helpful though, as it looks like exactly what I was wanting to do. – Henry Nov 22 '17 at 14:36
  • @Henry, very probably you have Windows-style line endings. – Wildcard Dec 01 '17 at 17:35
4
sed 'H;/PATTERN_1/h;/PATTERN_2/!d;x' infile

though this assumes that any line that matches PATTERN_2 is preceded by at least one line matching PATTERN_1. For the more general case add another condition to test for PATTERN_1 presence in the pattern space before printing:

sed 'H;/PATTERN_1/h;/PATTERN_2/!d;x;/PATTERN_1/!d' infile
don_crissti
  • 82,805
  • Thank you Don, much appreciated. Verified that it gave the same (and correct) results as the perl and awk answers. – Henry Nov 21 '17 at 21:01
4

Here's an evil bit of perl:

perl -0777 -ne '
    my $c1 = qr/Condition 1/;
    my $c2 = qr/Condition 2/;
    print for map {s/$c2.*?\n\K.*//s; $_}
              grep {/$c2/}
              split /(?=$c1)/ms;
' output.out

It:

  • reads the entire file (using the -0777 and -n options),
  • splits it where Condition 1 appears (split),
  • filters out paragraphs where Condition 2 does not appear (grep),
  • then removes from each interesting paragraph any lines following the Condition 2 line (map).
glenn jackman
  • 85,964
  • Thank you Glenn, much appreciated. Verified that it gave the same (and correct) results as the sed and awk answers. – Henry Nov 21 '17 at 21:01