0

I want to grep files which include pattern A (iwant) but I want to exclude files which contain pattern B (idontwant).

Example:

read -p "...what are you looking for: " iwant
read -p "...what should not be included: " idontwant

iwant="blue car" 
idontwant="red car"

Just assume I have the following files:

-rw-rw-r--.  1 terpentin terpentin  45 Jun  8 16:04 blue.car
-rw-rw-r--.  1 terpentin terpentin  44 Jun  8 16:05 mixed.car
-rw-rw-r--.  1 terpentin terpentin  40 Jun  8 16:04 red.car
find . -type f -print -exec cat {} \;

./mixed.car
blue car
red car
blue car

./red.car
red car
red car
red car

./blue.car
blue car
blue car
blue car

How is it possible to get only the file "./blue.car" as a result?

The original content includes hundreds of long text files, which makes it important to be as resource-efficient as possible.

2

Use

find . -type f ! -exec grep -q "$idontwant" {} ';' -exec grep -q "$iwant" {} ';' -print

or

find . -type f -exec grep -q "$iwant" {} ';' ! -exec grep -q "$idontwant" {} ';' -print
  • The terms (sometimes called “predicates”) in a find command are characterized as tests (e.g., -type f) and actions (e.g., -print and -delete).  It might be hard to figure out from the man page that -exec is both an action and a test.  So, just as
    find . -type f -mtime -30 -name '*.txt' -readable -size +5 test6 test7 test8
    successively narrows the search to files that meet all the criteria (satisfy all the specified tests), so
    find . -exec cmd1 {} ';' -exec cmd2 {} ';' -exec cmd3 {} ';' …
    finds files for which all the commands succeed.
  • Any find test can be negated (inverted) by preceding it with !.  So find . ! -type d finds plain files, symbolic links, named pipes, sockets, and device files — everything except directories.
  • Note that ! -exec grep … is not equivalent to -exec grep -v …-exec grep -v … will find files that have at least one line that doesn’t match.  ! -exec grep … will find files where no lines match.
  • The -q option to grep is officially a synonym for --quiet, but it also means quick.  It writes no output (except perhaps for error messages, as applicable), but also it exits as soon as it finds a match —  it doesn’t read every file to the end to find every match.  (Of course, if a file doesn’t contain any matches, then grep must read it in its entirety to determine that.)
  • So (TL;DR) the commands find the files for which
    grep -q "$iwant"     file
    succeeds and
    grep -q "$idontwant" file
    fails (because we preceded it with !).
  • The two commands are functionally equivalent, but may have different performance (i.e., may take different amounts of time to run).  If only a few files contain the search strings,
    find . -type f -exec grep -q "$iwant" {} ';' ! -exec grep -q "$idontwant" {} ';' –print
    will be faster, because the grep "$iwant" will eliminate most of the files.  If many of the files contain both strings, then
    find . -type f ! -exec grep -q "$idontwant" {} ';' -exec grep -q "$iwant" {} ';' –print
    will be faster, because the ! grep "$idontwant" will eliminate most of the files.
  • OK, that was fast - Thanks for the help guys. ;-) – NicoS Jun 8 at 15:18
0

With GNU grep we can perform the filename extraction with a judicious choice of regex and grep options:

$ grep -lzPsr '(?s:(?=.*blue)(?!.*red))' .

We are operating grep in slurp mode (-z) wherein the whole file is treated as one big line.

The -l will list the filenames of file(s) matching the regex.

The -r will run recursively on all files in current dir nd below.

The -s will silence the grep to not emit any warnings.

The regex will look for the presence of blue and absence of red in a file for it to say yes.

The -P invokes the Perl regex engine in grep so that we can get the advantage of pcre regexes.

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.