744

I'd like to find the files in the current directory that contain the text "chrome".

$ find . -exec grep chrome
find: missing argument to `-exec'

What am I doing wrong?

Gilles 'SO- stop being evil'
  • 766,554
  • 187
  • 1,586
  • 2,083
ripper234
  • 29,573
  • 43
  • 82
  • 90

6 Answers6

1123

You missed a ; (escaped here as \; to prevent the shell from interpreting it) or a + and a {}:

find . -exec grep chrome {} \;

or

find . -exec grep chrome {} +

find will execute grep and will substitute {} with the filename(s) found. The difference between ; and + is that with ; a single grep command for each file is executed whereas with + as many files as possible are given as parameters to grep at once.

muru
  • 65,449
  • 10
  • 184
  • 275
bmk
  • 11,354
  • 1
  • 13
  • 5
  • 41
    If you use the \; ending construct grep is passed one file at a time, so it doesn't display the file name by default, only the matched lines. To get a file list instead add use `grep -ls` inside of the find construct. – Caleb May 10 '11 at 06:55
  • 16
    `find . -exec grep foo {} +` will show you output like this `./dir/file.py:from foo import bar` – s g Apr 17 '15 at 20:30
  • 14
    `find . -exec grep foo {} \;` will show you output like this `from foo import bar` – s g Apr 17 '15 at 20:31
  • 12
    `find . -exec grep -l foo {} +` will show you output like this `./dir/file.py` – s g Apr 17 '15 at 20:32
  • 11
    `find . -exec grep -l foo {} \;` will show you output like this `./dir/file.py` – s g Apr 17 '15 at 20:33
  • 2
    Or you could just `find . -print -exec grep chrome \;` to have `find` print out the file name... – miken32 Jan 27 '17 at 17:48
  • Similarly `find . -type f -execdir grep foo {} +` will show you output like this `./file.py:from foo import bar` – Mark Booth Aug 16 '17 at 09:21
  • 1
    @Caleb Or just add -H to the grep command to specifically include the file name in the result lists, giving the same formatting as grepping directly on multiple files: `file ./ -exec grep -H chrome {} \;` – Svend Hansen Oct 20 '17 at 09:28
  • 3
    In `fish` shell you need to escape the braces too `find . -exec grep chrome \{\} \;` – Tamlyn Dec 08 '17 at 15:33
  • @Caleb If getting the list of files is the goal, I think one can do away with `find` and just use `grep`: `grep -ls 'chrome' *` – flow2k Sep 14 '18 at 06:34
  • 1
    @SvendHansen Do you mean `find` instead of `file`? – flow2k Sep 14 '18 at 06:36
  • Yes, indeed. Good spot, @flow2k :) (Unfortunately, I can't edit it now) – Svend Hansen Sep 14 '18 at 12:46
  • this won't work if you don't put a space before the `\;` or `+` – Joakim Jun 07 '21 at 07:52
64

You don't need to use find for this at all; grep is able to handle opening the files either from a glob list of everything in the current directory:

grep chrome *

...or even recursively for folder and everything under it:

grep chrome . -R
Caleb
  • 66,766
  • 16
  • 195
  • 220
  • 29
    grep will choke if the expansion goes over ARG_MAX. -R will visit everything while using find one can more easily add primitives to exclude certain files (-name, etc) or not even visit subtrees (-prune). – Mel May 09 '11 at 23:58
  • 9
    Good points @Mel. My point was that in all likelihood the asking party was making things more complex than they needed to be by introducing `find` when `grep` could do the job, but in some cases it would be more effective to to use find to fine tine the file list before going out to grep. – Caleb May 10 '11 at 06:53
  • 4
    @Mel `grep` does not choke in such a case, `exec` does. – Chris Down Mar 26 '13 at 06:21
  • Works only in case all files are in the same directory, not when they are spread over subdirectories. – Yaba Jun 06 '18 at 14:23
  • 3
    @Yaba My answer states how to handle the case of files spread over subdirectories. – Caleb Sep 14 '18 at 12:56
  • While the top voted answer (bmk's) does a better job of specifically answering the question that was asked, this answer does provide an excellent alternative to more efficiently (six times faster and with fewer processes in a single random test I did) achieve the same results. (bmx's answer solved my related problem, I forgot to escape the semicolon, but this answer is certainly useful.) – DaCheetah May 03 '20 at 23:53
  • @ChrisDown, both grep and exec had two examples, and in each case, one would choke and the other would not. grep chrome * and find . -exec grep chrome {} + will expand into roughly the same thing (if there are no subdirectories), and given enough files, would exceed ARG_MAX. Given a -R or exec with a semicolon, and there is no risk of reaching ARG_MAX. (But as Mel pointed out, find has more controls to limit the files searched.) – DaCheetah May 04 '20 at 00:04
  • 2
    @DaCheetah This is a misunderstanding -- the above comment has nothing to do with `find -exec`. `exec` here refers to libc functions which directly call the `execve` syscall (eg. `exec{ve,vp,lp,...}`), not `find -exec` which does its own manipulation prior. The point is that `grep` never even sees the arguments to choke on them, it's the kernel that rejects it. – Chris Down May 04 '20 at 15:31
23
find . | xargs grep 'chrome'

you can also do:

find . | xargs grep 'chrome' -ls

The first shows you the lines in the files, the second just lists the files.

Caleb's option is neater, fewer keystrokes.

Gilles 'SO- stop being evil'
  • 766,554
  • 187
  • 1,586
  • 2,083
Mathew
  • 363
  • 1
  • 3
  • 13
    The problem with `xargs` is that it expects its input to be quoted in a peculiar way that `find` doesn't produce. So `find … | xargs …` doesn't work if you have file names containing whitespace or `\'"`. – Gilles 'SO- stop being evil' May 10 '11 at 06:31
  • 4
    @Gilles You can get around that problem by using something like `find . | xargs -n1 -iX grep "X" 'chrome'` so that arguments are fed one at a time and quoted. Obviously this is a horribly inefficient way to handle this example, but for some situations it's nice. – Caleb May 10 '11 at 11:56
  • 1
    For completeness we should also mention the -i option for case insensitivity with 'grep'. Also there is -iname in find for case insensitivity. – Mathew May 10 '11 at 12:01
  • 14
    @Caleb: The only 100% reliable way to have `xargs` cope with Linux filenames is `find ... -print0 | xargs -0`, using NUL as separator. Alternative - `xargs -d '\n'` using newline as separator, 99% reliability. – user1686 May 10 '11 at 20:18
  • I use this often, but it will fail for very long lists of filenames, at which point find -exec becomes the winner. – Spacemoose Jul 05 '15 at 09:08
  • If you anticipate a ton of files here, you could still do: find . -print0 | xargs -I {} grep 'chrome' {} which is roughly equivalent to the -exec – Bryan Sep 09 '15 at 15:00
