3

Let's say I have multiple lines similar to below in a file.

Turbo is a cat. cats are good. cats are not dog.
Coco is a black cat. cats are furry. cats are not dog.

now, if want to grep all the ^.*cat but want to specially mention to capture till first (or nth) occurrence of the word cat.
Desired Output:

Turbo is a cat
Coco is a black cat
*blah is a so and so cat*

How can I grep it?

PS: I would love to have an answer using grep (or its other variants) only.

PS: I don't want to grep ^.*cat. and then do any operation to remove the "." . I want a generic answer.

2

With POSIX grep, you can only choose between printing the whole line, or not printing the line content at all. If you want to transform the line, you need to use another tool such as sed or awk. To print up to the first occurrence of cat:

sed -n 's/cat.*/cat/'
awk 'sub(/cat.*/,"")'

Printing up to the Nth occurrence is more complicated.

sed -n 's/cat/&\
/3; T; P'
awk 'gsub(/cat/,"&\n") >= 3 {split($0, a, "\n"); printf "%s%s%s\n", a[1], a[2], a[3]}'

With GNU grep, you can use the -o option to print only the matched part of the line. Use the -P option to activate Perl syntax, so that non-greedy quantifiers are available.

grep -P -o '^(.*?cat){1}'

Replace the number in braces by the number n of the last occurrence of cat to be printed.

While it's possible to express the same thing with extended regular expressions (-E), this requires a complex regexp, whose size is exponential in the size of the part to count (cat here).

1

grep only selects lines based on the regular expression specified and prints them.

I think you are forced to pipe the output lines and use an additional command to do the job.

Usually you would use sed or awk to do the job without grep, because they can both select lines and replace strings.

There is a solution below using awk:

awk -v word=cat -v n=2 'BEGIN {wordlength=length(word);} {line=$0;outputline="";position=index(line,word);for (i=1;position>0 && i<=n; i++) { outputline=outputline substr(line,1,position+wordlength-1);line=substr(line,position+wordlength);position=index(line,word);  } if (i!=1) {print outputline;}}'

You should set word to the string to search and the n to the number of occurrences wanted.

The test:

$ awk -v word=cat -v n=2 'BEGIN {wordlength=length(word);} {line=$0;outputline="";position=index(line,word);for (i=1;position>0 && i<=n; i++) { outputline=outputline substr(line,1,position+wordlength-1);line=substr(line,position+wordlength);position=index(line,word);  } if (i!=1) {print outputline;}}' file
Turbo is a cat. cat
Coco is a black cat. cat
  • So, regular expression cannot help selecting till nth pattern? Can't we have something similar to Eelvex's answer to stackoverflow.com/questions/5422949/… – Krishna Gupta Apr 12 '16 at 21:00
  • @KrishnaGupta The link is showing a solution with regular expression extension with perl. I am not familiar with it. The answer had been updated with a awk solution. – Jay jargot Apr 12 '16 at 21:43
0

Here's a sed solution (e.g. print up to and including the 2nd occurrence; replace 2 with your no.):

sed -n 's/cat/&\
/2
t print
d
:print
P' infile

This disables autoprinting via -n and attempts to replace the 2nd occurrence of cat with cat+ a newline character. If the substitution is successful it branches to :print and Prints up to the newline, otherwise the line is deleted.


With gnu sed you could write it as a one liner (e.g. print up to and including the 5th occurrence):

sed -n 's/cat/&\n/5;tt;d;:t;P' infile
  • Note that my answer assumes you don't want to print lines with less than N occurrences. It can be easily modified to print those lines too. – don_crissti Apr 12 '16 at 22:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.