Вопрос: Регулярное выражение для соответствия строке, которая не содержит слова?


Я знаю, что можно совместить слово, а затем разворачивать спички, используя другие инструменты (например, grep -v). Тем не менее, я хотел бы знать, можно ли сопоставлять строки, которые не содержат определенное слово (например, hede), используя регулярное выражение.

Входные данные:

hoho
hihi
haha
hede

Код:

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

Желаемый результат:

hoho
hihi
haha

3506


источник


Ответы:


Понятие о том, что регулярное выражение не поддерживает обратное совпадение, не совсем верно. Вы можете имитировать это поведение, используя негативные взгляды:

^((?!hede).)*$

Регулярное выражение выше будет соответствовать любой строке или строке без разрыва строки, не содержащий (под) строку 'hede'. Как уже упоминалось, это не то, что регулярное выражение «хорошо» (или должно делать), но все же оно является возможное.

И если вам нужно также совместить символы разрыва строки, используйте Модификатор DOT-ALL (конечный sв следующем шаблоне):

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

или использовать его inline:

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

(где /.../являются ограничителями регулярных выражений, т. е. не являются частью шаблона)

Если модификатор DOT-ALL недоступен, вы можете подражать аналогичному поведению с классом символов [\s\S]:

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

объяснение

Строка - это всего лишь список nперсонажи. До и после каждого символа есть пустая строка. Итак, список nперсонажи будут иметь n+1пустые строки. Рассмотрим строку "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

где e's - это пустые строки. Регулярное выражение (?!hede).смотрит, нет ли подстроки "hede"и, если это так (так что что-то еще видно), то .(точка) будет соответствовать любому символу, кроме разрыва строки. Обзоры также называются нулевая ширина-утверждение потому что они не потреблять любые символы. Они только утверждают / проверяют что-то.

Итак, в моем примере каждая пустая строка сначала проверяется, не существует ли "hede"впереди, прежде чем персонаж будет поглощен .(Точка). Регулярное выражение (?!hede).будет делать это только один раз, поэтому он завернут в группу и повторит нуль или более раз: ((?!hede).)*, Наконец, начало и конец ввода привязаны, чтобы убедиться, что весь вход потреблен: ^((?!hede).)*$

Как вы можете видеть, вход "ABhedeCD"потерпит неудачу, потому что e3, регулярное выражение (?!hede)не удается (там является "hede"впереди!).


4774



Заметим, что решение не начните с «Hede» :

^(?!hede).*$

как правило, намного эффективнее решения не содержать «Hede» :

^((?!hede).)*$

Первый проверяет «hede» только на первой позиции входной строки, а не на каждой позиции.


598



Если вы просто используете его для grep, вы можете использовать grep -v hedeчтобы получить все строки, которые не содержат hede.

ETA О, перечитывая вопрос, grep -vвероятно, то, что вы имели в виду под «вариантами инструментов».


161



Ответ:

^((?!hede).)*$

Объяснение:

^начало строки, (группу и захват до \ 1 (0 или более раз (соответствует наибольшей возможной сумме)),
(?!смотрите вперед, чтобы убедиться, что нет,

hedeваша строка,

)конец ожидания, .любой символ, кроме \ n,
)*end of \ 1 (Примечание: поскольку вы используете квантификатор для этого захвата, только LAST повторение захваченного шаблона будет сохранено в \ 1)
$перед необязательным \ n, а конец строки


115



Данные ответы совершенно прекрасные, просто академические точки:

Регулярные выражения в значении теоретических компьютерных наук НЕ БЫЛО делай это так. Для них это должно было выглядеть примерно так:

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

Это только ПОЛНОЕ совпадение. Выполнение этого для вспомогательных матчей было бы еще более неудобным.


89



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 may be helpful, however, in situations where a positive regex test is required (perhaps by an API).


48



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.


47



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.


41



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: ^(?>[^R]+|R(?!egex Hero))*$             6.137   // Lookahead only on the right first letter
04: ^(?>(?:.*?Regex Hero)?)^.*$             7.426   // Match the word and check if you're still at linestart
05: ^(?(?=.*?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:

I tried to test most proposed solutions, some Optimizations are possible for certain words. For Example if the First two letters of the search string are not the Same, answer 03 can be expanded to ^(?>[^R]+|R+(?!egex Hero))*$ resulting in a small performance gain.

But the overall most readable and performance-wise fastest solution seems to be 05 using a conditional statement or 04 with the possesive quantifier. I think the Perl solutions should be even faster and more easily readable.


38



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()
);

37



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)


30