52

I am trying to build a grep search that searches for a term but exludes lines which have a second term. I wanted to use multiple -e "pattern" options but that has not worked.

Here is an example of a command I tried and the error message it generated.

grep -i -E "search term" -ev "exclude term"
grep: exclude term: No such file or directory

It seams to me that the -v applies to all search terms / patterns. As this runs but then does not include search term in results.

grep -i -E "search term" -ve "exclude term"
1
  • Is there any other option for exclude, as sometimes we have to grep lines around a word and If we exclude in the next operation using '|' , it just removes that word but doesn't remove the block for that word
    – Learner
    Dec 6, 2018 at 17:12

5 Answers 5

60

To and expressions with grep you need two invocations:

grep -Ei "search term" | grep -Eiv "exclude term"

If the terms you are searching for are not regular expressions, use fixed string matching (-F) which is faster:

grep -F "search term" | grep -Fv "exclude term"
25

Short of invoking grep twice, there is only one way I can think of to accomplish this. It involves Perl Compatible Regular Expressions (PCRE) and some rather hacky look-around assertions.

To search for foo excluding matches that contain bar, you can use:

grep -P '(?=^((?!bar).)*$)foo'

Here's how it works:

  • (?!bar) matches anything that not bar without consuming characters from the string. Then . consumes a single character.

  • ^((?!bar).)* repeats the above from the start of the string (^) to the end of it ($). It will fail if bar is encountered at any given point, since (?!bar) will not match.

  • (?=^((?!bar).)*$) makes sure the string matches the previous pattern, without consuming characters from the string.

  • foo searches for foo as usual.

I found this hack in Regular expression to match string not containing a word?. In Bart Kiers' answer, you can find a much more detailed explanation of how the negative look-ahead operates.

2
  • 1
    Nice hack. This trick works in Java too, btw.
    – Raman
    Jul 2, 2019 at 18:32
  • This will not work as the regex only matches lines starting with foo, and not containing foo. A working (and better performing) regex solution would be grep -P '(?=^(?!.*bar).*foo)'
    – Qtax
    Feb 4, 2022 at 10:54
18

If you want to do this in one pass, you can use awk instead of grep.

Format:

echo "some text" | awk '/pattern to match/ && !/pattern to exclude/'

Examples:

  • echo "hello there" | awk '/hello/ && !/there/'

Returns nothing.

  • echo "hello thre" | awk '/hello/ && !/there/'

Returns: hello thre

  • echo "hllo there" | awk '/hello/ && !/there/'

Returns nothing.

For multiple patterns, you can use parenthesis to group them.

Examples:

  • echo "hello thre" | awk '(/hello/ || /hi/) && !/there/'

Returns: hello thre

  • echo "hi thre" | awk '(/hello/ || /hi/) && !/there/'

Returns: hi thre

  • echo "hello there" | awk '(/hello/ || /hi/) && !/there/'

Returns nothing.

  • echo "hi there" | awk '(/hello/ || /hi/) && !/there/'

Returns nothing.

4
  • 1
    It worked for me, but I lost the colors =P Aug 26, 2017 at 23:38
  • 1
    Colors from what output? If you are trying to preserve colors with ls, use the "--color=always" argument whenever parsing the output (or you will normally always lose the colors when parsing the text). Example: ls --color=always | awk '/hello/ && !/goodbye/' Sep 4, 2017 at 9:27
  • Thanks for the answer @Philip! I tried that before, but without success. I guess that as the pattern has the colored text, it doesn't match later, and I should include some kind of color code in the pattern. Anyway, yours is the fastest way that I found to do grep -R in several code files using Ubuntu command line. Sep 5, 2017 at 17:19
  • history | awk '/pattern1|pattern2/ && !/pattern3|pattern4/'
    – Cymatical
    Sep 24, 2021 at 17:40
3

The -v (or --invert-match) option filters out matches.

You can make use of grep along with pipe to grep for term and exclude another.

grep pattern1 *.txt | grep -v pattern2

Matches all lines in "*.txt" files containing pattern1, but not pattern2.

2

From my experiments it does not seam to make much difference if you pipe your exclude terms through grep or sed. Sed has some other useful text replacement features which I often use to better filter the out put of log files. So I am going to use sed as I combine quite a number of filters on sed.

wc /var/log/tomcat/tomcat.2013-01-14.log.1 
  1851725

 /usr/bin/time grep -i -E "(loginmanager)" /var/log/tomcat/tomcat.2013-01-14.log.1 | sed -e "/login OK/d" -e "/Login expired/d" | wc
24.05user 0.15system 0:25.27elapsed 95%CPU (0avgtext+0avgdata 3504maxresident)k
0inputs+0outputs (0major+246minor)pagefaults 0swaps
   5614   91168 1186298

 /usr/bin/time grep -i -E "(loginmanager)" /var/log/tomcat/tomcat.2013-01-14.log.1 | sed -e "/login OK/d" -e "/Login expired/d" | wc
23.50user 0.16system 0:24.48elapsed 96%CPU (0avgtext+0avgdata 3504maxresident)k
0inputs+0outputs (0major+246minor)pagefaults 0swaps
   5614   91168 1186298

 /usr/bin/time grep -i -E "(loginmanager)" /var/log/tomcat/tomcat.2013-01-14.log.1 | grep -v -e "login OK" -e "Login expired" | wc
23.08user 0.14system 0:23.55elapsed 98%CPU (0avgtext+0avgdata 3504maxresident)k
0inputs+0outputs (0major+246minor)pagefaults 0swaps
   5614   91168 1186298

 /usr/bin/time grep -i -E "(loginmanager)" /var/log/tomcat/tomcat.2013-01-14.log.1 | grep -v -e "login OK" -e "Login expired" | wc
23.50user 0.15system 0:25.27elapsed 93%CPU (0avgtext+0avgdata 3488maxresident)k
0inputs+0outputs (0major+245minor)pagefaults 0swaps
   5614   91168 1186298

2
  • 3
    Try comparing the runtime of grep -F instead of grep -E and don't use -i if you don't need it.
    – Thor
    Jan 17, 2013 at 20:17
  • 1
    But then you don't provide examples using sed ;)
    – Benjamin R
    Mar 7, 2018 at 3:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .