48

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

55

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 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
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
2

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.