grep for "term" and exclude "another term"
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"
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"
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 ifbar
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.
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.
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