284

I am trying to use grep to match lines that contain two different strings. I have tried the following but this matches lines that contain either string1 or string2 which not what I want.

grep 'string1\|string2' filename

So how do I match with grep only the lines that contain both strings?

hearsaxas
  • 2,851
  • 2
  • 15
  • 4
  • 1
    Related: https://unix.stackexchange.com/questions/37313/how-do-i-grep-for-multiple-patterns – AlikElzin-kilaka Jan 03 '18 at 09:06
  • 1
    This question seems clear enough, yet most every answer here is wrong. I can't help but wonder why. It's easy enough to test: Create a file with 3 lines in it: **1.** `string1`, **2.** `string2`, **3.** `string1 string2`. Now choose the accepted answer - or virtually any answer here - and you'll see that they do not give the correct answer. Hint: A correct answer is the one whose output is line **3.**, and only line **3.**. –  Feb 17 '22 at 17:22
  • 1
    I think the title is ambiguous and could lead folks to think that matching either of the two strings could be correct. Not everyone reads the body of the question :) – jlhasson Apr 06 '22 at 04:41

24 Answers24

259

You can use

grep 'string1' filename | grep 'string2'

Or

grep 'string1.*string2\|string2.*string1' filename
Muhammad Reda
  • 26,379
  • 14
  • 93
  • 105
dheerosaur
  • 14,736
  • 6
  • 30
  • 31
221

I think this is what you were looking for:

grep -E "string1|string2" filename

I think that answers like this:

grep 'string1.*string2\|string2.*string1' filename

only match the case where both are present, not one or the other or both.

user45949
  • 2,524
  • 1
  • 13
  • 3
32

To search for files containing all the words in any order anywhere:

grep -ril \'action\' | xargs grep -il \'model\' | xargs grep -il \'view_type\'

The first grep kicks off a recursive search (r), ignoring case (i) and listing (printing out) the name of the files that are matching (l) for one term ('action' with the single quotes) occurring anywhere in the file.

The subsequent greps search for the other terms, retaining case insensitivity and listing out the matching files.

The final list of files that you will get will the ones that contain these terms, in any order anywhere in the file.

Kinjal Dixit
  • 7,777
  • 2
  • 59
  • 68
  • 3
    Agreed! I'll just note that I had to give xargs a "-d '\n'" to handle file names with spaces. This worked for me on Linux: `grep -ril 'foo' | xargs -d '\n' grep -il 'bar'` – Tommy Harris May 11 '17 at 21:28
  • Interesting answer, but this it does not answer the OP's question. –  Feb 19 '22 at 05:16
  • @Seamus the question has been edited since it was asked. The answer is related to the question but you have to understand that people come to questions that match something close to what they are looking for. while i don't answer the question, i do provide a related answer. going by the upvotes, the answer was helpful to others. please do not do your advocacy here and ruin the platform. this is the last 'social media' place where i am active. but i will not hesitate to quit if this place becomes woke. – Kinjal Dixit Feb 22 '22 at 05:54
  • ??? `please do not do your advocacy here and ruin the platform...` No idea what you're on about, mate. It was an honest comment, and that is all. If you don't like it, that's fine, but you could have stopped after the first line. Me - "woke"?? ha ha ha –  Feb 22 '22 at 06:49
24

If you have a grep with a -P option for a limited perl regex, you can use

grep -P '(?=.*string1)(?=.*string2)'

which has the advantage of working with overlapping strings. It's somewhat more straightforward using perl as grep, because you can specify the and logic more directly:

perl -ne 'print if /string1/ && /string2/'
tchrist
  • 78,834
  • 30
  • 123
  • 180
  • 2
    Best answer. Shell is very easy and quick, but once the pattern gets complex you should use Python or Perl (or Awk). Don't beat your head against the wall trying to prove it can be done in pure shell (whatever that means these days). A reminder folks, these tools can be used in "one liner" syntax that are embed dibble into an existing shell script. – Scott Prive Nov 28 '14 at 15:06
14

Your method was almost good, only missing the -w

grep -w 'string1\|string2' filename
Leo
  • 2,331
  • 2
  • 19
  • 17
  • 1
    At least on OS-X and FreeBSD it does work! My guess is you're on something else (which the OP didn't define - hope you didn't downvote a correct answer to many users except you). – Leo Jul 23 '15 at 07:27
  • I am on OS-X. Perhaps I am not doing this correctly? Take a look at what I did: http://i.imgur.com/PFVlVAG.png – Ariel Jul 27 '15 at 18:13
  • 1
    Odd. I expected the difference was in not grepping into file, but, if I pipe my method with your ls, I do get result that you don't: http://imgur.com/8eTt3Ak.png - Both on both OS-X 10.9.5 ("grep (BSD grep) 2.5.1-FreeBSD") and FreeBSD 10 ("grep (GNU grep) 2.5.1-FreeBSD"). I'm curious what's your `grep -V` is. – Leo Jul 28 '15 at 19:17
  • 1
    Your examples are working for me: http://i.imgur.com/K8LM69O.png So the difference is that this method does not pick up substrings, they have to be complete strings on their own. I guess you will need to construct regexps within the grep to search for substrings. Something like this: `grep -w 'regexp1\|regexp2' filename` – Ariel Jul 28 '15 at 19:40
  • 4
    OP shows an example by matching string1 _or_ string2 and asks how to match lines that contains _both_ strings. This example still yields OR. – gustafbstrom Oct 05 '16 at 09:56
  • No - the question is clear: the AND condition applies to the line: `string1` AND `string2` ***must appear on the same line*** ... Your solution provides `string1` OR `string2` for the line. –  Feb 20 '22 at 07:38
  • Maybe mind that the question was edited, before going bold in bold. – Leo Apr 02 '22 at 17:26
7

You could try something like this:

(pattern1.*pattern2|pattern2.*pattern1)
Dorn
  • 185
  • 1
  • 7
7

Don't try to use grep for this, use awk instead. To match 2 regexps R1 and R2 in grep you'd think it would be:

grep 'R1.*R2|R2.*R1'

while in awk it'd be:

awk '/R1/ && /R2/'

but what if R2 overlaps with or is a subset of R1? That grep command simply would not work while the awk command would. Lets say you want to find lines that contain the and heat:

$ echo 'theatre' | grep 'the.*heat|heat.*the'
$ echo 'theatre' | awk '/the/ && /heat/'
theatre

You'd have to use 2 greps and a pipe for that:

$ echo 'theatre' | grep 'the' | grep 'heat'
theatre

and of course if you had actually required them to be separate you can always write in awk the same regexp as you used in grep and there are alternative awk solutions that don't involve repeating the regexps in every possible sequence.

Putting that aside, what if you wanted to extend your solution to match 3 regexps R1, R2, and R3. In grep that'd be one of these poor choices:

grep 'R1.*R2.*R3|R1.*R3.*R2|R2.*R1.*R3|R2.*R3.*R1|R3.*R1.*R2|R3.*R2.*R1' file
grep R1 file | grep R2 | grep R3

while in awk it'd be the concise, obvious, simple, efficient:

awk '/R1/ && /R2/ && /R3/'

Now, what if you actually wanted to match literal strings S1 and S2 instead of regexps R1 and R2? You simply can't do that in one call to grep, you have to either write code to escape all RE metachars before calling grep:

S1=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< 'R1')
S2=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< 'R2')
grep 'S1.*S2|S2.*S1'

or again use 2 greps and a pipe:

grep -F 'S1' file | grep -F 'S2'

which again are poor choices whereas with awk you simply use a string operator instead of regexp operator:

awk 'index($0,S1) && index($0.S2)'

Now, what if you wanted to match 2 regexps in a paragraph rather than a line? Can't be done in grep, trivial in awk:

awk -v RS='' '/R1/ && /R2/'

How about across a whole file? Again can't be done in grep and trivial in awk (this time I'm using GNU awk for multi-char RS for conciseness but it's not much more code in any awk or you can pick a control-char you know won't be in the input for the RS to do the same):

awk -v RS='^$' '/R1/ && /R2/'

So - if you want to find multiple regexps or strings in a line or paragraph or file then don't use grep, use awk.

Ed Morton
  • 188,023
  • 17
  • 78
  • 185
6

The | operator in a regular expression means or. That is to say either string1 or string2 will match. You could do:

grep 'string1' filename | grep 'string2'

