36

How do I recursively view a list of files that has one string and specifically doesn't have another string? Also, I mean to evaluate the text of the files, not the filenames.


Conclusion:

As per comments, I ended up using:

find . -name "*.html" -exec grep -lR 'base\-maps' {} \; | xargs grep -L 'base\-maps\-bot'

This returned files with "base-maps" and not "base-maps-bot". Thank you!!

6
  • 1
    Easiest way is using grep -l to list files matching a pattern, then pipe that to grep -v. Commented Feb 14, 2011 at 6:50
  • Belongs on superuser.com Commented Feb 14, 2011 at 6:52
  • 2
    Mikel: However, it will pipe only the file names to the other grep which in turn can only filter from those file names and not file contents. Commented Feb 14, 2011 at 6:52
  • I've tried that, and agree with Alan that it only filters filenames. Thus, it doesn't accomplish what I hope. Commented Feb 14, 2011 at 6:56
  • @Alan Can't you pass grep a fileset, and if so, can't you pass in a fileset based on another grep? This is just me imagining possibilities, but if anyone knew how to do it, that'd be awesome :D Commented Feb 14, 2011 at 6:57

6 Answers 6

50

Try this:

grep -rl <string-to-match> | xargs grep -L <string-not-to-match>

Explanation: grep -lr makes grep recursively (r) output a list (l) of all files that contain <string-to-match>. xargs loops over these files, calling grep -L on each one of them. grep -L will only output the filename when the file does not contain <string-not-to-match>.

Sign up to request clarification or add additional context in comments.

7 Comments

Should the grep strings be quotationed or escaped? Yes and no.
Also, is there a good way to make the process more efficient by only including .html files?
@Matrym find . -name "*.html" -exec grep -l <string-to-match> {} \; | xargs grep -L <string-not-to-match>
@chrisaycock adding R and escaping hyphens, does this look right? find . -name "*.html" -exec grep -lR 'base\-maps' {} \; | xargs grep -L 'base\-maps\-bot'
Fantastic. Awesome awesome awesome.
|
3

The use of xargs in the answers above is not necessary; you can achieve the same thing like this:

find . -type f -exec grep -q <string-to-match> {} \; -not -exec grep -q <string-not-to-match> {} \; -print

grep -q means run quietly but return an exit code indicating whether a match was found; find can then use that exit code to determine whether to keep executing the rest of its options. If -exec grep -q <string-to-match> {} \; returns 0, then it will go on to execute -not -exec grep -q <string-not-to-match>{} \;. If that also returns 0, it will go on to execute -print, which prints the name of the file.

As another answer has noted, using find in this way has major advantages over grep -Rl where you only want to search files of a certain type. If, on the other hand, you really want to search all files, grep -Rl is probably quicker, as it uses one grep process to perform the first filter for all files, instead of a separate grep process for each file.

Comments

1

These answers seem off as the match BOTH strings. The following command should work better:

grep -l <string-to-match> * | xargs grep -c <string-not-to-match> | grep '\:0'

Comments

1

Here is a more generic construction:

find . -name <nameFilter> -print0 | xargs -0 grep -Z -l <patternYes> | xargs -0 grep -L <patternNo>

This command outputs files whose name matches <nameFilter> (adjust find predicates as you need) which contain <patternYes>, but do not contain <patternNo>.

The enhancements are:

  • It works with filenames containing whitespace.
  • It lets you filter files by name.

If you don't need to filter by name (one often wants to consider all the files in current directory), you can strip find and add -R to the first grep:

grep -R -Z -l <patternYes> | xargs -0 grep -L <patternNo>

Comments

0

find . -maxdepth 1 -name "*.py" -exec grep -L "string-not-to-match" {} \;

This Command will get all ".py" files that don't contain "string-not-to-match" at same directory.

Comments

0

To match string A and exclude strings B & C being present in the same line I use, and quotes to allow search string to contain a space

grep -r <string A> | grep -v -e <string B> -e "<string C>" | awk -F ':' '{print $1}'

Explanation: grep -r recursively filters all lines matching in output format

filename: line

To exclude (grep -v) from those lines the ones that also contain either -e string B or -e string C. awk is used to print only the first field (the filename) using the colon as fieldseparator -F

1 Comment

Please add a little explanation for your code for further information.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.