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