which will pipe the results from the first command into the second grep. That should give you only lines that match both.

martineno
  • 2,623
  • 17
  • 14
  • 1
    Your statements are true, but don't answer OP question – Ben Wheeler Jul 24 '17 at 19:03
  • 2
    This does answer the question and this is indeed how most people write it. – Peter K Dec 10 '18 at 16:49
  • 1
    You get the correct answer with your proposed solution, but you explained it oddly! In your command, the `|` is a `pipe` - **it does not mean or**; you do explain that later, but what is the reason for that first comment? –  Feb 20 '22 at 07:48
6

And as people suggested perl and python, and convoluted shell scripts, here a simple awk approach:

awk '/string1/ && /string2/' filename

Having looked at the comments to the accepted answer: no, this doesn't do multi-line; but then that's also not what the author of the question asked for.

tink
  • 14,342
  • 4
  • 46
  • 50
  • 2
    This is not the only correct answer to the question, but it is the most elegant. It illustrates the principle of using the right tool for the job. –  Feb 20 '22 at 07:51
3

git grep

Here is the syntax using git grep with multiple patterns:

git grep --all-match --no-index -l -e string1 -e string2 -e string3 file

You may also combine patterns with Boolean expressions such as --and, --or and --not.

Check man git-grep for help.


--all-match When giving multiple pattern expressions, this flag is specified to limit the match to files that have lines to match all of them.

--no-index Search files in the current directory that is not managed by Git.

-l/--files-with-matches/--name-only Show only the names of files.

-e The next parameter is the pattern. Default is to use basic regexp.

Other params to consider:

--threads Number of grep worker threads to use.

-q/--quiet/--silent Do not output matched lines; exit with status 0 when there is a match.

To change the pattern type, you may also use -G/--basic-regexp (default), -F/--fixed-strings, -E/--extended-regexp, -P/--perl-regexp, -f file, and other.

Related:

For OR operation, see:

Community
  • 1
  • 1
kenorb
  • 155,785
  • 88
  • 678
  • 743
  • 2
    Always thought that "git grep" can only be run inside a git repository. I Was not aware of the --no-index option. Thanks for pointing it out! – Kamaraju Kusumanchi Jul 01 '19 at 17:48
3
grep ‘string1\|string2’ FILENAME 

GNU grep version 3.1

  • This doesn't work because the first character after `grep ` is `‘` not the single quote `'` , but something that looks similar - hex value: `e280980a` (Unicode?) instead of `270a`. Used `echo "‘" | xxd -ps -c 20` –  Feb 20 '22 at 09:28
2

Found lines that only starts with 6 spaces and finished with:

 cat my_file.txt | grep
 -e '^      .*(\.c$|\.cpp$|\.h$|\.log$|\.out$)' # .c or .cpp or .h or .log or .out
 -e '^      .*[0-9]\{5,9\}$' # numers between 5 and 9 digist
 > nolog.txt
Cristian
  • 548
  • 6
  • 8
2

Let's say we need to find count of multiple words in a file testfile. There are two ways to go about it

1) Use grep command with regex matching pattern

grep -c '\<\(DOG\|CAT\)\>' testfile

2) Use egrep command

egrep -c 'DOG|CAT' testfile 

With egrep you need not to worry about expression and just separate words by a pipe separator.

alexander.polomodov
  • 5,396
  • 14
  • 39
  • 46
Amit Singh
  • 31
  • 2
1

Place the strings you want to grep for into a file

echo who    > find.txt
echo Roger >> find.txt
echo [44][0-9]{9,} >> find.txt

Then search using -f

grep -f find.txt BIG_FILE_TO_SEARCH.txt 
Tim Seed
  • 5,119
  • 2
  • 30
  • 26
1
grep '(string1.*string2 | string2.*string1)' filename

will get line with string1 and string2 in any order

PKumar
  • 10,971
  • 6
  • 37
  • 52
James
  • 11
  • 1
1
grep -i -w 'string1\|string2' filename

This works for exact word match and matching case insensitive words ,for that -i is used

Saurabh
  • 7,525
  • 4
  • 45
  • 46
0

for multiline match:

echo -e "test1\ntest2\ntest3" |tr -d '\n' |grep "test1.*test3"

or

echo -e "test1\ntest5\ntest3" >tst.txt
cat tst.txt |tr -d '\n' |grep "test1.*test3\|test3.*test1"

we just need to remove the newline character and it works!

Aquarius Power
  • 3,729
  • 5
  • 32
  • 67
0

I often run into the same problem as yours, and I just wrote a piece of script:

function m() { # m means 'multi pattern grep'

    function _usage() {
    echo "usage: COMMAND [-inH] -p<pattern1> -p<pattern2> <filename>"
    echo "-i : ignore case"
    echo "-n : show line number"
    echo "-H : show filename"
    echo "-h : show header"
    echo "-p : specify pattern"
    }

    declare -a patterns
    # it is important to declare OPTIND as local
    local ignorecase_flag  filename linum header_flag colon result OPTIND

    while getopts "iHhnp:" opt; do
    case $opt in
        i)
        ignorecase_flag=true ;;
        H)
        filename="FILENAME," ;;
        n)
        linum="NR," ;;
        p)
        patterns+=( "$OPTARG" ) ;;
        h)
        header_flag=true ;;
        \?)
        _usage
        return ;;
    esac
    done

    if [[ -n $filename || -n $linum ]]; then
    colon="\":\","
    fi

    shift $(( $OPTIND - 1 ))

    if [[ $ignorecase_flag == true ]]; then
    for s in "${patterns[@]}"; do
            result+=" && s~/${s,,}/"
    done
    result=${result# && }
    result="{s=tolower(\$0)} $result"
    else
    for s in "${patterns[@]}"; do
            result="$result && /$s/"
    done
    result=${result# && }
    fi

    result+=" { print "$filename$linum$colon"\$0 }"

    if [[ ! -t 0 ]]; then       # pipe case
    cat - | awk "${result}"
    else
    for f in "$@"; do
        [[ $header_flag == true ]] && echo "########## $f ##########"
        awk "${result}" $f
    done
    fi
}

Usage:

echo "a b c" | m -p A 
echo "a b c" | m -i -p A # a b c

You can put it in .bashrc if you like.

ruanhao
  • 4,663
  • 6
  • 28
  • 43
0

You should have grep like this:

$ grep 'string1' file | grep 'string2'
kenorb
  • 155,785
  • 88
  • 678
  • 743
Raghuram
  • 3,937
  • 2
  • 19
  • 25
0

When the both strings are in sequence then put a pattern in between on grep command:

$ grep -E "string1(?.*)string2" file

Example if the following lines are contained in a file named Dockerfile:

FROM python:3.8 as build-python
FROM python:3.8-slim

To get the line that contains the strings: FROM python and as build-python then use:

$ grep -E "FROM python:(?.*) as build-python" Dockerfile

Then the output will show only the line that contain both strings:

FROM python:3.8 as build-python
eQ19
  • 9,880
  • 3
  • 65
  • 77
0

If git is initialized and added to the branch then it is better to use git grep because it is super fast and it will search inside the whole directory.

git grep 'string1.*string2.*string3'
0

searching for two String and highlight only string1 and string2

grep -E 'string1.*string2|string2.*string1' filename | grep -E 'string1|string2'
  • or
grep 'string1.*string2\|string2.*string1' filename | grep -E 'string1\|string2'
nextloop
  • 166
  • 9
0

Much simpler command to grep both the strings:

(cat file | grep 'phrase_1') && (cat file | grep 'phrase_2')
Gayan Weerakutti
  • 11,904
  • 2
  • 71
  • 68
-1

ripgrep

Here is the example using rg:

rg -N '(?P<p1>.*string1.*)(?P<p2>.*string2.*)' file.txt

It's one of the quickest grepping tools, since it's built on top of Rust's regex engine which uses finite automata, SIMD and aggressive literal optimizations to make searching very fast.

Use it, especially when you're working with a large data.

See also related feature request at GH-875.

kenorb
  • 155,785
  • 88
  • 678
  • 743
  • 2
    This answer isn't quite right. The named capturing groups are unnecessary, and this doesn't handle the case when `string2` appears before `string1`. The simplest solution to this problem is `rg string1 file.txt | rg string2`. – BurntSushi5 May 01 '19 at 15:01