40

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

example:

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?

1
  • 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, 2012 at 19:09

4 Answers 4

35

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" {} \+
13
  • 1
    -L already has negotiation, because it means files _without_ match. Therefore you don't need -v option here.
    – rush
    Jul 5, 2012 at 19:38
  • 3
    not with the extra + at the end. Jul 5, 2012 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, 2012 at 5:03
  • 8
    @user946850 -exec ... {} \+ is not equivalent to xargs. Please read the findutils documentation! (I worked quite hard on it!) Jul 6, 2012 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. Jul 7, 2012 at 17:07
12

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.


EDIT::

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.

5
  • 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, 2012 at 19:14
  • If the author of the question just wants to find filenames, shouldn't the grep be 'grep -l -v'?
    – user732
    Jul 5, 2012 at 19:14
  • he's looking for specific contents in the *.java files
    – lornix
    Jul 5, 2012 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. Jul 6, 2012 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, 2012 at 15:00
4

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
2
  • 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, 2012 at 12:06
  • @Hyangelo \s is a GNU grep extension, which I think is synonymous with [[:space:]]. Jul 6, 2012 at 17:40
3
find -type f -name '*.java' 2>&1 | grep -v "something something"

what you're "grepping out" is displayed to stderr, not stdout.

the command above redirect stderr to stdout

Source

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .