I'm trying to find all files that are of a certain type and do not contain a certain string. I am trying to go about it by piping find to grep -v


find -type f -name '*.java' | xargs grep -v "something something"

This does not seem to work. It seems to be just returning all the files that the find command found. What I am trying to do is basically find all .java files that match a certain filename(e.g. ends with 'Pb' as in SessionPb.java) and that do not have an 'extends SomethingSomething" inside it.

My suspicion is that I'm doing it wrong. So how should the command look like instead?

  • you might add what makes you think it didn't work. maybe your grep expression is too explicit? needs a '-i' for case insensitivty? See my answer below too... – lornix Jul 5 '12 at 19:09

There is no need in xargs here. Also you need to use grep with -L option (files without match), cause otherwise it will output the file content instead of its name, like in your example.

find . -type f -iname "*.java" -exec grep -L "something somethin" {} \+
  • 1
    -L already has negotiation, because it means files _without_ match. Therefore you don't need -v option here. – rush Jul 5 '12 at 19:38
  • 3
    not with the extra + at the end. – lynxlynxlynx Jul 5 '12 at 22:33
  • 2
    @user946850 every time I write find -exec \+ somebody writes me that it's much better to use xargs. why nobody looks man before writing a comment? (: – rush Jul 6 '12 at 5:03
  • 6
    @user946850 -exec ... {} \+ is not equivalent to xargs. Please read the findutils documentation! (I worked quite hard on it!) – James Youngman Jul 6 '12 at 14:57
  • 2
    @user946850 yes. One of the differences is that (by default) xargs processes its input and separates arguments on white space. Quotes are also special (by default) to xargs. Neither of these things is true for -exec ... {} +. Old versions of xargs used to consider the underscore as an EOF indicator, but this isn't the case any more. – James Youngman Jul 7 '12 at 17:07

You've almost got it really...

find . -type f -iname "*.java" -print0 | xargs -0 grep -v "something something"

The dot '.' says to start from here. (yours implies it.. but never assume).

-iname uses case-insensitive search, just in case (or just in no-case).
-print0 sends filenames to xargs with a trailing \x00 character, which prevents problems with filenames having spaces in them.

the '-0' on xargs says to expect filenames ending with \x00 instead of returns.

and your grep command...

Pretty much got it.


From your update:

find . -type f -iname "*pb.java" -print0 | xargs -0 grep -iL "something"

should help. (Added -L from @rush's answer, good job)

I get the idea that your grep needs either the '-i' option, or to be less explicit.

Try the command in parts... does THIS output filenames that seem proper?

find . -type f -iname "*pb.java"

If so, then your problem is likely either your grep search pattern is not matching (spelling error? it happens!), or there just aren't any matches.

Absolute worst case:

grep -riL "something" *

will do a LOT more work searching everything, but should give you some output.

  • I tried your modifications and I'm still not getting the result I am expecting. I'll try updating the question to make it clearer. – Hyangelo Jul 5 '12 at 19:14
  • If the author of the question just wants to find filenames, shouldn't the grep be 'grep -l -v'? – Bruce Ediger Jul 5 '12 at 19:14
  • he's looking for specific contents in the *.java files – lornix Jul 5 '12 at 19:17
  • xargs -0 grep -v "something something" should be xargs -0 grep -v "something something" /dev/null otherwise you will get odd results when find produces no matching files. – James Youngman Jul 6 '12 at 14:58
  • {Grin} yeah, somewhere in there the logic got all hairy. Nothing like an inversed false logic multiple test to make your head hurt. – lornix Jul 6 '12 at 15:00

The computer is being a computer: it's doing what you told it to do instead of what you wanted it to do.

grep -v "something something" prints all lines that do not contain something something. For example, it prints two lines among the following three:

hello world
this is something something
something else

To print files that do not contain extends SomethingSomething anywhere, use the -L option:

grep -L -E 'extends[[:space:]]+SomethingSomething' FILENAME…

Some versions of grep do not have the -L option (it is not specified by POSIX). If yours doesn't, have it print nothing, and use the return code to have the calling shell do whatever it should do instead.

grep -q -E 'extends[[:space:]]+SomethingSomething' FILENAME ||
echo "$FILENAME"

Alternatively, use awk.

awk '
    FNR == 1 && NR != 1 && !found { print fn }
    FNR == 1 { fn = FILENAME; found = 0; }
    /extends[[:space:]]+SomethingSomething/ { found = 1 }
    END { if (fn != "" && !found) print fn }

On Linux or Cygwin (or other system with GNU grep), you don't need to use find, as grep is capable of recursing.

grep -R --include='*.java' -L -E 'extends[[:space:]]+SomethingSomething'

If your shell is ksh or bash or zsh, you can make the shell do the filename matching. On bash, run set -o globstar first (you can put this in your ~/.bashrc).

grep -L -E 'extends[[:space:]]+SomethingSomething' **/*.java
  • Wow this is even better. I was using 'extends(/s)+SomethingSomething' which seemed to work as far as I could tell. Is there any difference with this syntax with the one specified in the Extended RegEx version? – Hyangelo Jul 6 '12 at 12:06
  • @Hyangelo \s is a GNU grep extension, which I think is synonymous with [[:space:]]. – Gilles 'SO- stop being evil' Jul 6 '12 at 17:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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