ChatGPT解决这个技术问题 Extra ChatGPT

How can I grep recursively, but only in files with certain extensions?

I'm working on a script to grep certain directories:

{ grep -r -i CP_Image ~/path1/;
grep -r -i CP_Image ~/path2/;
grep -r -i CP_Image ~/path3/;
grep -r -i CP_Image ~/path4/;
grep -r -i CP_Image ~/path5/; }
| mailx -s GREP email@domain.example

How can I limit results only to extensions .h and .cpp?

Tried grep -r -i CP_Image ~/path1/*.{h,cpp}?
Use The Silver Searcher: ag -i CP_Image ~/path[1-5] | mailx -s GREP email@domain.com. Job done.
Use egrep (is most likely pre-installed on your system), and then you can use a regex.
The GNU guys really messed up when they added -r to grep to have it search for files as that breaks the UNIX mantra of having tools that "do one thing and do it well". There's a perfectly good tool for finding files with a VERY obvious name.

S
Stephen Ostermiller

Just use the --include parameter, like this:

grep -inr --include \*.h --include \*.cpp CP_Image ~/path[12345] | mailx -s GREP email@domain.example

That should do what you want.

To take the explanation from HoldOffHunger's answer below:

grep: command

-r: recursively

-i: ignore-case

-n: each output line is preceded by its relative line number in the file

--include \*.cpp: all *.cpp: C++ files (escape with \ just in case you have a directory with asterisks in the filenames)

./: Start at current directory.


For the record: -r (recursive) -i (ignore-case) --include (search only files that match the file pattern)
Can be further optimized to grep -r -i --include \*.h --include \*.cpp CP_Image ~/path[12345]
@Hong where is the documentation that -R is for symbolic links?
This example seems to have a high score because it covers such a wide range of possibilites but the answer given below of grep -r --include=*.txt 'searchterm' ./ really explains the essence of the answer
why not use double quotes instead of backslash? e.g: grep -r -i --include="*.h" --include="*.cpp" CP_Image
H
HoldOffHunger

Some of these answers seemed too syntax-heavy, or they produced issues on my Debian Server. This worked perfectly for me:

grep -r --include=\*.txt 'searchterm' ./

...or case-insensitive version...

grep -r -i --include=\*.txt 'searchterm' ./

grep: command

-r: recursively

-i: ignore-case

--include: all *.txt: text files (escape with \ just in case you have a directory with asterisks in the filenames)

'searchterm': What to search

./: Start at current directory.

Source: PHP Revolution: How to Grep files in Linux, but only certain file extensions?


You should escape the * using \*.cpp or '*.cpp'. Otherwise it won’t give the expected result when the working directory contains some *.txt files.
@Melebius can you explain why it needs escaping - does it have anything to do with the CPP or TXT extensions you mentioned? Or did you just use those as examples?
@SimonEast These extensions are those used in this question and answer, nothing special otherwise. It would probably work without escaping when using --include=<pattern> but it is important to escape * with --include <pattern> (a space instead of =) which feels very similar otherwise.
@Melebius adding to what you wrote, it does work with --include=<pattern>. It also works with --include<pattern>, so long as there are no files matching the pattern in the current directory. I.e., it's safest to escape the pattern when you're not using the = syntax, but you can live dangerously if you assume there are no files matching the pattern in the current directory.
C
Community
grep -rnw "some thing to grep" --include=*.{module,inc,php,js,css,html,htm} ./

grep -rn "some thing to grep" --include=*.{module,inc,c,h} *
Nice answer. Cleaner than the accepted on IMO but you should add search criteria as @ashish noted
why is --include option after needle, not with other options?
@vladkras, what do you mean needle? Is it --?
Almost, but that didn't work for me--it kept trying to match on --include=*.foo. The working solution was wrapping the --include value in quotes. E.g. --include="*.foo".
P
Peter Mortensen

Use:

find . -name '*.h' -o -name '*.cpp' -exec grep "CP_Image" {} \; -print

i'd suggest grouping those -name arguments. strange things can happen if you don't. find . \( -name '*.h' -o -name '*.cpp' \) -exec grep "CP_Image" {} \; -print
use with additional "-type f" to ignore all directory objects, only interested in files.
I used this method for years and it works but it's a LOT slower than recursive grep since find's exec spawns a separate grep process for each file to be searched.
Addressing @beaudet's comment, find can optionally bundle arguments, reducing invocations of the called process to a minimum. find . \( -name \*.h -o -name \*.cpp \) -exec grep -H CP_Image {} + This is suggested but not highlighted in @fedorqui's answer below and is a worthwhile improvement. The -H argument to grep here is useful when find only identifies a single matching file. This could eliminate the usage of -print in the answer. If your total list of files is sufficiently small, using a recursive shell glob (eg. {path1,path2}/**/*.{cpp,h}) might be preferable.
P
Peter Mortensen

