46

I am in a folder with lots of .txt files, I would like to find all the files which contain stringA but don't contain stringB (they are not necessarily in the same line). Does anyone know how to do this?

don_crissti
  • 79,330
  • 30
  • 216
  • 245
SoftTimur
  • 657
  • 1
  • 6
  • 6

3 Answers3

47

As long as your filenames do not contain spaces, tabs, newline (assuming an unmodified $IFS) or wildcard characters and don't start with -, and if your grep supports the -L option, you can do it as follows:

$ cat file1
stringA
stringC
$ cat file2
stringA
stringB
$ grep -L stringB $(grep -l stringA file?)
file1

The grep executed in the subshell $(), will print all filenames which contain stringA. This filelist is input for the main grep command, which lists all files that do not contain stringB.

From man grep

  -v, --invert-match
          Invert the sense of matching, to select non-matching lines.  (-v is specified by POSIX.)
  -L, --files-without-match
          Suppress normal output; instead print the name of each input file from which no output would normally have been printed.  The scanning will stop on the first match.
  -l, --files-with-matches
          Suppress normal output; instead print the name of each input file from which output would normally have been printed.  The scanning will stop on the first match.  (-l is specified by POSIX.)
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
Bernhard
  • 11,992
  • 4
  • 59
  • 69
  • Thank you... I have lots of files, your solution does give almost all the good results, but I see also several `grep: alias: No such file or directory`, do you know why? – SoftTimur May 08 '14 at 06:52
  • Do you have a spaces in your filenames? Or aliases defined for grep? – Bernhard May 08 '14 at 07:06
  • Sometimes there are spaces in the filenames... What should I do to cover them? – SoftTimur May 08 '14 at 07:07
  • @debai Sorry, I really don't want to change their filename, is there anything I can do on the level of the commands... – SoftTimur May 08 '14 at 07:12
  • @SoftTimur grep -L stringB $(grep -l stringA `ls -l | tail -n+2 | awk '{print $NF}' | sed -e 's/\s/\\ /g'` – debal May 08 '14 at 07:25
  • Sorry, how could i merge this with `grep -L stringB $(grep -l stringA file?)`? – SoftTimur May 08 '14 at 07:25
  • @SoftTimur Please check my answer for proper formatting of the code.. :) – debal May 08 '14 at 07:27
  • 1
    A whitespace-safe version should be something like `while read -rd $'\0' file; do grep -L 'stringB' "$file"; done < <(find . -type f -exec grep -Zl 'stringA' {} \;)` – steeldriver May 08 '14 at 19:48
  • @SoftTimur - Use his command and just precede it with: `IFS='\n' ;... ` where the `\n` is a literal newline. That will handle the spaces problem anyway. – mikeserv Jun 05 '14 at 19:22
  • useful to know if you use std::vector without (#)include `grep -L "" $(grep -rl std::vector *) | grep -v .so$ | grep -v .o$` – fiorentinoing Apr 20 '18 at 14:46
  • If the subshell returns no results, grep will wait forever since there is no output. I suggest using `grep -lR 'wantedString' . | xargs -r grep -L 'non-wanted-string'` The key being `-r` passed to xargs, which does not run the command if no arguments are available – Cec May 23 '22 at 07:03
  • @debal [piping `ls` like that is a bad idea](https://unix.stackexchange.com/q/128985/44425). `find` is the standard way – phuclv May 27 '23 at 02:21
6

With GNU tools:

grep -lZ stringA ./*.txt |
  xargs -r0 grep -L stringB

-L, -Z, -r, -0 are GNU extensions sometimes but not always found in some other implementations.

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
0
#run loop for each file in the directory
for i in `ls -l | tail -n+2 | awk '{print $NF}'` ; do
   #check if file contains "string B" 
   #if true then filename is not printed
   if [[ `egrep "string B" $i | wc -l` -eq 0 ]] ; then
      #check if file contains "string A"
      #if false then file name is not printed
      if [[ `egrep "string A" $i | wc -l` -gt 0 ]] ; then
         #file name is printed only if "string A" is present and "string B" is absent
         echo $i
      fi
   fi
done

After checking Bernhard's answer:

grep -Le "string B" $(grep -le "string A" `ls`)

If file name contains spaces:

grep -L stringB $(grep -l stringA `ls -l | tail -n+2 | awk '{print $NF}' | sed -e 's/\s/\\ /g'`
debal
  • 3,664
  • 5
  • 17
  • 18
  • Maybe someone else can give a better solution to your space problem, but this is all I could think of at the moment. :) Cheers – debal May 08 '14 at 07:30
  • 1
    People will complain that you are parsing `ls` :) – Bernhard May 08 '14 at 07:33
  • Thank you, but for the space problem, your commend still gives me errors like `grep: def.txt: No such file or directory` for filenames like `abc def.txt`. – SoftTimur May 08 '14 at 07:35