Extract lines that follow a pattern

Viewed 122

I'm looking to extract all lines that begins with Start and its next line ends with ***.

Appreciate any help.

Example:

*********************************************
Start the extract for customer_id [XXXX-2359]
*********************************************
Start the extract for customer_id [XXXX-2987]
Available
Printing records
Moving to output file
*********************************************
Start the extract for customer_id [XXXX-1539]
*********************************************
Start the extract for customer_id [XXXX-4527]
Available
Printing records
Moving to output file
*********************************************

Desired Output:

Start the extract for customer_id [XXXX-2359]
Start the extract for customer_id [XXXX-1539]

I tried:

awk '/Start*/ {p=1;print;next} /$**/ && p {p=0;print} p' test
6 Answers

Could you please try following, written and tested with shown samples. With tac + awk approach here.

tac Input_file | 
awk '
/^\*/{
  count=""
  found=1
  next
}
found && ++count==1 && /^Start/{
  print
  found=""
}
' | tac

Explanation: Adding detailed explanation for above.

tac Input_file |                   ##Using tac wit Input_file to print contents in reverse order and send it to awk command.
awk '                              ##Starting awk program here which reads tac output as an Input here.
/^\*/{                             ##Checking condition if line starts from * then do following.
  count=""                         ##Nullifying count here.
  found=1                          ##Setting found as 1 here.
  next                             ##next will skip all further statements from here.
}
found && ++count==1 && /^Start/{   ##Checking if found is SET and count is 1 and line starts with Start then do following.
  print                            ##Printing current line here.
  found=""                         ##Nullifying found here.
}
' | tac                            ##Sending awk program output as an input to tac to get output in exact order.
$ awk '(index($0,"***")==1) && (p1=="Start"){print p0} {p1=$1; p0=$0}' file
Start the extract for customer_id [XXXX-2359]
Start the extract for customer_id [XXXX-1539]

For every line after the first, p1 and p0 contain the values of $1 and $0 from the previous line read. So when the current line starts with 3 *s and the $1 from the previous line (p1) was Start then it prints the $0 from the previous line (p0).

With respect to the regexps in your question:

  • Start* means Star followed by t repeated 0 or more times.
  • $** contains back-to-back regexp repetition characters (*) and so is undefined behavior per POSIX and so any tool can do whatever it likes with it. Some will report it, some will silently ignore one of the *s, others could do anything else. The $ at the start is an end-of-string indication which matches the end of the current input so having any *s after it doesn't make sense but AFAIK it's not technically invalid.

This awk should work:

awk 'p != "" && /^\*{3}/ {print p} {p = ($1 == "Start" ? $0 : "")}' file

Start the extract for customer_id [XXXX-2359]
Start the extract for customer_id [XXXX-1539]
sed -n '/^Start/!d;N;/\*\{3\}$/P;D' file
pcregrep -Mo1 '^(Start.*)\n.*\*{3}$' file

Start* would match Star or Startttt anywhere in a string. The regex to match Start at the beginning of a string is ^Start.

$** is not a useful RE - * should not be repeated like that. POSIX leaves the behaviour undefined, with GNU sed reporting it as Invalid preceding regular expression. \*\{3\}$ (or equivalent) will match a string ending with three asterisks.

I would do it following way using GNU AWK, let file.txt content be

*********************************************
Start the extract for customer_id [XXXX-2359]
*********************************************
Start the extract for customer_id [XXXX-2987]
Available
Printing records
Moving to output file
*********************************************
Start the extract for customer_id [XXXX-1539]
*********************************************
Start the extract for customer_id [XXXX-4527]
Available
Printing records
Moving to output file
*********************************************

then

awk 'BEGIN{RS="\n?[*]+\n";FS="\n"}($NF~/^Start/)' file.txt

output

Start the extract for customer_id [XXXX-2359]
Start the extract for customer_id [XXXX-1539]

Explanation: I instructed AWK to treat content between lines with * as rows and lines in content as fields. To attain such effect I set row separator (RS) to one or more * followed by newline (\n) with \n optionally at begin (without that first ********************************************* would not be detected corretly) and FS to newline (\n). Then I check if last field i.e. line starts with Start. DISCLAIMER: this solution assumes that last line of your file is always like [*]+ it might fail if it is not.

(tested in GNU Awk 5.0.1)

This might work for you (GNU sed):

sed -n '/^\*\*\*/{g;/^Start/p};h' file

Turn off implicit printing.

If the current line begins ***, replace it by the previous line stored in the hold space and if that line begins Start print that line.

In all cases, store the current line in the hold space.

Another couple of solutions:

sed -n 'N;/^Start.*\n\*\*\*/P;D' file

or:

sed -n 'N;/^Start/{/^\*\*\*/MP};D' file

Both open a 2 line window and print the first of those lines if it begins Start and the second begins ***.

The second solution uses the M flag to match the *** at the start of a line, as there are only 2 such lines and the first begins Start the criteria for the both matches is met.

Related