There isn't any -r option on HP and Sun servers, but this way worked for me on my HP server:

find . -name "*.c" | xargs grep -i "my great text"

-i is for case insensitive search of string.


I've come across several servers for web hosting companies that do not have the --include option available for fgrep and this is the command line that I use in those instances.
The --include option is also not available when using Git for Windows (MinGW/MSys).
@DarrenLewis available in Git Bash for Windows. But strangely, it adds colorful aliases like ll but does not add --color=auto to grep.
This should be the accepted answer for completeness, portability, and brevity!
Re "HP and Sun servers": Do you mean for HP-UX and Solaris?
S
Stephen Ostermiller

This answer is good:

grep -r -i --include \*.h --include \*.cpp CP_Image ~/path[12345] | mailx -s GREP email@domain.example

But it can be updated to:

grep -r -i --include \*.{h,cpp} CP_Image ~/path[12345] | mailx -s GREP email@domain.example

Which can be simpler.


What does "The below answer" refer to? References to relative positions of answers are not reliable as they depend on the view (votes/oldest/active) and changing of the accepted answer and change over time (for votes, active, and accepted state). Please respond by editing your answer, not here in comments (without "Edit:", "Update:", or similar - the answer should appear as if it was written today).
Can we simply use grep command along with ls command?
@Harsha Sure. use pipe to make the output of ls as the input of grep. such as ls | grep *.h
Could you provide me a link that would give explanations as to how I can fully understand this?
C
Community

Since this is a matter of finding files, let's use find!

Using GNU find you can use the -regex option to find those files in the tree of directories whose extension is either .h or .cpp:

find -type f -regex ".*\.\(h\|cpp\)"
#            ^^^^^^^^^^^^^^^^^^^^^^^

Then, it is just a matter of executing grep on each of its results:

find -type f -regex ".*\.\(h\|cpp\)" -exec grep "your pattern" {} +

If you don't have this distribution of find you have to use an approach like Amir Afghani's, using -o to concatenate options (the name is either ending with .h or with .cpp):

find -type f \( -name '*.h' -o -name '*.cpp' \) -exec grep "your pattern" {} +
#            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

And if you really want to use grep, follow the syntax indicated to --include:

grep "your pattern" -r --include=*.{cpp,h}
#                      ^^^^^^^^^^^^^^^^^^^

I needed a script which returned exit status 1 if the grep command matched any files. I started out using find+xargs+grep. But the fact that xargs returns exit status 123 if grep returns 1 made things more complicated. It was more straightforward to just use grep in my case.
P
Peter Mortensen

The easiest way is:

find . -type  f -name '*.extension' 2>/dev/null | xargs grep -i string

Add 2>/dev/null to kill the error output.

To include more file extensions and grep for password throughout the system:

find / -type  f \( -name '*.conf' -o -name "*.log" -o -name "*.bak" \) 2>/dev/null |
xargs grep -i password

P
Peter Mortensen

ag (the silver searcher) has pretty simple syntax for this

       -G --file-search-regex PATTERN
          Only search files whose names match PATTERN.

so

ag -G *.h -G *.cpp CP_Image <path>

using ag 2.2.0, i needed to put my flags last: ag _string_to_find_ -G _filename_regex_
P
Peter Mortensen

You should write "-exec grep " for each "-o -name ":

find . -name '*.h' -exec grep -Hn "CP_Image" {} \; -o -name '*.cpp' -exec grep -Hn "CP_Image" {} \;

Or group them by ( )

find . \( -name '*.h' -o -name '*.cpp' \) -exec grep -Hn "CP_Image" {} \;

Option '-Hn' shows the file name and line.


P
Peter Mortensen

Here is a method I normally use to find .c and .h files:

tree -if | grep \\.[ch]\\b | xargs -n 1 grep -H "#include"

Or if you need the line number as well:

tree -if | grep \\.[ch]\\b | xargs -n 1 grep -nH "#include"

n
nvd

If you want to filter out extensions from the output of another command e.g. "git":

files=$(git diff --name-only --diff-filter=d origin/master... | grep -E '\.cpp$|\.h$')

for file in $files; do
    echo "$file"
done