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?

Rui F Ribeiro
  • 55,929
  • 26
  • 146
  • 227
Hyangelo
  • 503
  • 1
  • 4
  • 7
  • 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 05 '12 at 19:09

4 Answers4

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" {} \+
rush
  • 27,055
  • 7
  • 87
  • 112
  • This works but not with the negation part(e.g. when i add -v or -vi). – Hyangelo Jul 05 '12 at 19:31
  • 1
    `-L` already has negotiation, because it means `files _without_ match`. Therefore you don't need `-v` option here. – rush Jul 05 '12 at 19:38
  • 1
    `xargs` is much, much better than `find` + `-exec`. The former creates one `grep` process that gets all files as arguments, while the latter executes `grep` once for every file. – krlmlr Jul 05 '12 at 21:41
  • 3
    not with the extra + at the end. – lynxlynxlynx Jul 05 '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 06 '12 at 05:03
  • @rush: I didn't know about this variant -- it seems that this is indeed equivalent to `xargs` if not better. Thank you for the hint! (And since nobody seems to know this `\+` thingy -- is this new anyway? -- why don't you write a sentence or two about it?) – krlmlr Jul 06 '12 at 13:47
  • @user946850 it was implemented over 7 years ago. Is it still new? (: as in debian changelog: `2005-01-15: First working version of -exec ...+` – rush Jul 06 '12 at 13:54
  • 8
    @user946850 `-exec ... {} \+` is not equivalent to `xargs`. Please read the findutils documentation! (I worked quite hard on it!) – James Youngman Jul 06 '12 at 14:57
  • @rush: Well, I first got in touch with `find` like 12 years ago and since then didn't bother looking into the details of the `-exec` subcommand. So, yes, to me, it's new. And I imagine to many others, too. So, please bear with me :-) – krlmlr Jul 06 '12 at 17:33
  • @JamesYoungman: "This variant of the `-exec` action runs the specified command on the selected files, but the command line is built by appending each selected file name at the end; the total number of invocations of the command will be much less than the number of matched files. The command line is built in much the same way that xargs builds its command lines. Only one instance of `{}` is allowed within the command. The command is executed in the starting directory." Do you mean this? – krlmlr Jul 06 '12 at 17:34
  • @user946850 there is no problem. =) ps. hint. if his name is real James Youngman is findutils developer ;) btw, James thank you for you work. it is great. – rush Jul 06 '12 at 17:40
  • 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 07 '12 at 17:07
  • 1
    @JamesYoungman: Yes, I tend to use `-print0` and `-0`, respectively. Nice extension you have built there. Have you considered printing something to standard error whenever `-exec ... \;` is used? – krlmlr Jul 07 '12 at 20:33
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.

lornix
  • 3,452
  • 19
  • 29
  • 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 05 '12 at 19:14
  • If the author of the question just wants to find filenames, shouldn't the grep be 'grep -l -v'? –  Jul 05 '12 at 19:14
  • he's looking for specific contents in the *.java files – lornix Jul 05 '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 06 '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 06 '12 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
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
  • 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 06 '12 at 12:06
  • @Hyangelo `\s` is a GNU grep extension, which I think is synonymous with `[[:space:]]`. – Gilles 'SO- stop being evil' Jul 06 '12 at 17:40
2
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

Slavik
  • 191
  • 1
  • 5