208

One of my favorite BASH commands is:

find . -name '*.*' -exec grep 'SearchString' {} /dev/null \;

which searches the contents of all of the files at and below the current directory for the specified SearchString. As a developer, this has come in handy at times.

Due to my current project, and the structure of my codebase, however, I'd like to make this BASH command even more advanced by not searching any files that are in or below a directory that contains ".svn", or any files that end with ".html"

The MAN page for find kind of confused me though. I tried using -prune, and it gave me strange behavior. In an attempt to skip only the .html pages (to start), I tried :

find . -wholename './*.html' -prune -exec grep 'SearchString' {} /dev/null \;

and did not get the behavior I was hoping for. I think I might be missing the point of -prune. Could you guys help me out?

Thanks

Oliver Salzburg
  • 86,445
  • 63
  • 260
  • 306
Cody S
  • 2,334
  • 4
  • 16
  • 20
  • 2
    Just fyi: `find` is not a build-in bash command but a separate program – jhenninger Mar 06 '12 at 00:54
  • 3
    You can search inside file with `grep -rl 'SearchString'` – emanuele Nov 10 '14 at 14:43
  • @emanuele Hi, welcome to SuperUser (and the Stack Exchange network). This is a question I asked, and that was answered, 2 1/2 years ago. Typically, if you would like to add an answer to the question, please do so by scrolling to the bottom and answering there, instead of in a comment. Since this question already has an accepted answer (the one with the green checkmark), it's unlikely that your answer is going to get much attention, however. FYI. – Cody S Nov 10 '14 at 22:52
  • 2
    Hi, it is not an answer to your question. It is only a tip, as you stated in preamble that use `find` to search inside a file. – emanuele Nov 11 '14 at 12:48
  • 3
    FWIW, `-name '*.*'` does not find *all* files: only those with a `.` in their name (the use of `*.*` is typically an DOS-ism, whereas in Unix, you normally use just `*` for that). To really match them all, just remove the argument altogether: `find . -exec ...`. Or if you want to only apply grep to files (and skip directories) then do `find . -type f -exec ...`. – Stefan Feb 20 '16 at 01:49

5 Answers5

292

You can use the negate (!) feature of find to not match files with specific names:

find . ! -name '*.html' ! -path '*.svn*' -exec grep 'SearchString' {} /dev/null \;

So if the name ends in .html or contains .svn anywhere in the path, it will not match, and so the exec will not be executed.

Stephen S
  • 618
  • 7
  • 22
Paul
  • 59,223
  • 18
  • 147
  • 168
  • 1
    Should I still specify -name '*.*' somewhere in there? Would I do that before, or after the negations? – Cody S Mar 06 '12 at 01:21
  • Was the intention of your `*.*` match to ensure it only matched files containing a `.`? Find will match all files in the absence of a `name` directive, so the above will match everything except html and svn – Paul Mar 06 '12 at 01:23
  • 5
    I think you want `-wholename '*.svn*'` rather than `-name`. – fuenfundachtzig May 09 '15 at 22:35
  • @fuenfundachtzig `wholename` just does the match against the path as well as the file name doesn't it? How would that improve the answer? If it does in a way I am missing, feel free to edit the answer and enhance it. – Paul May 10 '15 at 11:55
  • 2
    Yes, it does, so that the `.svn` directories are excluded from the search results. – fuenfundachtzig May 10 '15 at 12:41
  • 1
    @Paul The desired effect is to exclude "files that are in or below a *directory* that contains `.svn`", so `path` (or `wholename`, but `path` is more portable) is more accurate than `name` for the answer. They questioner doesn't appear to have any files with `.svn` in the name. – Stephen S Aug 27 '16 at 23:41
  • I tried this with `-exec echo mv {} ..` and it told me it was going to try to move `.` to `..`. Do you need to do something to avoid that? – Noumenon Oct 24 '16 at 02:42
  • 2
    @Noumenon `! -name '.'` should exclude `.` from the find results. – Paul Oct 24 '16 at 02:57
  • 1
    Is there a description of which versions of `find` this '!' syntax is compatible with? It seems to not work with my Ubuntu 14.04 server – user5359531 Oct 02 '18 at 00:34
13

I've had the same issue for a long time, and there are several solutions which can be applicable in different situations:

  • ack-grep is a sort of "developer's grep" which by default skips version control directories and temporary files. The man page explains how to search only specific file types and how to define your own.
  • grep's own --exclude and --exclude-dir options can be used very easily to skip file globs and single directories (no globbing for directories, unfortunately).
  • find . \( -type d -name '.svn' -o -type f -name '*.html' \) -prune -o -print0 | xargs -0 grep ... should work, but the above options are probably less of a hassle in the long run.
l0b0
  • 7,171
  • 4
  • 33
  • 54
11

The following find command does prune directories whose names contain .svn, Although it does not descend into the directory, the pruned path name is printed ...(-name '*.svn' is the cause!) ..

You can filter out the directory names via: grep -d skip which silently skips such input "directory names".

With GNU grep, you can use -H instead of /dev/null. As a slight side issue: \+ can be much faster than \;, eg. for 1 million one-line files, using \; it took 4m20s, using \+ it took only 1.2s.

The following method uses xargs instead of -exec, and assumes there are no newlines \n in any of your file names. As used here, xargs is much the same as find's \+.

xargs can pass file-names which contain consecutive spaces by changing the input delimiter to '\n' with the -d option.

This excludes directories whose names contain .svn and greps only files which don't end with .html.

find . \( -name '*.svn*' -prune  -o ! -name '*.html' \) |
   xargs -d '\n' grep -Hd skip 'SearchString'
Peter.O
  • 3,023
  • 1
  • 28
  • 30
5

By using "-not" and "-and":

find . -type f \( \
    -not -name "${1}" \
    -and -not -name "${2}" \
\)

In one line:

find . -type f \( -not -name "${1}" -and -not -name "${2} \)
3

This example excludes files that have the "test" in their names from the search. The search itself looks for "ProductReplacement" for XML files only.

find . ! -name '*test*.*' -name '*.xml' -exec grep -i 'ProductReplacement' {} \; -print

You can specify more exclusion patetrns with additional

! -name 'file_pattern' entries.

George Smith
  • 231
  • 2
  • 7