How can I use find command to search string/pattern in a file recursively?

Hi,

How can I use find command to search string/pattern in a file recursively?
What I tried:

find . -type f -exec cat {} | grep "make" \;

Output:

grep: find: ;: No such file or directory
missing argument to `-exec'

And this:

find . -type f -exec cat {} \; -exec grep "make" {} \;

This print all the contents of files and the matched part of that file. I want to print only the matched part.

I wanted to use find with exec, normally we do

cat filename | grep "make"

Is it possible to do `cat filename | grep "make"` with find exec ?

Any answer will be highly appreciated.
Thanks in advance.

Hello cola,

If you want to print only the file names which have string make in them then following may help you in same.

find . -type f -exec grep -l "make" {} \+

In case you want to see the line/record where it has string make in it then following may help you in same too.

find . -type f -exec grep "make" {} \+

Thanks,
R. Singh

In addition to what R. Singh has already said...

Stop using cat ! The grep utility is perfectly capable of opening files by itself and doing it that way is not only MUCH more efficient, it also gets rid of all of your problems. Try:

find . -type f -exec grep "make" {} \;

or if you want the grep output to let you know which file(s) contain the word you are trying to find; and since you're looking for a fixed string you can make the search much faster with:

find . -type f -exec grep -F "make" /dev/null {} +

which looks for fixed strings (instead of regular expressions) in large numbers of files with each invocation of grep (instead of one invocation of grep for each file processed).

What's the difference between {} \; and {} + or {} \+ ?

Is it possible to call multiple commands with -exec like piping:

find . -type f -exec onecommand | secondcommand {} \;

What's wrong with using cat?

Hello cola,

Following are the answers for your queries.

  1. Difference between {} \+ and {} \; is {} \+ is faster than {} \; .
  2. Yes, it is possible to use multiple exec calls into a single find command, it is completely depends on your requirements.
  3. So there is something called UUOC (Useless use of cat), so using cat command with a command which is able to read Input_file by itself it called UUOC in very short description. Following is an example for same. Let's say we have following Input_file.
cat  Input_file
abcdef
test test test1
test2 asajndjad
dwjfewygfwkcm wdjcwcnwnc

Now if want to look for string test in above Input_file then example of UUOC is as follows.

cat Input_file | grep "test"
test test test1
test2 asajndjad

Above will read the Input_file by cat command then it will push the standard output of this command as standard input to next grep command(that's why pipe | is used here) which will then read file and search for string test then. So point here is unnecessarily we are using cat when grep itself is capable to read Input_file from itself. So by directly using grep here to read and search Input_file we could save sometime(depending upon you Input_file size) and processes too.

Now following is the example of grep which is capable of reading the Input_file by itself and search a string too.

grep "test"  Input_file
test test test1
test2 asajndjad

I hope this helps you.

Thanks,
R. Singh

I guess this is a typo, isn't it? Should be \; in the end, not +

Hi rovf,
No!

find ... -exec command {} \;

Invokes command once for each file meeting the criteria specified by ..., while:

find ... -exec command {} +

invokes command once for a group of files meeting the criteria specified by ... with the number of files in the group being determined by the size of the environment passed to command, the length of command, and the length of the pathnames of the files being passed to command.

The difference between \; and + terminating a find -exec primary is described above.

The command given to a:

find -exec command {} \;

or:

find -exec command {} +

primary is something that can be executed by find . The find utility doesn't know how to execute pipelines. You could exec sh -c "pipeline" but the shell's -c operand has to be a single argument so the command you give find won't see the {} (which it will replace with a the name of a found file) as a separate argument, so it might not replace it with the name of a found file. (The standards leave it unspecified whether or not find replaces {} if that string does not appear as a separate argument.)

If you need to use find -exec to run a pipeline (which you DO NOT need to do in this case and doing so would involve running a shell and a cat and a grep when all you need is grep ) would be to put the pipeline in a separate shell script such as scriptname containing:

#!/bin/your_shell
cat "$1" | grep -F 'make'

make it executable with:

chmod +x scriptname

and move it to some directory in the list of directories in your $PATH environment variable. Then you can execute it with:

find ... -exec scriptname {} \;

If a small number of files is involved, you might get away with having scriptname contain:

#!/bin/your_shell
cat "$@" | grep -F /dev/null 'make'

and executing it with:

find ... -exec scriptname {} +

but if the arguments passed to scriptname along with scriptname and its environment is shorter than the command that scriptname will execute, scriptname may fail with a E2BIG error condition unless you build extra argument loop processing to guarantee that the commands being executed by your script won't fail due to the size of the argument list and environment list your shell will be passing to a member of the exec family of functions to run that stage in your pipeline.

Using:

grep 'word' "file"

invokes one utility, reads all of the data in file and writes lines in file that contain word while:

cat "file" | grep 'word'

invokes two utilities, reads all of the data in file , writes all of the data in file reads all of the data in file again and then writes all of the lines in file that contain word . So the useless use of cat creates an unneeded process, forces the system to read and write the entire contents of file an extra unneeded time wasting system memory, wasting I/O bandwidth, and wasting CPU cycles that could be used by other processes to do something useful. Use cat if you need it; DO NOT use cat if you don't need it! Using cat when you don't need it takes longer for you to get the results you want and wastes system resources that you or someone else on your system might need to use to get something done that needs the cycles you are wasting.

1 Like

Don, I think you mean

#!/bin/your_shell
cat "$@" | grep -F 'make'

grep reads from the pipe stream, and cannot see the file names.
It is equivalent to

grep -F -h 'make' "$@"

where grep sees the file names and the -h prevents printing them as a prefix.
Unfortunately grep by default prefixes the file names when it has got two or more file names. But not with one file name.
And there is no option to enforce the prefix. Therefore the /dev/null trick: it is one more file name. Nothing is ever found there, but it enforces the prefix. For example

grep -F 'make' /dev/null "$@"

will prefix the file name even if the "$@" has only one argument (file name).

Are {} \+ and {} + same?

Hi MadeInGermany,
Yes. I agree with all of the above. Note that the code the user posted was:

-exec cat {} | grep "make"

and I was demonstrating how to get the same output. And, I later suggested using the /dev/null trick because I assume (without any indication from the original poster of this thread) that just having a list of matching lines without knowing what files they came from would not be very useful. I should have made clear in post #7 that the discussion I included in post #3 still applied.

Yes. But {} ; cannot be used instead of {} \; . The semicolon has special meaning to the shell and must be escaped to make the shell pass the semicolon to find . The plus-sign is not special to the shell in this context, so it does not need to be escaped.

find . -type f -exec grep "make" {} \;

Is it possible to get the same output with find without using -exec grep? Does find have anything to search string in files?

I don't know of any version of find that has a primary that searches file contents. And you haven't bothered to tell us what operating system you're using, so we can't check the find man page on your system to see if it has any extensions that might provide that capability. Why don't you read the find man page on your system and see if you can find any primaries or options that would do what you want with the find that is available on your system?

In addition to what DonCragun said, you could also use

grep -rF make .