Edit: HatLess's sed
solution looks much better to me.
I agree with the Shawn's answer; sed
is not the best tool for the job. But here's a solution with sed
:
Have a script.sed
file:
# read the full file into the pattern space
:1
$! { N ; b1 }
# replace last occurrence of "2 lines plus pattern line"
# with just the pattern line
s/(.*\n.*\n)(pattern\n?.*)\'/\2/m
Run it like this:
sed -E -f script.sed file.txt
Or in a single line like this:
sed -E ':1 ; $! { N ; b1 } ; s/(.*\n.*\n)(pattern\n?.*)\'\''/\2/m' text
The basic idea is that, because we need to work on the latest pattern in the file, we need to read the entire file before modifying it.
The first two lines are a loop using sed
's goto-like commands:
:1
creates a label called 1
.
$!
makes sure that we run the following commands for every line except the last one.
N
reads the next line.
b1
jumps to the label 1
.
The following substitution command will only run on the last line. Note the following:
- We don't need to escape the capturing group parentheses (
\(
and \)
) because we call sed with the -E
flag which turns on the Extended Regular Expression syntax.
- We pass the flag
m
to the substitute command, which makes the regex work in multiline mode. In our case, this provides the following characteristics:
- The dot (
.
) no longer matches newline characters (\n
). This is useful in our case because we want to be explicit about the number of lines we match.
- It enables the special
\'
character (a sed
-only feature), which matches the end of the buffer. We need this to anchor our regex to the end of the file.
- Also note the
\n?
after pattern. Because sed
reads lines without the trailing new line, this is a way to match a "pattern" that might be either the last line or a line in the middle of the file.