Regular expression to match a line that doesn't contain a word
Asked Answered
N

35

5360

I know it's possible to match a word and then reverse the matches using other tools (e.g. grep -v). However, is it possible to match lines that do not contain a specific word, e.g. hede, using a regular expression?

Input:
hoho
hihi
haha
hede
Code:
grep "<Regex for 'doesn't contain hede'>" input
Desired output:
hoho
hihi
haha
Nonresistant answered 2/1, 2009 at 7:30 Comment(6)
Probably a couple years late, but what's wrong with: ([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$)))*? The idea is simple. Keep matching until you see the start of the unwanted string, then only match in the N-1 cases where the string is unfinished (where N is the length of the string). These N-1 cases are "h followed by non-e", "he followed by non-d", and "hed followed by non-e". If you managed to pass these N-1 cases, you successfully didn't match the unwanted string so you can start looking for [^h]* againCyrilcyrill
@stevendesu: try this for 'a-very-very-long-word' or even better half a sentence. Have fun typing. BTW, it is nearly unreadable. Don't know about the performance impact.Yuhas
@PeterSchuetze: Sure it's not pretty for very very long words, but it is a viable and correct solution. Although I haven't run tests on the performance, I wouldn't imagine it being too slow since most of the latter rules are ignored until you see an h (or the first letter of the word, sentence, etc.). And you could easily generate the regex string for long strings using iterative concatenation. If it works and can be generated quickly, is legibility important? That's what comments are for.Cyrilcyrill
@stevendesu: i'm even later, but that answer is almost completely wrong. for one thing, it requires the subject to contain "h" which it shouldn't have to, given the task is "match lines which [do] not contain a specific word". let us assume you meant to make the inner group optional, and that the pattern is anchored: ^([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$))?)*$ this fails when instances of "hede" are preceded by partial instances of "hede" such as in "hhede".Heavyladen
This question has been added to the Stack Overflow Regular Expression FAQ, under "Advanced Regex-Fu".Acro
Related: Regex: Matching by exclusion, without look-ahead - is it possible?Troika
O
7392

The notion that regex doesn't support inverse matching is not entirely true. You can mimic this behavior by using negative look-arounds:

^((?!hede).)*$

The regex above will match any string, or line without a line break, not containing the (sub)string 'hede'. As mentioned, this is not something regex is "good" at (or should do), but still, it is possible.

And if you need to match line break chars as well, use the DOT-ALL modifier (the trailing s in the following pattern):

/^((?!hede).)*$/s

or use it inline:

/(?s)^((?!hede).)*$/

(where the /.../ are the regex delimiters, i.e., not part of the pattern)

If the DOT-ALL modifier is not available, you can mimic the same behavior with the character class [\s\S]:

/^((?!hede)[\s\S])*$/

Explanation

A string is just a list of n characters. Before, and after each character, there's an empty string. So a list of n characters will have n+1 empty strings. Consider the string "ABhedeCD":

    ┌──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┐
S = │e1│ A │e2│ B │e3│ h │e4│ e │e5│ d │e6│ e │e7│ C │e8│ D │e9│
    └──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┘
    
index    0      1      2      3      4      5      6      7

where the e's are the empty strings. The regex (?!hede). looks ahead to see if there's no substring "hede" to be seen, and if that is the case (so something else is seen), then the . (dot) will match any character except a line break. Look-arounds are also called zero-width-assertions because they don't consume any characters. They only assert/validate something.

So, in my example, every empty string is first validated to see if there's no "hede" up ahead, before a character is consumed by the . (dot). The regex (?!hede). will do that only once, so it is wrapped in a group, and repeated zero or more times: ((?!hede).)*. Finally, the start- and end-of-input are anchored to make sure the entire input is consumed: ^((?!hede).)*$

As you can see, the input "ABhedeCD" will fail because on e3, the regex (?!hede) fails (there is "hede" up ahead!).

