224
votes

I am passing a list of regex patterns to grep to check against a syslog file. They are usually matching an IP address and log entry;

grep "1\.2\.3\.4.*Has exploded" syslog.log

It's just a list of patterns like the "1\.2\.3\.4.*Has exploded" part I am passing, in a loop, so I can't pass "-v" for example.

I am confused trying to do the inverse of the above, and NOT match lines with a certain IP address and error so "!1.2.3.4.*Has exploded" will match syslog lines for anything other than 1.2.3.4 telling me it has exploded. I must be able to include an IP to NOT match.

I have seen various similar posts on StackOverflow. However they use regex patterns that I can't seem to get to work with grep. Can anyone provide a working example for grep please?

UPDATE: This is happening in a script like this;

patterns[1]="1\.2\.3\.4.*Has exploded"
patterns[2]="5\.6\.7\.8.*Has died"
patterns[3]="\!9\.10\.11\.12.*Has exploded"

for i in {1..3}
do
 grep "${patterns[$i]}" logfile.log
done
3
Do you mean you sometimes want to match a pattern, but other times want to match everything except a certain pattern? (this seems like an odd requirement, but whatever). In that case, why don't you iterate over two different lists of patterns? - beerbajay
Well I'm not very knowledgeable about regex; I don't want to grep for "Has Exploded" because I don't want to know this about every logging device, so can I somehow grep for "Has Exploded" and !9.10.11.12 in one statement? - jwbensley
If you absolutely must do it in one statement, negative lookbehinds are the way to go, as Neil suggests. See my comment there. - beerbajay
Use PCRE-style regex matching, and a negative lookahead assertion, as per @Neil 's answer: patterns[3]="\!9\.10\.11\.12.*Has exploded" changes to patterns[3]="(?<!9\.10\.11\.12).*Has exploded" and grep "${patterns[$i]}" logfile.log changes to grep -P "${patterns[$i]}" logfile.log PCRE assumes more metacharacters by default, so some of the escapes may need to be removed from other matching expressions. - Codex24

3 Answers

427
votes

grep matches, grep -v does the inverse. If you need to "match A but not B" you usually use pipes:

grep "${PATT}" file | grep -v "${NOTPATT}"
16
votes
(?<!1\.2\.3\.4).*Has exploded

You need to run this with -P to have negative lookbehind (Perl regular expression), so the command is:

grep -P '(?<!1\.2\.3\.4).*Has exploded' test.log

Try this. It uses negative lookbehind to ignore the line if it is preceeded by 1.2.3.4. Hope that helps!

2
votes
patterns[1]="1\.2\.3\.4.*Has exploded"
patterns[2]="5\.6\.7\.8.*Has died"
patterns[3]="\!9\.10\.11\.12.*Has exploded"

for i in {1..3}
 do
grep "${patterns[$i]}" logfile.log
done

should be the the same as

egrep "(1\.2\.3\.4.*Has exploded|5\.6\.7\.8.*Has died)" logfile.log | egrep -v "9\.10\.11\.12.*Has exploded"