This is fairly simple - it just takes a little coordination between sed's two buffers. For example:
sed -n 'H;/fish/!d;$!n;x;p;G
' <<\INFILE
this is a cow
this is a goat
this is a some fish
this is a fishie
this is a fish
this is a lion
this is a cat
INFILE
That command appends every line to Hold space following an inserted \newline character. Any line which does !not match /fish/ is immediately thereafter deleted from output. Which leaves us with only /fish/ lines. So the line is overwritten with the next line of input. Then pattern and hold spaces are swapped - we did just Hold the line after all. Now pattern space is Hold space and vice-versa. So we just print whatever was saved from the last time a /fish/ line matched.
This can only get up to the last occurrence of a match because it only prints when it finds a match - and it stores intervening lines in the interim. Still, it only stores as little as necessary between matches - each time the buffers are exchanged they are refreshed. Here's your output:
this is a cow
this is a goat
this is a some fish
this is a fishie
this is a fish
Thanks very much to don cristi for showing me that I sometimes skipped a fish. It now makes certain to push the buffer to both ends each time it is refreshed - and each turn to overwrite the current pattern space before deleting it - in case. It will work with a fish on the first line or the last, and any line in between as near as I can tell.
Another thing I was doing was pulling the next line on the last line - which is a big sed no-no. Thanks again to don for helping me with that as well.
A more thorough example:
sed 'x;/./G;//!x;/fish/p;//s/.*//;x;d'
That handles a few problems surrounding the other better, I hope. Pattern/hold spaces are exchanged every cycle - and this is to avoid getting extra blank lines as a result of the edit s///ubstitution at the end of the command. So the buffers are swapped and if the hold buffer is not empty the the current line is appended into that buffer following a \newline character - which is where the extra lines would come from otherwise. Else the buffers are just swapped back and hold space remains empty for the current cycle. As near as I can tell, this command preserves all blank lines and everything else - but merely stops printing at the last match.
Some of the difficulty I had with this is commonly associated with hold space - the only way to use it effectively is to fall short of - fall behind - the line cycle in order to compare old lines with older. My usual preference is for an N;P;D loop. You might do this thing using something along those lines like...
sed -ne :n -e '/fish/!N;//p;//!bn'
There sed continuously appends the Next line of input to pattern space and branches back to the :n label to try again if fish does not match any of the lines it has built-up thus far. It only prints lines - or line-sequences - which match fish before dumping the contents at line-cycle end and starting anew with a fresh buffer.
I purposely do not test for the last line here - if the last line matches it will be printed, else - and with -n this holds true even for GNU sed - the loop will just end the file all of its own last line or no.
sed -n '/fish/p'? – Runium Dec 02 '14 at 20:17