10

Find is one way and you can try the_silver_searcher then all you need to do is

ag chrome

It will search chrome in all files (include sub directories) and it is faster than find

Ask and Learn
  • 1,825
  • 4
  • 23
  • 33
  • There is also pt (platinum searcher, available at https://github.com/monochromegane/the_platinum_searcher) which, IMHO, does the job faster -- might not matter if there are only a few files. – Hopping Bunny Nov 14 '18 at 04:40
7

Here's an example of how I usually use find/exec...

find  . -name "*.py" -print -exec fgrep hello {} \;

This searches recursively for all .py files, and for each file print(s) out the filename and fgrep's for 'hello' on that (for each) file. Output looks like (just ran one today):

./r1.py
./cgi-bin/tst1.py
print "hello"
./app/__init__.py
./app/views.py
./app/flask1.py
./run.py
./tst2.py
print "hello again"
Jeff Schaller
  • 64,162
  • 33
  • 104
  • 234
jon
  • 71
  • 1
  • 1
  • 1
    not to mess with your workflow, but you might like: `find . -name "*.py" -exec fgrep -l hello {} \;` -- it'll print the filenames of files that match, and nothing else – Jeff Schaller Aug 23 '17 at 16:19
5

To see list of files instead of lines:

grep -l "chrome" *

or:

grep -r -l "chrome" .