6

I know the following commands will print up to the first occurrence of a certain pattern but will not include the later occurrences:

sed -n '1,/<pattern>/p' <file>

sed '/<pattern>/q' <file>

For example lets say I have a file having the following lines:

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

Now the outputs:

$ sed '/fish/q' file  

this is a cow  
this is a goat  
this is a some fish 

$ sed -n '1,/fish/p' file  

this is a cow  
this is a goat  
this is a some fish 

I want the output starting from the very first line till the line containing the last occurrence of fish i.e. my desired output is:

this is a cow   
this is a goat  
this is a some fish  
this is a fishie  
this is a fish 

How to do that using sed?

4
  • sed -n '/fish/p' ?
    – Runium
    Dec 2, 2014 at 20:17
  • Right you are..
    – heemayl
    Dec 3, 2014 at 19:32
  • I have updated the question properly and hope it will be removed from hold.
    – heemayl
    Dec 3, 2014 at 20:09
  • @KasiyA: Yes, Its working properly. Thanks.
    – heemayl
    Dec 3, 2014 at 20:21

5 Answers 5

5

Try this:

$ tac infile | sed -n '/fish/,$p' |tac

In general case if you run below sed command, you will get all line from first matched pattern to end of input file.

$ sed -n '/fish/,$p' file

this is a some fish
this is a fishie
this is a fish
this is a lion
this is a cat

So my solution is: If we run tac command on input file your last matched pattern will change to as first pattern. see the result of tac infile:

$ tac infile

this is a cat
this is a lion
this is a fish
this is a fishie
this is a some fish
this is a goat
this is a cow

The tac command is the same as cat command, while tac prints files in reverse order.

Now if we run our first sed command, you will get all lines first matched pattern to end of input file. like:

$ tac infile | sed -n '/fish/,$p'
this is a fish
this is a fishie
this is a some fish
this is a goat
this is a cow

Ok, finished. We only need to run tac command again to reverse back the lines to original order:

$ tac infile | sed -n '/fish/,$p' |tac
this is a cow
this is a goat
this is a some fish
this is a fishie
this is a fish

Done!

0
3

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.

9
  • @don_crissti - correct! It's not about blank lines though - it's more about adjacent fishes. They get swallowed when they're near one another - specifically when n doesn't get checked and another fish happens in the meanwhile. I doubly populate the buffer - it will still refresh w/ every exchange and so on, but once per turn the buffer is held and got so nothing slips by. I'm about to post the edit. Thanks very much for your comment.
    – mikeserv
    Dec 3, 2014 at 23:42
  • @don_crissti - please try it again if you will. I threw the stuff you mentioned at it.
    – mikeserv
    Dec 3, 2014 at 23:45
  • @don_crissti - I could do that, but I don't I have that problem - I think... I'll check again. I can explicitly check the last line, of course. In any case, I do very much appreciate your help. Back in a few, I hope.
    – mikeserv
    Dec 4, 2014 at 1:11
  • @don_crissti - I saw what you meant and figured out why, I think. If you find the time, try it again? I appreciate it.
    – mikeserv
    Dec 4, 2014 at 1:35
  • @don_crissti - ok. fixed. I hope. I admit - you have shaken my confidence, but I was pretty busy earlier. Anyway, thanks for the test data - I verified it works at least with the file you linked me.
    – mikeserv
    Dec 4, 2014 at 6:55
0

You could also use awk which might be shorter than Sed:

awk ' /^fish/ { print $0 }' filename.txt

Some people may write this:

awk ' /^fish/ { print $1 $2 $3 $4 $5 }' filename.txt

The $n represents a column. The shortcut $0 represents the entire line.

3
  • Please check my desired output as your answer will print the lines containing "fish" at first but i want the output to be from the first line till the line containing the last occurrence of fish.
    – heemayl
    Dec 3, 2014 at 19:22
  • Fix your question stating that in words as the question is on hold.
    – eyoung100
    Dec 3, 2014 at 19:34
  • I have updated the question too..
    – heemayl
    Dec 3, 2014 at 19:43
0

The question is not very clear, but after some edit iterations I believe you should rather use pcregrep for this task. For example if the input is

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
this is a fisherman
this is a dog

and desire output

this is a cow
this is a goat
this is a some fish
this is a fishie
this is a fish

then

pcregrep -M '(.|\n)*fish.*(?=(.|\n)*fish.*)'

will do the job.

0
0

Using Raku (formerly known as Perl_6)

raku -e '$/.put if m/^ .* fish/ for lines.join("\n");'

Sample Input:

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

Sample Output:

this is a cow   
this is a goat  
this is a some fish  
this is a fishie  
this is a fish

Note, the regex above will terminate on the word fish which works fine as long as fish is the last word on the line. If, however, the OP wants to capture the entire last line (so that for example the entire word fisherman is returned), then change the regex matcher above to m/^ .* fish .*? $$ / .

https://raku.org

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .