68

I've been trying to find a way to filter a line that has the word "lemon" and "rice" in it. I know how to find "lemon" or "rice" but not the two of them. They don't need to be next to the other, just one the same line of text.

αғsнιη
  • 36,400
Sebastian
  • 785
  • 1
    To find all of the strings inside a file, you can run grep in FOR loop: https://unix.stackexchange.com/a/462445/43233 – Noam Manos Aug 14 '18 at 06:55

7 Answers7

79

"Both on the same line" means "'rice' followed by random characters followed by 'lemon' or the other way around".

In regex that is rice.*lemon or lemon.*rice. You can combine that using a |:

grep -E 'rice.*lemon|lemon.*rice' some_file

If you want to use normal regex instead of extended ones (-E) you need a backslash before the |:

grep 'rice.*lemon\|lemon.*rice' some_file

For more words that quickly gets a bit lengthy and it's usually easier to use multiple calls of grep, for example:

grep rice some_file | grep lemon | grep chicken
dessert
  • 41,116
  • Your last line is a conjunction not disjunction no? To wit: the grep rice finds lines containing rice. It is fed into grep lemon which will only find lines containing lemon .. and so on. Whereas the OP - as well as your prior answers - are allowing any of [rice|lemon|chicken] – WestCoastProjects Jan 04 '17 at 22:16
  • Script version: http://askubuntu.com/a/879253/5696 – Jeff Feb 03 '17 at 01:22
  • @Florian Diesch - Mind explaining why | needs to be escaped in grep? Thanks! – fugitive Feb 03 '17 at 21:25
  • 1
    @fugitive egrep uses extended regex where | is understood as OR logic. grep defaults to basic regex, where \| is OR – Sergiy Kolodyazhnyy Jul 24 '17 at 15:06
  • 1
    As stated in grep's manpage, egrep is deprecated and should be replaced by grep -E. I took the freedom to edit the answer accordingly. – dessert Sep 27 '17 at 17:19
32

You can pipe the output of first grep command to another grep command and that would match both the patterns. So, you can do something like:

grep <first_pattern> <file_name> | grep <second_pattern>

or,

cat <file_name> | grep <first_pattern> | grep <second_pattern>

Example:

Let's add some contents to our file:

$ echo "This line contains lemon." > test_grep.txt
$ echo "This line contains rice." >> test_grep.txt
$ echo "This line contains both lemon and rice." >> test_grep.txt
$ echo "This line doesn't contain any of them." >> test_grep.txt
$ echo "This line also contains both rice and lemon." >> test_grep.txt

What does the file contain:

$ cat test_grep.txt 
This line contains lemon.
This line contains rice.
This line contains both lemon and rice.
This line doesn't contain any of them.
This line also contains both rice and lemon.

Now, let's grep what we want:

$ grep rice test_grep.txt | grep lemon
This line contains both lemon and rice.
This line also contains both rice and lemon.

We only get the lines where both the patterns match. You can extend this and pipe the output to another grep command for further "AND" matches.

Aditya
  • 13,626
23

Though the question asks for 'grep', I thought it might be helpful to post a simple 'awk' solution:

awk '/lemon/ && /rice/'

This can easily be extended with more words, or other boolean expressions besides 'and'.

muru
  • 207,970
David B.
  • 231
12

Another idea to finding the matches in any order is using:

grep with -P (Perl-Compatibility) option and positive lookahead regex (?=(regex)):

grep -P '(?=.*?lemon)(?=.*?rice)' infile

or you can use below, instead:

grep -P '(?=.*?rice)(?=.*?lemon)' infile
  • The .*? means matching any characters . that occurrences zero or more times * while they are optional followed by a pattern(rice or lemon). The ? makes everything optional before it (means zero or one time of everything matched .*)

(?=pattern): Positive Lookahead: The positive lookahead construct is a pair of parentheses, with the opening parenthesis followed by a question mark and an equals sign.

So this will return all lines with contains both lemon and rice in random order. Also this will avoid of using |s and doubled greps.


See also:
Advanced Grep Topics

αғsнιη
  • 36,400
10

This command returns matches for a line which has either foo or goo.

grep -e foo -e goo

This command return matches for lines which have both foo and goo in any order.

grep -e foo.*goo -e goo.*foo
netskink
  • 199
  • 3
  • 6
  • This is mostly good, but also returns lines with only "foo" or "goo" in addition to "foo" and "goo". – Brian Oct 03 '22 at 11:42
  • Indeed. "either foo or goo" And, upon rereading, I see that the OP said ~"I know how to do OR I want to know how to do AND". I will update the answer. – netskink Oct 03 '22 at 13:51
2

If we admit that providing an answer that is not grep based is acceptable, like the above answer based on awk, I would propose a simple perl line like:

$ perl -ne 'print if /lemon/ and /rice/' my_text_file

The search can be ignoring case with some/all of the words like /lemon/i and /rice/i. On most Unix/Linux machines perl is installed as well as awk anyway.

  • Refused!!! ;) Because it make no sense.. :) – An0n Aug 26 '18 at 02:41
  • that works, but (perl seems exotic! because I'm really not familiar). wonder at the resource use vs various in-shell methods, use of double greps or awk, for instance. I can imagine it being a factor in the choice for doing somethings on a large file or group of files -- but then PERL has plenty of other capabilities which are largely challenged by PYTHON these days...not sure here – Old Uncle Ho Aug 30 '24 at 13:58
0

Here's a script to automate the grep piping solution:

#!/bin/bash

# Use filename if provided as environment variable, or "foo" as default
filename=${filename-foo}

grepand () {
# disable word splitting and globbing
IFS=
set -f
if [[ -n $1 ]]
then
grep -i "$1" ${filename} | filename="" grepand "${@:2}"
else
# If there are no arguments, assume last command in pipe and print everything
cat
fi
}

grepand "$@"
Jeff
  • 782
  • 1
    This should probably be implemented using a recursive function, instead of building a command string and evaling it, which breaks easily – muru Feb 03 '17 at 01:42
  • @muru Feel free to suggest an edit. I do appreciate the comment. – Jeff Feb 03 '17 at 01:46
  • 1
    Editing it do that will be too much of a rewrite, so I won't do that. If you want to add it, here's what I imagine it should look like: http://paste.ubuntu.com/23915379/ – muru Feb 03 '17 at 02:36