Extract multiple lines if match

The following will match the tag given in the tag variable:

awk -v tag=P1-DIMMD1 '/ID    SIZE TYPE/ { block = $0; output = 0; next } { block = block "\n" $0 } /Location Tag/ { output = ($0 ~ tag) } /Configured Voltage/ && output { print block }'

The AWK script is

/ID    SIZE TYPE/ {
  block = $0
  output = 0
  next
}

{ block = block "\n" $0 }

/Location Tag/ { output = ($0 ~ tag) }

/Configured Voltage/ && output { print block }

We accumulate a block in the block variable, and output it when we reach the end of the block if we saw the right tag in the process.


You could use ed ... and sed, man!

You have to want to use ed for this one, though, as ed wants to operate on a file, not as part of a pipeline.

  1. command > dimm-output
  2. wanted=P1-DIMMD1
  3. ed -s dimm-output <<< $'/Location Tag: '"$wanted"$'\n?^ID.*SIZE.*TYPE\n.,/Configured Voltage/p\nq\n' | sed 1,2d

The ed command-string breaks down to four \n-separated commands:

  1. search forwards, using /, for the text "Location Tag: " followed by the value of the $wanted variable
  2. search backwards, using ?, for the pattern: (start-of-line), "ID", anything, "SIZE", anything, "TYPE"
  3. from that line (.), through (,) the next line that matches "Configured Voltage", print those lines (p)
  4. quit ed: q

Because ed auto-prints the matching line when you search, I used sed here to delete those two lines.


Inspired by @Stephen Kitts great answer, I wrote a little more general script to perform block matching when having a specified start and end pattern.

#!/usr/bin/awk -f
BEGIN {
    pstart=ARGV[1];
    pstop=ARGV[2];
    pmatch=ARGV[3];
    ARGV[1]=ARGV[4];
    ARGC=2;
}
$0 ~ pstart { block = $0; output = 0; next }
{ block = block "\n" $0 }
$0 ~ pmatch { output = 1 }
$0 ~ pstop && output { print block; output = 0 }

Usage: match_block START END MATCH [FILE]

./match_block '^ID' 'Configured Voltage' 'Location Tag: P1-DIMMD1' f

or

command | ./match_block '^ID' 'Configured Voltage' 'Location Tag: P1-DIMMD1'

Thanks for suggesting writing this as an awk script directly. My original shell script was:

#!/bin/sh

[ -z "$4" ] && file="-" || file="$4"

awk \
  -v BLOCKSTART_PATTERN="$1" \
  -v BLOCKEND_PATTERN="$2" \
  -v BLOCKMATCH_PATTERN="$3" \
  '
  $0 ~ BLOCKSTART_PATTERN { block = $0; output = 0; next }
  { block = block "\n" $0 }
  $0 ~ BLOCKMATCH_PATTERN { output = 1 }
  $0 ~ BLOCKEND_PATTERN && output { print block; output = 0 }
  ' "$file"

Usage: match_block START END MATCH [FILE].
If file is omitted, stdin will be used.

In your case:

command | ./match_block '^ID' 'Configured Voltage' 'Location Tag: P1-DIMMD1'

or

./match_block '^ID' 'Configured Voltage' 'Location Tag: P1-DIMMD1' file