Oscillograph answered 2/1, 2009 at 7:30 Comment(30)
I would not go so far as to say that this is something regex is bad at. The convenience of this solution is pretty obvious and the performance hit compared to a programmatic search is often going to be unimportant.Clop
Strictly speaking negative loook-ahead makes you regular expression not-regular.Hafnium
@PeterK, sure, but this is SO, not MathOverflow or CS-Stackexchange. People asking a question here are generally looking for a practical answer. Most libraries or tools (like grep, which the OP mentions) with regex-support all have features that mke them non-regular in a theoretical sense.Oscillograph
@Bart Kiers, no offense to you answer, just this abuse of terminology irritates me a bit. The really confusing part here is that regular expressions in the strict sense can very much do what OP wants, but the common language to write them does not allow it, which leads to (mathematically ugly) workarounds like look-aheads. Please see this answer below and my comment there for (theoretically aligned) proper way of doing it. Needless to say it works faster on large inputs.Hafnium
In case you ever wondered how to do this in vim: ^\(\(hede\)\@!.\)*$Africah
This answer worked better for me. Can you explain the difference between ^((?!hede).)*$ and ^(?!hede).*$?Hypogenous
@Z.Khullah You're missing the m flag. Adding it in your regex didn't do the trick, but creating a new test with m did work on Regexr: regexr.com/3hj1b In short: it's a bug in Regexr.Oscillograph
Lookbacks/lookarounds are only supported in pcre superuser.com/a/596499/658319. Perhaps a disclaimer should be added.Altissimo
@Altissimo true, but I don't think a disclaimer is needed. This is SO, not Math Overflow or CS-SE, and nearly all popular programming languages with regex-support are PCRE (is close to it).Oscillograph
same with word boundaries: ^((?!\bhede\b).)*$Clap
Why are so many answers saying ^((?!hede).)*$ ? Is it not more efficient to use ^(?!.*hede).*$ ? It does the same thing but in fewer steps.Phlegmy
A little extension, I know it was not in the question but might be useful. What if you want to filter more than one words, the same time match for these words separately, and remove spaces and line breaks in addition. I tested it in Python: import re; exp = re.compile(r'(?!\s)(?:AND|OR|NOT|\(|\)|(?:(?!OR|AND|NOT|\s{2:})(?:[-\w: ]))+)(?<!\s)'); exp.findall('foobar AND (foo loves bar OR NOT bla bla)') results: ['foobar', 'AND', '(', 'foo loves bar', 'OR', 'NOT', 'bla bla', ')']Multimillionaire
What about if we want to include \n aka line break too in the match?Hydrostatics
This throws -bash: !ERROR: event not found? If you do not want the -v parameter, the negative lookahead in grep can be done, see this answer below.Braden
Can you elaborate on why RegEx "should not" do negative searches in this manner? ^((?!hede).)*$ or @Phlegmy ^(?!.*hede).*$ looks pretty elegant to a layperson looking to narrow down lines that DONT contain something. OP never elaborated on why RegEx is inappropriate for this. Ordinary find search tools cannot not do this.Brood
@JonGrah my solution, ^((?!hede).)* (which is different than (?!.*hede).*), works like this: from every position in the string, look ahead to the end of the string to see if "hede" can be seen. So, if your string is 10 chars long, and does not contain "hede", it will look ahead 11 + 10 + 9 + ... + 2 + 1 times (which equals 66). And if your string is of size N (and N is very large), it will perform (N * (N + 1)) / 2 lookahead steps. Hence my remark "should not" do it.Oscillograph
@Phlegmy ^(?!.*?hede).* is a lot more efficient in many implementations, for example in JS when applied to very big string inputs is runs quite fast, while the other solution (lookahead at each position) will just break with "maximum call stack exceeded".Chessman
@BartKiers I think it could be useful for many readers to include the alternative ^(?!.*?hede).* which will in fact just perform an optimized fast forward search for the keyword, without any backtracking and then match the whole line. which should be just as fast as any manually programmed solution.Chessman
Shouldn't this be ^(.(?!hede))*$? @Falco, I doubt a simple fixed length negative lookahead will cause troubles to any regex engine. But these negative lookups are rarely supported. Even some engines reject non-fixed sized negative lookbehinds.Wrestle
@Wrestle Just tried your Regex with a 9M character long string in the Browser Console: "Error: maximum call stack size exceeded" - while my alternative succeeds in milliseconds - a single complex variable length look-ahead is currently in e.g. JS Regex-Engine better optimized than 9 Million lookaheads with backtrackingChessman
Something like ^(?!.*hede).*$ and ^((?!hede).)*$ do completely different things. If ^(?!.*hede).*$ works for your use case, then that will indeed be much faster (and thus the way to go).Oscillograph
@Falco, which browser and what expression are you running? I tried in Ruby with a 9M chars string and I see no noticeable difference between these regular expressions. I see no reason to backtrack because after a dot is matched, I see no reason for backtracking to it. I payed more at your regular expression though and I like it more. In case regex engine supports variable length look-ahead, it's neat.Wrestle
And actually my suggested regexp is invalid and will miss hede. Because dot is in front. regexp are just a necessary evil.Wrestle
@BartKiers can you provide an example where both regexes are different? They seem to match exactly the same set on first intuition?Chessman
@Wrestle Just run this in the Chrome DevTools Console: /^((?!hede).)*$/.exec('Lorem Ipsum'.repeat(1000000)) - gives Callstack exceeded errorChessman
@Falco, Firefox also throws Uncaught InternalError: too much recursion.Wrestle
@Wrestle and this doesn't happen with ^(?!.*?hede).*$ the engine will try to find the word once, starting at the beginning of the string and immediately fail the whole regex if it is found. If not the regex immediately matches the whole string (most Engines have a shortcut for .*) Running it on the same 9M char string in the Browser returns in miliseconds: /^(?!.*?hede).*$/.exec('Lorem Ipsum'.repeat(1000000))Chessman
@BartKiers That's a great answer and I learned a lot, but I didn't read (at the time), I tried to make sense on my own. For this, I used non-programming/spoken language (as in "you know that hede thingy? We don't like it, so let's start by negating it: (?!hede)". This "vulgar" kind of explanation really helped me, so I'd like to suggest an edit for me to include this kind of "language". Usually, I'd just submit an edit and wait for peer analysis, but this is a waay out of my league answer, so I'd rather ask for permission first!Hypallage
@FabioFreitas sorry, I'm not too keen on rephrasing (large) parts of my answer. Feel free to create an answer of your own where you explain the concept in your own words.Oscillograph
@BartKiers Oh, I'm certainly not proposing changing any of your words. You had the correct solution and explained it properly and formally, which is really great. An answer of my own would not and should not even be read by anyone looking at this question. I'm proposing adding at the end this kind of stuff for didactic purposes only. I discuss similar ideas, but based on the Monty Hall Problem, on this Reddit postHypallage
B
971

Note that the solution to does not start with “hede”:

^(?!hede).*$

is generally much more efficient than the solution to does not contain “hede”:

^((?!hede).)*$

The former checks for “hede” only at the input string’s first position, rather than at every position.

Boult answered 2/1, 2009 at 7:30 Comment(7)
^((?!hede).)*$ worked for me using the jQuery DataTable plugin to exclude a string from the datasetAndrej
Hello! I can't compose does not end with "hede" regex. Can you help with it?Hydrochloride
@AleksYa: just use the "contain" version, and include the end anchor into the search string: change the string to "not match" from "hede" to "hede$"Neutretto
To match lines that does not end with "hede", one way is to use ^.*(?!hede).{4}. The number in braces should match the length of non-matching text you want (e.g. "hede" is length 4).Tuttifrutti
@AleksYa: the does not end version could be done using negative lookbehind as: (.*)(?<!hede)$. @Nyerguds' version would work as well but completely misses the point on performance the answer mentions.Paver
Why are so many answers saying ^((?!hede).)*$ ? Is it not more efficient to use ^(?!.*hede).*$ ? It does the same thing but in fewer stepsPhlegmy
Can't address the efficiency (I'm using it on very small strings) but your version works fine on my case rejecting one case of a general case that should be accepted.Complicated
L
249

