Some possible solutions:
sed: sed -z 's/.*\(StartPattern.*EndPattern[^\n]*\n\).*/\1\n/' file
ed : printf '%s\n' '1;kx' '?^End?;kx' "?^Start?;'xp" | ed -s file
ex : printf '%s\n' '1' '?^End?' "?^Start?,.p" | ex file
awk: awk '/^Start/{s=1;section=""}
s{section=section $0 ORS}
/^End/{complete=section;s=0}
END{printf ("%s",complete)}' file
tac: tac file | sed -n '/^End/,/^Start/{p;/^Start/q}' | tac
regex sed
You can match the last occurrence of a pattern between start
and end
with a regex like:
.*START.*END.*
Then, you can extract the range including the delimiters with a parentheses.
.*\(START.*END\).*
That will work in sed (as it may use the replace s///) but require GNU sed to make the whole file one string (using the -z option):
sed -z 's/.*\(StartPattern.*EndPattern[^\n]*\n\).*/\1\n/' file
ed
It is possible to search backwards in ed
with ?regex?
. So, we can search backwards for EndPattern
(to ensure the pattern is complete and we are at the last one) and then search also backward to the previous StartPattern
.
printf '%s\n' '?^End?;kx' '?^Start?;kx' '.;/End/p' | ed -s file
The ;kx
is used to avoid that ed prints the selected line.
That would fail if the last line is End
, to avoid that, start on the first line and search backward for End
.
And, since the limits are being marked, we can use a simpler range:
printf '%s\n' '1;ky' '?^End?;ky' '?^Start?;kx' "'x;'yp" | ed -s file
Or,
printf '%s\n' '1;kx' '?^End?;kx' "?^Start?;'xp" | ed -s file
That is assuming that at least one complete section of Start
-- End
exists. If there is none, the script will fail.
I have seen several uses of ?Start?,?End?
. That may fail in several ways because it doesn't mean "find the next End
after what was found by Start
. Compare:
$ printf '%s\n' 1 '?START?,?END?p' | ex -s <(printf '%s\n' 111 START 222 END 333 END 444)
START
222
END
333
END
$ printf '%s\n' 1 '?START?,/END/p' | ex -s <(printf '%s\n' 111 START 222 END 333 END 444)
START
222
END
ex
The command from ed
could be simplified to work in ex
:
printf '%s\n' '1' '?^End?' '?^Start?,.p' | ex file
awk
We can store each complete section Start
to End
in one variable and print it at the end.
awk '
/^Start/{s=1;section=""} # If there is an start, mark a section.
s{section=section $0 ORS} # if inside a section, capture all lines.
/^End/{complete=section;s=0} # If a section ends, unmark it but store.
END{printf ("%s",complete)}' file # Print a complete section (if one existed).
# tac
We can reverse the whole file (line by line) and then print only the **first** section that starts at `End` and ends at `Start`. Then reverse again:
tac file | sed -n '/^End/,/^Start/{p;/^Start/q}' | tac
The /^Start/q
exists sed to ensure that only the first section is printed.
Note that this will print everything from the last End
to the start of the file if there is no Start
to be found (instead of just not printing).
test file
Tested with (at least) this file (and others):
$ cat file3
Don't print 1
Don't print 2
Don't print 3
StartPattern_here-1
Inside Pattern but Don't print 1-1
Inside Pattern but Don't print 1-2
Inside Pattern but Don't print 1-3
EndPattern_here-1
Lines between 1 and 2 - 1
Lines between 1 and 2 - 2
Lines between 1 and 2 - 3
StartPattern_here-2
Inside Pattern but Don't print 2-1
Inside Pattern but Don't print 2-2
Inside Pattern but Don't print 2-3
EndPattern_here-2
Lines between 2 and 3 - 1
Lines between 2 and 3 - 2
Lines between 2 and 3 - 3
StartPattern_here-3
Inside Pattern, Please Do print 3-1
Inside Pattern, Please Do print 3-2
Inside Pattern, Please Do print 3-3
EndPattern_here-3
Lines between 3 and 4 - 1
Lines between 3 and 4 - 2
Lines between 3 and 4 - 3
StartPattern_here-4
This section has an start
but not an end, thus,
incomplete.
Lines between 4 and $ - 1
Lines between 4 and $ - 2
Lines between 4 and $ - 3