14

I use a command to recursively find files containing a certain string1:

find . -type f -exec grep -H string1 {} \; 

I need to find files containing multiple strings, so the command should return those containing all strings. Something like this:

find . -type f -exec grep -H string1 AND string2 {} \; 

I couldn't find a way. The strings can be anywhere in the files. Even a solution for only two strings would be nice.

2
  • I could translate your question to an "egrep" question: the command egrep -l "string1|string2" gives all the files which contain string1 OR string2, in case a parameter exist in order to make egrep -l "string1 <parameter> string2"give the files which contain string1 AND string2, your question would be solved. (I don't know if such a parameter exists, though) Commented Oct 4, 2016 at 12:07
  • There could theoretically be a union operator & just like | corresponds to intersection, but no common regex tools implement this. The easy fix is awk '/pattern1/ && /pattern2/' so there is already a simple way to do exactly this, albeit not with grep. Commented Sep 24, 2021 at 10:46

8 Answers 8

17

you can also try this;

find . -type f -exec grep -l 'string1' {} \; | xargs grep -l 'string2' 

this shows file names that contain string1 and string2

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

2 Comments

This is a very nice solution. I suggest surrounding {} with single quotes and using the -d argument to xargs to avoid errors due to filenames with spaces, like this: find . -type f -exec grep -l 'string1' '{}' \; | xargs -d '\n' grep -l 'string2'
I would also suggest using a + instead of \; to end the -exec option of the find command. This will search as many files as possible with a single grep command, reducing the number of processes that will run, resulting in a much quicker result.
6

You can chain your actions and use the exit status of the first one to only execute the second one if the first one was successful. (Omitting the operator between primaries defaults to -and/-a.)

find . -type f -exec grep -q 'string1' {} \; -exec grep -H 'string2' {} \; 

The first grep command uses -q, "quiet", which returns a successful exit status if the string was found.

To collect all files containing string1 and then run the search for string2 with just a single invocation of grep, you could use -exec ... {} +:

find . -type f -exec grep -q 'string1' {} \; -exec grep 'string2' {} + 

Comments

5

with GNU grep

grep -rlZ 'string1' | xargs -0 grep -l 'string2' 


from man grep

-r, --recursive

Read all files under each directory, recursively, following symbolic links only if they are on the command line. Note that if no file operand is given, grep searches the working directory. This is equivalent to the -d recurse option.

-Z, --null Output a zero byte (the ASCII NUL character) instead of the character that normally follows a file name. For example, grep -lZ outputs a zero byte after each file name instead of the usual newline. This option makes the output unambiguous, even in the presence of file names containing unusual characters like newlines. This option can be used with commands like find -print0, perl -0, sort -z, and xargs -0 to process arbitrary file names, even those that contain newline characters.

Comments

5

Amazed that this old question lacks the obvious simple Awk solution:

find . -type f -exec awk '/string1/ && /string2/ { print; r=1 } END { exit 1-r }' {} \; 

The trickery with the r variable is just to emulate the exit code from grep (zero means found, one means not; if you don't care, you can take that out).

For efficiency, maybe switch from -exec ... {} \; to -exec ... {} + though then you might want to refactor the Awk script a bit (either throw out the exit code, or change it so the exit code indicates something like "no files matched" vs "only some files matched" vs "all files matched"?)

The above code looks for files which contain both strings on the same line. The case of finding them on any lines is an easy change.

awk '/string1/ { s1=1 } /string2/ { s2=1 } s1 && s2 { print FILENAME; exit } END { exit(1 - (s1 && s2)) }' file 

This just prints the name of the file, and assumes that you have a single input file. For processing multiple files, refactor slightly, to reset the values of s1 and s2 when visiting a new file:

awk 'FNR == 1 { s1 = s2 = 0 } /string1/ { s1 = 1 } /string2/ { s2 = 1 } s1 && s2 { r=1; print FILENAME; nextfile } END { exit 1-r }' file1 file2 file3 ... 

Some ancient Awk versions might not support nextfile, though it is now in POSIX.

Comments

1

Answer

As you can see from the other answers on this page, there are several command-line tools that can be used to perform conjunctive searching across files. A fast and flexible solution that has not yet been posted is to use ag:

ag -l string1 | xargs ag -l string2 

Useful variations

For case-insensitive searching, use the -i option of ag:

ag -il string1 | xargs ag -il string2 

For additional search terms, extend the pipeline:

ag -l string1 | xargs ag -l string2 | xargs ag -l string3 | xargs ag -l string4 

Comments

1
grep -rlZ string1 | xargs -0 grep -l string2 

If your patterns are fixed strings, we can speed up the command by adding -F to grep:

grep -rlZF string1 | xargs -0 grep -lF string2 

Comments

0
The following Bash script I wrote could be useful here. Edit to refer to your own home directories etc., in accordance with your own preferences. The script uses the file ".lookfor.txt" to contain the strings you want to search for (and you can have an arbitrary number of strings in that file, separated by newlines), and writes the list of files to the file ".found.txt", which can then be perused at leisure. Code now follows: #!/bin/bash #MyFind.sh #copy the target folder into a variable of my choice target=$1 #create the full path to the target directory #(I'm mostly interested in files on my SD card, and #I can change this if needed in the future - found #the path I wanted using lsblk) srchdir=/media/calilasseia/DATA2/${target} #Put the path in quotes in case the target folder #has spaces or other odd characters in the folder #specification, so that cd is passed a proper path #argument ... cd "$srchdir" #Now perform the search I want! #So that I can search for multiple text strings #at once, I use the -f option for grep, which #searches for matches with every string in the #file ".lookfor.txt" below. If I want to change #the search strings, I edit that file and re-run #the script. #Also, I direct the output to a text file, namely ".found.txt" #so that i can read it at leisure. grep -f /home/calilasseia/Documents/.lookfor.txt -i -l *.txt > /home/calilasseia/Documents/.found.txt #For those who want this feature, display the list of files #containing the desired text in the terminal. I don't usually #need this, but sometimes it could be useful if the script #encounters a weird edge case I hadn't anticipated. cat /home/calilasseia/Documents/.found.txt Example usage: MyFind "TmpData/FB Posts" Note to novices: once you've written the script, use "chmod u+x MyFind.sh" to inform the operating system that this is an executable script (assuming you called the script "MyFind.sh" as I did of course!) 

Comments provided for those who are new to bash scripts.

Hope this proves useful!

Comments

-1

This was the easiest for me (thanks to ChatGPT). You can add as many strings as you want

find /path/to/directory -type f -name '*string1*' -name '*string2*' 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.