If you're just using it for grep, you can use grep -v hede to get all lines which do not contain hede.

ETA Oh, rereading the question, grep -v is probably what you meant by "tools options".

Luanneluanni answered 2/1, 2009 at 7:30 Comment(6)
Tip: for progressively filtering out what you don't want: grep -v "hede" | grep -v "hihi" | ...etc.Brennan
Or using only one process grep -v -e hede -e hihi -e ...Sabadell
Or just grep -v "hede\|hihi" :)Thuggee
If you have many patterns that you want to filter out, put them in a file and use grep -vf pattern_file fileManagement
Or simply egrep or grep -Ev "hede|hihi|etc" to avoid the awkward escaping.Pox
The egrep one: egrep -v "hede|hihi|etc"Hyetal
F
244

Answer:

^((?!hede).)*$

Explanation:

^the beginning of the string, ( group and capture to \1 (0 or more times (matching the most amount possible)),
(?! look ahead to see if there is not,

hede your string,

) end of look-ahead, . any character except \n,
)* end of \1 (Note: because you are using a quantifier on this capture, only the LAST repetition of the captured pattern will be stored in \1)
$ before an optional \n, and the end of the string

Feinstein answered 2/1, 2009 at 7:30 Comment(1)
awesome that worked for me in sublime text 2 using multiple words '^((?!DSAU_PW8882WEB2|DSAU_PW8884WEB2|DSAU_PW8884WEB).)*$'Appellee
Y
118

The given answers are perfectly fine, just an academic point:

Regular Expressions in the meaning of theoretical computer sciences ARE NOT ABLE do it like this. For them it had to look something like this:

^([^h].*$)|(h([^e].*$|$))|(he([^h].*$|$))|(heh([^e].*$|$))|(hehe.+$) 

This only does a FULL match. Doing it for sub-matches would even be more awkward.

Yost answered 2/1, 2009 at 7:30 Comment(8)
Important to note this only uses basic POSIX.2 regular expressions and thus whilst terse is more portable for when PCRE is not available.Waylan
I agree. Many if not most regular expressions are not regular languages and could not be recognized by a finite automata.Kelleykelli
@ThomasMcLeod, Hades32: Is it within the realms of any possible regular language to be able to say ‘not’ and ‘and’ as well as the ‘or’ of an expression such as ‘(hede|Hihi)’? (This maybe a question for CS.)Carpophagous
@JohnAllen: ME!!! …Well, not the actual regex but the academic reference, which also relates closely to computational complexity; PCREs fundamentally can not guarantee the same efficiency as POSIX regular expressions.Carpophagous
@JamesHaigh, not <string> is definitely regular but does and make sense? Ask a new question and notify me in a comment and I'll try to answer it.Kelleykelli
Sorry -this answer just doesn't work, it will match hhehe and even match hehe partially (the second half)Chessman
This can be syntactically simplified to ^([^h].*|h([^e].*)?|he([^h].*)?|heh([^e].*)?|hehe.+)$ or ^(([^h]|h[^e]|he[^h]|heh[^e]|hehe.).*|h|he|heh)$.Beattie
See my answer for how to do it for submatches too. As for inverting a RE, the inverse always exists without needing extra operators. Grail has a tool to find it, the one I used in my answer.Broadspectrum
C
80

If you want the regex test to only fail if the entire string matches, the following will work:

^(?!hede$).*

e.g. -- If you want to allow all values except "foo" (i.e. "foofoo", "barfoo", and "foobar" will pass, but "foo" will fail), use: ^(?!foo$).*

Of course, if you're checking for exact equality, a better general solution in this case is to check for string equality, i.e.

myStr !== 'foo'

You could even put the negation outside the test if you need any regex features (here, case insensitivity and range matching):

!/^[a-f]oo$/i.test(myStr)

The regex solution at the top of this answer may be helpful, however, in situations where a positive regex test is required (perhaps by an API).

Chromaticity answered 2/1, 2009 at 7:30 Comment(4)
what about trailing whitespaces? Eg, if I want test to fail with string " hede "?Current
@Current the \s directive matches a single whitespace characterChromaticity
thanks, but I didn't manage to update the regex to make this work.Current
@eagor: ^(?!\s*hede\s*$).*Chromaticity
A
73

With negative lookahead, regular expression can match something not contains specific pattern. This is answered and explained by Bart Kiers. Great explanation!

However, with Bart Kiers' answer, the lookahead part will test 1 to 4 characters ahead while matching any single character. We can avoid this and let the lookahead part check out the whole text, ensure there is no 'hede', and then the normal part (.*) can eat the whole text all at one time.

Here is the improved regex:

/^(?!.*?hede).*$/

Note the (*?) lazy quantifier in the negative lookahead part is optional, you can use (*) greedy quantifier instead, depending on your data: if 'hede' does present and in the beginning half of the text, the lazy quantifier can be faster; otherwise, the greedy quantifier be faster. However if 'hede' does not present, both would be equal slow.

Here is the demo code.

For more information about lookahead, please check out the great article: Mastering Lookahead and Lookbehind.

Also, please check out RegexGen.js, a JavaScript Regular Expression Generator that helps to construct complex regular expressions. With RegexGen.js, you can construct the regex in a more readable way:

