Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I am using

find . -name '*.[cCHh][cC]' -exec grep -nHr "$1" {} ';'
find . -name '*.[cCHh]' -exec grep -nHr "$1" {} ';'

to search for a string in all files ending with .c, .C, .h, .H, .cc and .CC listed in all subdirectories. But since this includes two commands this feels inefficient.

How do I write a regex to include .c,.C,.h,.H,.cc and .CC files using one single regex?

EDIT: I am running this on bash on a Linux machine.

share|improve this question
1  
By the way, you can use '+' at the end of find instead of ';'. It will accelerate the command due to shell will execute one grep per many files, not one grep per file as with ';'. –  rush Oct 19 '12 at 12:45
add comment

3 Answers

up vote 2 down vote accepted

As you (incorrectly – what you used is a shell pattern) mentioned it in the subject, you should use regular expressions:

find . -iregex '.*\.[ch]+'

The above is lazy approach, which will also find .ch, .hh and alike, if there exists. For exact matches you still have to enumerate what you want, but that is still easier with regular expressions:

find . -regex '.*\.\(c\|C\|cc\|CC\|h\|H\)'
share|improve this answer
    
How is this different from using find . -name '.*\.\(c\|C\|cc\|CC\|h\|H\)' ? –  Arpith Oct 20 '12 at 18:03
    
@Arpith, with -name you specify a shell pattern, with -regex you specify a regular expression. That '.*\.(c\|C\|cc\|CC\|h\|H)' string interpreted as shell pattern will rarely match anything, but certainly not what you intended in your question: pastebin.com/yhddCnbv –  manatwork Oct 21 '12 at 9:52
    
Great answer! Thanks a lot. :-) –  Arpith Oct 21 '12 at 18:42
add comment

Portably/standardly (POSIX, Unix (SUS) and Linux (LSB) standards) and efficiently, you'd write it:

find . \( -name '*.cc' -o -name '*.CC' -o -name '*.[cChH]' \) \
  -type f -exec grep -n -- "$1" /dev/null {} +

The most important point here is to use + instead of ;. Otherwise, you'll run one grep command per file.

The -H option is GNU specific, but adding /dev/null (which makes sure grep gets at least two files to look in) guarantees that grep displays the file name.

You'll need "--" unless you can make sure that $1 will never start with -.

Adding -type f here, to avoid looking into non-regular files (like directories), but as that means it also excludes symlinks, you may wish to leave it out.

share|improve this answer
    
-H versus /dev/null: very nice! FWIW, the -H option does seem to be widely available (I see it on FreeBSD 9, BusyBox grep, and Mac OS X grep). –  dubiousjim Oct 19 '12 at 14:26
    
@dubiousjim AFAIK FreeBSD and MacOS/X greps are the GNU grep. –  Stephane Chazelas Oct 19 '12 at 14:30
    
Oh yeah, you're right! That surprises me about FreeBSD. Most of their tools aren't Gnu. For instance, their sed and awk aren't. –  dubiousjim Oct 19 '12 at 14:32
add comment

Can be shortened to this single line:

find -type f -regextype posix-egrep -iregex '.*\.(cc|h|c)$' -exec grep -nHr "$1" {} \;

share|improve this answer
    
Your regular expression is wrong. It says “any character 0 or more times, followed by one of the enumerated strings”. On my machine that finds a lot of .sh script files… –  manatwork Oct 19 '12 at 8:52
    
@manatwork right, updated the answer –  warl0ck Oct 19 '12 at 9:01
    
knitpicking here, but the above would match .cC or .Cc files which were not requested. Also note that the $ is not needed as GNU find's regexps are implicitely anchored. –  Stephane Chazelas Oct 19 '12 at 9:14
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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