Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Regex lookahead for 'not followed by' in grep

I am attempting to grep for all instances of Ui\. not followed by Line or even just the letter L

What is the proper way to write a regex for finding all instances of a particular string NOT followed by another string?

Using lookaheads

grep "Ui\.(?!L)" *
bash: !L: event not found


grep "Ui\.(?!(Line))" *
nothing
like image 533
Lee Quarella Avatar asked Oct 11 '22 16:10

Lee Quarella


People also ask

Does grep support lookahead?

Show activity on this post. grep in macOS does not support lookahead.

Does grep support negative lookahead?

Negative lookahead, which is what you're after, requires a more powerful tool than the standard grep . You need a PCRE-enabled grep. If you have GNU grep , the current version supports options -P or --perl-regexp and you can then use the regex you wanted.

Can you use regex with grep?

GNU grep supports three regular expression syntaxes, Basic, Extended, and Perl-compatible. In its simplest form, when no regular expression type is given, grep interpret search patterns as basic regular expressions. To interpret the pattern as an extended regular expression, use the -E ( or --extended-regexp ) option.

How do you negate grep?

To display only the lines that do not match a search pattern, use the -v ( or --invert-match ) option. The -w option tells grep to return only those lines where the specified string is a whole word (enclosed by non-word characters). By default, grep is case-sensitive.


5 Answers

Negative lookahead, which is what you're after, requires a more powerful tool than the standard grep. You need a PCRE-enabled grep.

If you have GNU grep, the current version supports options -P or --perl-regexp and you can then use the regex you wanted.

If you don't have (a sufficiently recent version of) GNU grep, then consider getting ack.

like image 181
Jonathan Leffler Avatar answered Oct 19 '22 04:10

Jonathan Leffler


The answer to part of your problem is here, and ack would behave the same way: Ack & negative lookahead giving errors

You are using double-quotes for grep, which permits bash to "interpret ! as history expand command."

You need to wrap your pattern in SINGLE-QUOTES: grep 'Ui\.(?!L)' *

However, see @JonathanLeffler's answer to address the issues with negative lookaheads in standard grep!

like image 45
NHDaly Avatar answered Oct 19 '22 04:10

NHDaly


You probably cant perform standard negative lookaheads using grep, but usually you should be able to get equivalent behaviour using the "inverse" switch '-v'. Using that you can construct a regex for the complement of what you want to match and then pipe it through 2 greps.

For the regex in question you might do something like

grep 'Ui\.' * | grep -v 'Ui\.L'
like image 14
Karel Tucek Avatar answered Oct 19 '22 03:10

Karel Tucek


If you need to use a regex implementation that doesn't support negative lookaheads and you don't mind matching extra character(s)*, then you can use negated character classes [^L], alternation |, and the end of string anchor $.

In your case grep 'Ui\.\([^L]\|$\)' * does the job.

  • Ui\. matches the string you're interested in

  • \([^L]\|$\) matches any single character other than L or it matches the end of the line: [^L] or $.

If you want to exclude more than just one character, then you just need to throw more alternation and negation at it. To find a not followed by bc:

grep 'a\(\([^b]\|$\)\|\(b\([^c]\|$\)\)\)' *

Which is either (a followed by not b or followed by the end of the line: a then [^b] or $) or (a followed by b which is either followed by not c or is followed by the end of the line: a then b, then [^c] or $.

This kind of expression gets to be pretty unwieldy and error prone with even a short string. You could write something to generate the expressions for you, but it'd probably be easier to just use a regex implementation that supports negative lookaheads.

*If your implementation supports non-capturing groups then you can avoid capturing extra characters.

like image 7
dougcosine Avatar answered Oct 19 '22 03:10

dougcosine


At least for the case of not wanting an 'L' character after the "Ui." you don't really need PCRE.

    grep -E 'Ui\.($|[^L])' *

Here I've made sure to match the special case of the "Ui." at the end of the line.

like image 2
Doug Robinson Avatar answered Oct 19 '22 04:10

Doug Robinson