var _ = regexGen;

var regex = _(
    _.startOfLine(),             
    _.anything().notContains(       // match anything that not contains:
        _.anything().lazy(), 'hede' //   zero or more chars that followed by 'hede',
                                    //   i.e., anything contains 'hede'
    ), 
    _.endOfLine()
);
Avoirdupois answered 2/1, 2009 at 7:30 Comment(5)
so to simply check if given string does not contain str1 and str2: ^(?!.*(str1|str2)).*$Beer
Yes, or you can use lazy quantifier: ^(?!.*?(?:str1|str2)).*$, depending on your data. Added the ?: since we don't need to capture it.Avoirdupois
This is by far the best answer by a factor of 10xms. If you added your jsfiddle code and results onto the answer people might notice it. I wonder why the lazy version is faster than the greedy version when there is no hede. Shouldn't they take the same amount of time?Frendel
Yes, they take the same amount of time since they both tests the whole text.Avoirdupois
@Frendel the lazy version is most likely faster because of the underlying implementation and optimizations in the engine. computers are usually good at accessing data linearly from start to finish, caching and branch prediction may be optimized for this sort of access.Chessman
S
71

FWIW, since regular languages (aka rational languages) are closed under complementation, it's always possible to find a regular expression (aka rational expression) that negates another expression. But not many tools implement this.

Vcsn supports this operator (which it denotes {c}, postfix).

You first define the type of your expressions: labels are letter (lal_char) to pick from a to z for instance (defining the alphabet when working with complementation is, of course, very important), and the "value" computed for each word is just a Boolean: true the word is accepted, false, rejected.

In Python:

In [5]: import vcsn
        c = vcsn.context('lal_char(a-z), b')
        c
Out[5]: {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z} → 𝔹

then you enter your expression:

In [6]: e = c.expression('(hede){c}'); e
Out[6]: (hede)^c

convert this expression to an automaton:

In [7]: a = e.automaton(); a

The corresponding automaton

finally, convert this automaton back to a simple expression.

In [8]: print(a.expression())
        \e+h(\e+e(\e+d))+([^h]+h([^e]+e([^d]+d([^e]+e[^]))))[^]*

where + is usually denoted |, \e denotes the empty word, and [^] is usually written . (any character). So, with a bit of rewriting ()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*.

You can see this example here, and try Vcsn online there.

Samanthasamanthia answered 2/1, 2009 at 7:30 Comment(6)
True, but ugly, and only doable for small character sets. You don't want to do this with Unicode strings :-)Fregoso
The regexp ()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).* didn't work for me using egrep. It matches hede. I also tried anchoring it to the beginning and end, and it still didn't work.Broadspectrum
@PedroGimeno When you anchored, you made sure to put this regex in parens first? Otherwise the precedences between anchors and | won't play nicely. '^(()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*)$'.Samanthasamanthia
@Samanthasamanthia That seemed to be the problem, thanks and sorry (see my answer for a full substring match). And forgot to say, the graph doesn't have [^d] anywhere. I suspect that's a mistake.Broadspectrum
@PedroGimeno Thanks for pointing this out. When I first answered I read incorrectly and thought that hehe was to reject. I fixed the text, but forgot to fix the snapshot.Samanthasamanthia
I think it's worth remarking that this method is for matching lines that are not the word 'hede', rather than lines than don't contain the word 'hede' which is what the OP asked for. See my answer for the latter.Broadspectrum
K
67

Here's a good explanation of why it's not easy to negate an arbitrary regex. I have to agree with the other answers, though: if this is anything other than a hypothetical question, then a regex is not the right choice here.

Kunzite answered 2/1, 2009 at 7:30 Comment(4)
Some tools, and specifically mysqldumpslow, only offer this way to filter data, so in such a case, finding a regex to do this is the best solution apart from rewriting the tool (various patches for this have not been included by MySQL AB / Sun / Oracle.Barmecidal
Exactly analagous to my situation. Velocity template engine uses regular expressions to decide when to apply a transformation (escape html) and I want it to always work EXCEPT in one situation.Angie
What alternative is there? Ive never encountered anything that could do precise string matching besides regex. If OP is using a programming language, there may be other tools available, but if he/she is using not writing code, there probably isnt any other choice.Triploid
One of many non-hypothetical scenarios where a regex is the best available choice: I'm in an IDE (Android Studio) that shows log output, and the only filtering tools provided are: plain strings, and regex. Trying to do this with plain strings would be a complete fail.Offutt
C
55

Benchmarks

I decided to evaluate some of the presented Options and compare their performance, as well as use some new Features. Benchmarking on .NET Regex Engine: http://regexhero.net/tester/

Benchmark Text:

The first 7 lines should not match, since they contain the searched Expression, while the lower 7 lines should match!

Regex Hero is a real-time online Silverlight Regular Expression Tester.
XRegex Hero is a real-time online Silverlight Regular Expression Tester.
Regex HeroRegex HeroRegex HeroRegex HeroRegex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her Regex Her Regex Her Regex Her Regex Her Regex Her Regex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her is a real-time online Silverlight Regular Expression Tester.Regex Hero
egex Hero egex Hero egex Hero egex Hero egex Hero egex Hero Regex Hero is a real-time online Silverlight Regular Expression Tester.
RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRegex Hero is a real-time online Silverlight Regular Expression Tester.

Regex Her
egex Hero
egex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her is a real-time online Silverlight Regular Expression Tester.
Regex Her Regex Her Regex Her Regex Her Regex Her Regex Her is a real-time online Silverlight Regular Expression Tester.
Nobody is a real-time online Silverlight Regular Expression Tester.
Regex Her o egex Hero Regex  Hero Reg ex Hero is a real-time online Silverlight Regular Expression Tester.

Results:

Results are Iterations per second as the median of 3 runs - Bigger Number = Better

01: ^((?!Regex Hero).)*$                    3.914   // Accepted Answer
02: ^(?:(?!Regex Hero).)*$                  5.034   // With Non-Capturing group
03: ^(?!.*?Regex Hero).*                   7.356   // Lookahead at the beginning, if not found match everything
04: ^(?>[^R]+|R(?!egex Hero))*$             6.137   // Lookahead only on the right first letter
05: ^(?>(?:.*?Regex Hero)?)^.*$             7.426   // Match the word and check if you're still at linestart
06: ^(?(?=.*?Regex Hero)(?#fail)|.*)$       7.371   // Logic Branch: Find Regex Hero? match nothing, else anything

P1: ^(?(?=.*?Regex Hero)(*FAIL)|(*ACCEPT))  ?????   // Logic Branch in Perl - Quick FAIL
P2: .*?Regex Hero(*COMMIT)(*FAIL)|(*ACCEPT) ?????   // Direct COMMIT & FAIL in Perl

Since .NET doesn't support action Verbs (*FAIL, etc.) I couldn't test the solutions P1 and P2.

Summary:

The overall most readable and performance-wise fastest solution seems to be 03 with a simple negative lookahead. This is also the fastest solution for JavaScript, since JS does not support the more advanced Regex Features for the other solutions.

Chessman answered 2/1, 2009 at 7:30 Comment(1)
You should time ^(?!.*hede) too. /// Also, it's probably better to rank the expressions for the matching corpus and the non-matching corpus separately because it's usually a case that most line match or most lines don't.Macswan
B
39

Since no one else has given a direct answer to the question that was asked, I'll do it.

The answer is that with POSIX grep, it's impossible to literally satisfy this request:

grep "<Regex for 'doesn't contain hede'>" input

The reason is that with no flags, POSIX grep is only required to work with Basic Regular Expressions (BREs), which are simply not powerful enough for accomplishing that task, because of lack of alternation in subexpressions. The only kind of alternation it supports involves providing multiple regular expressions separated by newlines, and that doesn't cover all regular languages, e.g. there's no finite collection of BREs that matches the same regular language as the extended regular expression (ERE) ^(ab|cd)*$.

However, GNU grep implements extensions that allow it. In particular, \| is the alternation operator in GNU's implementation of BREs. If your regular expression engine supports alternation, parentheses and the Kleene star, and is able to anchor to the beginning and end of the string, that's all you need for this approach. Note however that negative sets [^ ... ] are very convenient in addition to those, because otherwise, you need to replace them with an expression of the form (a|b|c| ... ) that lists every character that is not in the set, which is extremely tedious and overly long, even more so if the whole character set is Unicode.

Thanks to formal language theory, we get to see how such an expression looks like. With GNU grep, the answer would be something like:

grep "^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$" input

(found with Grail and some further optimizations made by hand).

You can also use a tool that implements EREs, like egrep, to get rid of the backslashes, or equivalently, pass the -E flag to POSIX grep (although I was under the impression that the question required avoiding any flags to grep whatsoever):

egrep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" input

Here's a script to test it (note it generates a file testinput.txt in the current directory). Several of the expressions presented in other answers fail this test.

#!/bin/bash
REGEX="^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$"

# First four lines as in OP's testcase.
cat > testinput.txt <<EOF
hoho
hihi
haha
hede

h
he
ah
head
ahead
ahed
aheda
ahede
hhede
hehede
hedhede
hehehehehehedehehe
hedecidedthat
EOF
diff -s -u <(grep -v hede testinput.txt) <(grep "$REGEX" testinput.txt)

In my system it prints:

Files /dev/fd/63 and /dev/fd/62 are identical

as expected.

For those interested in the details, the technique employed is to convert the regular expression that matches the word into a finite automaton, then invert the automaton by changing every acceptance state to non-acceptance and vice versa, and then converting the resulting FA back to a regular expression.

As everyone has noted, if your regular expression engine supports negative lookahead, the regular expression is much simpler. For example, with GNU grep:

grep -P '^((?!hede).)*$' input

However, this approach has the disadvantage that it requires a backtracking regular expression engine. This makes it unsuitable in installations that are using secure regular expression engines like RE2, which is one reason to prefer the generated approach in some circumstances.

Using Kendall Hopkins' excellent FormalTheory library, written in PHP, which provides a functionality similar to Grail, and a simplifier written by myself, I've been able to write an online generator of negative regular expressions given an input phrase (only alphanumeric and space characters currently supported, and the length is limited): http://www.formauri.es/personal/pgimeno/misc/non-match-regex/

For hede it outputs:

^([^h]|h(h|e(h|dh))*([^eh]|e([^dh]|d[^eh])))*(h(h|e(h|dh))*(ed?)?)?$

which is equivalent to the above.

Broadspectrum answered 2/1, 2009 at 7:30 Comment(0)
F
36

Not regex, but I've found it logical and useful to use serial greps with pipe to eliminate noise.

eg. search an apache config file without all the comments-

grep -v '\#' /opt/lampp/etc/httpd.conf      # this gives all the non-comment lines

and

grep -v '\#' /opt/lampp/etc/httpd.conf |  grep -i dir

The logic of serial grep's is (not a comment) and (matches dir)

Fletafletch answered 2/1, 2009 at 7:30 Comment(2)
I think he is asking for the regex version of the grep -vMetatarsal
This is dangerous. Also misses lines like good_stuff #comment_stuffDejesus
A
31

with this, you avoid to test a lookahead on each positions:

/^(?:[^h]+|h++(?!ede))*+$/

equivalent to (for .net):

^(?>(?:[^h]+|h+(?!ede))*)$

Old answer:

/^(?>[^h]+|h+(?!ede))*$/
Almeda answered 2/1, 2009 at 7:30 Comment(4)
Good point; I'm surprised nobody mentioned this approach before. However, that particular regex is prone to catastrophic backtracking when applied to text that doesn't match. Here's how I would do it: /^[^h]*(?:h+(?!ede)[^h]*)*$/Dinse
...or you can just make all the quantifiers possessive. ;)Dinse
@Alan Moore - I'm surprised too. I saw your comment (and best regex in the pile) here only after posting this same pattern in an answer below.Impanation
@ridgerunner, doesn't have to be the best tho. I've seen benchmarks where the top answer performs better. (I was surprised about that tho.)Eurythmic
M
30

Aforementioned (?:(?!hede).)* is great because it can be anchored.

^(?:(?!hede).)*$               # A line without hede

foo(?:(?!hede).)*bar           # foo followed by bar, without hede between them

But the following would suffice in this case:

^(?!.*hede)                    # A line without hede

This simplification is ready to have "AND" clauses added:

^(?!.*hede)(?=.*foo)(?=.*bar)   # A line with foo and bar, but without hede
^(?!.*hede)(?=.*foo).*bar       # Same
Macswan answered 2/1, 2009 at 7:30 Comment(0)
M
28

An, in my opinon, more readable variant of the top answer:

^(?!.*hede)

Basically, "match at the beginning of the line if and only if it does not have 'hede' in it" - so the requirement translated almost directly into regex.

Of course, it's possible to have multiple failure requirements:

^(?!.*(hede|hodo|hada))

Details: The ^ anchor ensures the regex engine doesn't retry the match at every location in the string, which would match every string.

The ^ anchor in the beginning is meant to represent the beginning of the line. The grep tool matches each line one at a time, in contexts where you're working with a multiline string, you can use the "m" flag:

/^(?!.*hede)/m # JavaScript syntax

or

(?m)^(?!.*hede) # Inline flag
Mong answered 2/1, 2009 at 7:30 Comment(3)
One difference from top answer is that this does not match anything, and that matches the whole line if without "hede"Freshen
@BernardoDalCorno This can easily be changed by adding .* to the expression: ^(?!.*hede).* the match will then contain all text.Chessman
This answer seems to be the most efficient one for JavaScript, since all other answers will run into "max call stack size exceeded" on really big input. This answer uses no groups, just a simple lookahead.Chessman
I
24

Here's how I'd do it:

^[^h]*(h(?!ede)[^h]*)*$

Accurate and more efficient than the other answers. It implements Friedl's "unrolling-the-loop" efficiency technique and requires much less backtracking.

Impanation answered 2/1, 2009 at 7:30 Comment(1)
What if the search word contains 2 more more of the same first letter? like hhede or hedhe??Brood
D
21

Another option is that to add a positive look-ahead and check if hede is anywhere in the input line, then we would negate that, with an expression similar to:

^(?!(?=.*\bhede\b)).*$

with word boundaries.


The expression is explained on the top right panel of regex101.com, if you wish to explore/simplify/modify it, and in this link, you can watch how it would match against some sample inputs, if you like.


RegEx Circuit

jex.im visualizes regular expressions:

enter image description here

Dmz answered 2/1, 2009 at 7:30 Comment(2)
I don't understand how the "inner" positive lookahead is useful.Erivan
It is a camouflaged ^(?!.*\bhede\b).*$Follmer
F
20

If you want to match a character to negate a word similar to negate character class:

For example, a string:

<?
$str="aaa        bbb4      aaa     bbb7";
?>

Do not use:

<?
preg_match('/aaa[^bbb]+?bbb7/s', $str, $matches);
?>

Use:

<?
preg_match('/aaa(?:(?!bbb).)+?bbb7/s', $str, $matches);
?>

Notice "(?!bbb)." is neither lookbehind nor lookahead, it's lookcurrent, for example:

"(?=abc)abcde", "(?!abc)abcde"
Forsooth answered 2/1, 2009 at 7:30 Comment(2)
There is no "lookcurrent" in perl regexp's. This is truly a negative lookahead (prefix (?!). Positive lookahead's prefix would be (?= while the corresponding lookbehind prefixes would be (?<! and (?<= respectively. A lookahead means that you read the next characters (hence “ahead”) without consuming them. A lookbehind means that you check characters that have already been consumed.Guaranty
Not sure how (?!abc)abcde makes any sense at all.Erivan
P
15

The OP did not specify or Tag the post to indicate the context (programming language, editor, tool) the Regex will be used within.

For me, I sometimes need to do this while editing a file using Textpad.

Textpad supports some Regex, but does not support lookahead or lookbehind, so it takes a few steps.

If I am looking to retain all lines that Do NOT contain the string hede, I would do it like this:

1. Search/replace the entire file to add a unique "Tag" to the beginning of each line containing any text.

    Search string:^(.)  
    Replace string:<@#-unique-#@>\1  
    Replace-all  

2. Delete all lines that contain the string hede (replacement string is empty):

    Search string:<@#-unique-#@>.*hede.*\n  
    Replace string:<nothing>  
    Replace-all  

3. At this point, all remaining lines Do NOT contain the string hede. Remove the unique "Tag" from all lines (replacement string is empty):

    Search string:<@#-unique-#@>
    Replace string:<nothing>  
    Replace-all  

Now you have the original text with all lines containing the string hede removed.


If I am looking to Do Something Else to only lines that Do NOT contain the string hede, I would do it like this:

1. Search/replace the entire file to add a unique "Tag" to the beginning of each line containing any text.

    Search string:^(.)  
    Replace string:<@#-unique-#@>\1  
    Replace-all  

2. For all lines that contain the string hede, remove the unique "Tag":

    Search string:<@#-unique-#@>(.*hede)
    Replace string:\1  
    Replace-all  

3. At this point, all lines that begin with the unique "Tag", Do NOT contain the string hede. I can now do my Something Else to only those lines.

4. When I am done, I remove the unique "Tag" from all lines (replacement string is empty):

    Search string:<@#-unique-#@>
    Replace string:<nothing>  
    Replace-all  
Puritan answered 2/1, 2009 at 7:30 Comment(0)
T
13

Since the introduction of ruby-2.4.1, we can use the new Absent Operator in Ruby’s Regular Expressions

from the official doc

(?~abc) matches: "", "ab", "aab", "cccc", etc.
It doesn't match: "abc", "aabc", "ccccabc", etc.

Thus, in your case ^(?~hede)$ does the job for you

2.4.1 :016 > ["hoho", "hihi", "haha", "hede"].select{|s| /^(?~hede)$/.match(s)}
 => ["hoho", "hihi", "haha"]
Tsarevna answered 2/1, 2009 at 7:30 Comment(0)
J
12

Through PCRE verb (*SKIP)(*F)

^hede$(*SKIP)(*F)|^.*$

This would completely skips the line which contains the exact string hede and matches all the remaining lines.

DEMO

Execution of the parts:

Let us consider the above regex by splitting it into two parts.

  1. Part before the | symbol. Part shouldn't be matched.

    ^hede$(*SKIP)(*F)
    
  2. Part after the | symbol. Part should be matched.

    ^.*$
    

PART 1

Regex engine will start its execution from the first part.

^hede$(*SKIP)(*F)

Explanation:

  • ^ Asserts that we are at the start.
  • hede Matches the string hede
  • $ Asserts that we are at the line end.

So the line which contains the string hede would be matched. Once the regex engine sees the following (*SKIP)(*F) (Note: You could write (*F) as (*FAIL)) verb, it skips and make the match to fail. | called alteration or logical OR operator added next to the PCRE verb which inturn matches all the boundaries exists between each and every character on all the lines except the line contains the exact string hede. See the demo here. That is, it tries to match the characters from the remaining string. Now the regex in the second part would be executed.

PART 2

^.*$

Explanation:

  • ^ Asserts that we are at the start. ie, it matches all the line starts except the one in the hede line. See the demo here.
  • .* In the Multiline mode, . would match any character except newline or carriage return characters. And * would repeat the previous character zero or more times. So .* would match the whole line. See the demo here.

    Hey why you added .* instead of .+ ?

    Because .* would match a blank line but .+ won't match a blank. We want to match all the lines except hede , there may be a possibility of blank lines also in the input . so you must use .* instead of .+ . .+ would repeat the previous character one or more times. See .* matches a blank line here.

  • $ End of the line anchor is not necessary here.

Jasonjasper answered 2/1, 2009 at 7:30 Comment(0)
W
8

It may be more maintainable to two regexes in your code, one to do the first match, and then if it matches run the second regex to check for outlier cases you wish to block for example ^.*(hede).* then have appropriate logic in your code.

OK, I admit this is not really an answer to the posted question posted and it may also use slightly more processing than a single regex. But for developers who came here looking for a fast emergency fix for an outlier case then this solution should not be overlooked.

Wavelet answered 2/1, 2009 at 7:30 Comment(0)
C
8

The TXR Language supports regex negation.

$ txr -c '@(repeat)
@{nothede /~hede/}
@(do (put-line nothede))
@(end)'  Input

A more complicated example: match all lines that start with a and end with z, but do not contain the substring hede:

$ txr -c '@(repeat)
@{nothede /a.*z&~.*hede.*/}
@(do (put-line nothede))
@(end)' -
az         <- echoed
az
abcz       <- echoed
abcz
abhederz   <- not echoed; contains hede
ahedez     <- not echoed; contains hede
ace        <- not echoed; does not end in z
ahedz      <- echoed
ahedz

Regex negation is not particularly useful on its own but when you also have intersection, things get interesting, since you have a full set of boolean set operations: you can express "the set which matches this, except for things which match that".

Cornhusking answered 2/1, 2009 at 7:30 Comment(1)
Note that it is also the solution for ElasticSearch Lucene based regex.Follmer
H
6

As long as you are dealing with lines, simply mark the negative matches and target the rest.

In fact, I use this trick with sed because ^((?!hede).)*$ looks not supported by it.

For the desired output

  1. Mark the negative match: (e.g. lines with hede), using a character not included in the whole text at all. An emoji could probably be a good choice for this purpose.

    s/(.*hede)/🔒\1/g
    
  2. Target the rest (the unmarked strings: e.g. lines without hede). Suppose you want to keep only the target and delete the rest (as you want):

    s/^🔒.*//g
    

For a better understanding

Suppose you want to delete the target:

  1. Mark the negative match: (e.g. lines with hede), using a character not included in the whole text at all. An emoji could probably be a good choice for this purpose.

    s/(.*hede)/🔒\1/g
    
  2. Target the rest (the unmarked strings: e.g. lines without hede). Suppose you want to delete the target:

    s/^[^🔒].*//g
    
  3. Remove the mark:

    s/🔒//g
    
Heffernan answered 2/1, 2009 at 7:30 Comment(0)
E
6

I wanted to add another example for if you are trying to match an entire line that contains string X, but does not also contain string Y.

For example, let's say we want to check if our URL / string contains "tasty-treats", so long as it does not also contain "chocolate" anywhere.

This regex pattern would work (works in JavaScript too)

^(?=.*?tasty-treats)((?!chocolate).)*$

(global, multiline flags in example)

Interactive Example: https://regexr.com/53gv4

Matches

(These urls contain "tasty-treats" and also do not contain "chocolate")

  • example.com/tasty-treats/strawberry-ice-cream
  • example.com/desserts/tasty-treats/banana-pudding
  • example.com/tasty-treats-overview

Does Not Match

(These urls contain "chocolate" somewhere - so they won't match even though they contain "tasty-treats")

  • example.com/tasty-treats/chocolate-cake
  • example.com/home-cooking/oven-roasted-chicken
  • example.com/tasty-treats/banana-chocolate-fudge
  • example.com/desserts/chocolate/tasty-treats
  • example.com/chocolate/tasty-treats/desserts
Empale answered 2/1, 2009 at 7:30 Comment(0)
S
6

The below function will help you get your desired output

<?PHP
      function removePrepositions($text){
            
            $propositions=array('/\bfor\b/i','/\bthe\b/i'); 
        
            if( count($propositions) > 0 ) {
                foreach($propositions as $exceptionPhrase) {
                    $text = preg_replace($exceptionPhrase, '', trim($text));

                }
            $retval = trim($text);

            }
        return $retval;
    }
     
        
?>
Sapodilla answered 2/1, 2009 at 7:30 Comment(0)
R
5

^((?!hede).)*$ is an elegant solution, except since it consumes characters you won't be able to combine it with other criteria. For instance, say you wanted to check for the non-presence of "hede" and the presence of "haha." This solution would work because it won't consume characters:

^(?!.*\bhede\b)(?=.*\bhaha\b) 
Robichaux answered 2/1, 2009 at 7:30 Comment(0)
H
3

How to use PCRE's backtracking control verbs to match a line not containing a word

Here's a method that I haven't seen used before:

/.*hede(*COMMIT)^|/

How it works

First, it tries to find "hede" somewhere in the line. If successful, at this point, (*COMMIT) tells the engine to, not only not backtrack in the event of a failure, but also not to attempt any further matching in that case. Then, we try to match something that cannot possibly match (in this case, ^).

If a line does not contain "hede" then the second alternative, an empty subpattern, successfully matches the subject string.

This method is no more efficient than a negative lookahead, but I figured I'd just throw it on here in case someone finds it nifty and finds a use for it for other, more interesting applications.

Heavyladen answered 2/1, 2009 at 7:30 Comment(0)
F
3

A simpler solution is to use the not operator !

Your if statement will need to match "contains" and not match "excludes".

var contains = /abc/;
var excludes =/hede/;

if(string.match(contains) && !(string.match(excludes))){  //proceed...

I believe the designers of RegEx anticipated the use of not operators.

Flabbergast answered 2/1, 2009 at 7:30 Comment(0)
M
2

Maybe you'll find this on Google while trying to write a regex that is able to match segments of a line (as opposed to entire lines) which do not contain a substring. Tooke me a while to figure out, so I'll share:

Given a string:

<span class="good">bar</span><span class="bad">foo</span><span class="ugly">baz</span>

I want to match <span> tags which do not contain the substring "bad".

/<span(?:(?!bad).)*?> will match <span class=\"good\"> and <span class=\"ugly\">.

Notice that there are two sets (layers) of parentheses:

  • The innermost one is for the negative lookahead (it is not a capture group)
  • The outermost was interpreted by Ruby as capture group but we don't want it to be a capture group, so I added ?: at it's beginning and it is no longer interpreted as a capture group.

Demo in Ruby:

s = '<span class="good">bar</span><span class="bad">foo</span><span class="ugly">baz</span>'
s.scan(/<span(?:(?!bad).)*?>/)
# => ["<span class=\"good\">", "<span class=\"ugly\">"]
Min answered 2/1, 2009 at 7:30 Comment(0)
P
1

Simplest thing that I could find would be

[^(hede)]

Tested at https://regex101.com/

You can also add unit-test cases on that site

Premolar answered 2/1, 2009 at 7:30 Comment(2)
Not works on visual codeLagunas
This simply finds a single character which is not (, h, e, d, or ).Quibbling
M
1

With ConyEdit, you can use the command line cc.gl !/hede/ to get lines that do not contain the regex matching, or use the command line cc.dl /hede/ to delete lines that contain the regex matching. They have the same result.

Mou answered 2/1, 2009 at 7:30 Comment(1)
Similarly, but less obscurely, awk '!/hede/' or grep -v 'hede'Quibbling
R
0

While its true that you can use a look around, I read an article which uses another method which seems more elegant and less syntactically cumbersome.

The idea is counterintuitive: to actually match the stuff you don't want to match but only match what you want to match inside groups & reference them later.

E.g. Blacklisting words: pattern='\bTarzan\b|\bJane\b|(\w+)' Then just use group(1) e.g. '\1' to get your matches which exclude the prefixed words.

Great article describing this: https://www.rexegg.com/regex-best-trick.html#simplecase & Great SO Answer which also describes it: How do (*SKIP) or (*F) work on regex?

Rachellerachis answered 2/1, 2009 at 7:30 Comment(0)
E
0

Using (?<!hede) is a better answer. (?<!whateverYouDontWantToMatch) is a negative look behind as opposed to (?!whateverYouDontWantToMatch) which is a negative look ahead. which means that with (?<!) it will check right at the current position of the string instead of only looking after the match. So for example. You will run into issues using (?!) and it only works in this case because of the anchor.

Epsilon answered 2/1, 2009 at 7:30 Comment(0)
N
0
# 一个简单的方式
import re
skip_word = 'hede'
stranger_char = '虩'
content = '''hoho
hihi
haha
hede'''
print(
    '\n'.join(re.findall(
        '([^{}]*?)\n'.format(stranger_char), 
        content.replace(skip_word, stranger_char)
    )).replace(stranger_char, skip_word) 
)

# hoho
# hihi
# haha
Nathanialnathaniel answered 2/1, 2009 at 7:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.