Регулярное выражение для соответствия строке, не содержащей слова

4295

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

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

hoho
hihi
haha
hede

Код:

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

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

hoho
hihi
haha
knaser
источник
85
Возможно, на пару лет позже, но что не так с ([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$)))*:? Идея проста. Продолжайте сопоставление, пока не увидите начало нежелательной строки, затем сопоставляйте только в N-1 случаях, когда строка не завершена (где N - длина строки). Эти случаи N-1: «h, сопровождаемый не-е», «он следует, не-d», и «hed, сопровождаемый не-e». Если вам удалось пропустить эти случаи N-1, вы успешно не сопоставили нежелательную строку, поэтому вы можете начать поиск [^h]*снова
stevendesu
323
@stevendesu: попробуйте это как «очень-очень-длинное слово» или даже лучше половину предложения. Весело печатать. Кстати, это почти не читается. Не знаю о влиянии на производительность.
Питер Шютце
13
@PeterSchuetze: Конечно, это не очень красиво для очень длинных слов, но это жизнеспособное и правильное решение. Хотя я не проводил тесты производительности, я бы не подумал, что она слишком медленная, поскольку большинство последних правил игнорируются до тех пор, пока вы не увидите h (или первую букву слова, предложения и т. Д.). И вы можете легко сгенерировать строку регулярного выражения для длинных строк, используя итеративную конкатенацию. Если это работает и может генерироваться быстро, важна ли разборчивость? Для этого и нужны комментарии.
stevendesu
57
@stevendesu: я даже позже, но этот ответ почти полностью неверен. во-первых, он требует, чтобы субъект содержал «h», чего не должно быть, учитывая, что задание «сопоставить строки, которые [не содержат] конкретного слова». давайте предположим, что вы хотели сделать внутреннюю группу необязательной и что шаблон привязан: ^([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$))?)*$ это происходит, когда экземплярам "hede" предшествуют частичные экземпляры "hede", такие как "hhede".
Jaytea
8
Этот вопрос был добавлен в FAQ по регулярному выражению переполнения стека в разделе «Advanced Regex-Fu».
aliteralmind

Ответы:

5897

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

^((?!hede).)*$

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

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

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

или используйте его в строке:

/(?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это пустые строки. Регулярное выражение (?!hede).смотрит вперед, чтобы увидеть, нет ли подстроки, "hede"которая будет видна, и если это так (то есть что-то еще видно), то .(точка) будет соответствовать любому символу, кроме разрыва строки. Осмотры также называются утверждениями нулевой ширины, потому что они не потребляют никаких символов. Они только утверждают / подтверждают что-то.

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

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

Барт Киерс
источник
26
Я бы не сказал, что это плохо для регулярных выражений. Удобство этого решения довольно очевидно, и снижение производительности по сравнению с программным поиском часто оказывается несущественным.
Архимаредес
29
Строго говоря, отрицательный перебор делает регулярное выражение нерегулярным.
Питер К
55
@PeterK, конечно, но это ТАК, а не MathOverflow или CS-Stackexchange. Люди, задающие вопрос здесь, обычно ищут практический ответ. Большинство библиотек или инструментов (например grep, которые упоминает OP) с поддержкой регулярных выражений имеют функции, которые делают их нерегулярными в теоретическом смысле.
Барт Киерс
19
@ Барт Киерс, не обижайся на тебя, ответь, только это злоупотребление терминологией меня немного раздражает. Действительно запутанная часть в том, что регулярные выражения в строгом смысле слова могут делать то, что хочет OP, но общий язык их написания не позволяет этого, что приводит к (математически некрасивым) обходным путям, таким как упреждающие просмотры. Пожалуйста, посмотрите этот ответ ниже и мой комментарий для (теоретически выровненного) правильного способа сделать это. Излишне говорить, что он работает быстрее на больших входах.
Питер К
17
Если вы когда-нибудь задумывались, как это сделать в vim:^\(\(hede\)\@!.\)*$
baldrs
739

Обратите внимание, что решение не начинается с «хеде» :

^(?!hede).*$

обычно гораздо эффективнее, чем решение , не содержащее «хеде» :

^((?!hede).)*$

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

FireCoding
источник
5
Спасибо, я использовал его для проверки того, что строка не содержит последовательности цифр ^ ((?! \ D {5,}).) *
Самих,
2
Здравствуйте! Я не могу сочинять , не заканчивается "хеде" регулярное выражение. Вы можете помочь с этим?
Алекс Я.
1
@AleksYa: просто используйте версию «содержать» и включите конечный якорь в строку поиска: измените строку с «не совпадает» с «hede» на «hede $»
Nyerguds
2
@AleksYa: не заканчивается версия может быть сделано с помощью отрицательного просмотра назад , как: (.*)(?<!hede)$. Версия @Nyerguds тоже подойдет, но полностью упускает момент производительности, о котором говорится в ответе.
thisismydesign
5
Почему так много ответов ^((?!hede).)*$? Разве это не более эффективно для использования ^(?!.*hede).*$? Он делает то же самое, но в несколько шагов
JackPRead
208

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

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

Афина
источник
22
Совет: для постепенной фильтрации того, что вам не нужно: grep -v "hede" | grep -v "хихи" | ...так далее.
Оливье Лалонд
51
Или используя только один процессgrep -v -e hede -e hihi -e ...
Олаф Дитче
15
Или просто grep -v "hede\|hihi":)
Путник
2
Если у вас есть много шаблонов, которые вы хотите отфильтровать, поместите их в файл и используйтеgrep -vf pattern_file file
codeforester
4
Или просто egrepили grep -Ev "hede|hihi|etc"чтобы избежать неловкого побега.
Амит Найду
161

Ответ:

^((?!hede).)*$

Объяснение:

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

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

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

Джессика
источник
14
удивительно, что работал для меня в возвышенном тексте 2, используя несколько слов ' ^((?!DSAU_PW8882WEB2|DSAU_PW8884WEB2|DSAU_PW8884WEB).)*$'
Дамодар Башьял
3
@DamodarBashyal Я знаю, что я довольно поздно здесь, но вы можете полностью удалить второй срок там, и вы получите те же самые результаты
forresthopkinsa
99

Приведенные ответы прекрасно, просто академический балл:

Регулярные выражения в смысле теоретических компьютерных наук НЕ МОГУТ делать это так. Для них это должно было выглядеть примерно так:

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

Это только соответствует ПОЛНОМУ. Делать это для под-матчей было бы еще более неловко.

Hades32
источник
1
Важно отметить, что в нем используются только основные регулярные выражения POSIX.2, и поэтому он более переносим, ​​когда PCRE недоступен.
Steve-o
5
Согласен. Многие, если не большинство регулярных выражений, не являются регулярными языками и не могут быть распознаны конечными автоматами.
ThomasMcLeod
@ThomasMcLeod, Hades32: Это в пределах возможного регулярного языка, чтобы быть в состоянии сказать « не » и « и », а также « или » выражения, такого как « (hede|Hihi)»? (Это может быть вопрос для CS.)
Джеймс Хей
7
@JohnAllen: Я !!! … Ну, не фактическое регулярное выражение, а академическая справка, которая также тесно связана с вычислительной сложностью; PCRE принципиально не может гарантировать такую ​​же эффективность, как регулярные выражения POSIX.
Джеймс Хей
4
Извините - этот ответ просто не работает, он будет совпадать с хе-хе и даже частично совпадать с хе-хе (вторая половина)
Falco
60

Если вы хотите, чтобы тест регулярного выражения завершился неудачей, только если вся строка совпадает, будет работать следующее:

^(?!hede$).*

Например, если вы хотите разрешить все значения, кроме «foo» (то есть «foofoo», «barfoo» и «foobar» пройдут, но «foo» завершится ошибкой), используйте: ^(?!foo$).*

Конечно, если вы проверяете точное равенство, лучшим общим решением в этом случае является проверка на равенство строк, т.е.

myStr !== 'foo'

Вы даже можете поместить отрицание вне теста, если вам нужны какие-либо функции регулярных выражений (здесь, нечувствительность к регистру и согласование диапазона):

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

Однако решение regex в верхней части этого ответа может быть полезным в ситуациях, когда требуется положительный тест regex (возможно, через API).

Рой Тинкер
источник
как насчет конечных пробелов? Например, если я хочу, чтобы тест провалился со строкой " hede "?
eagor
@eagor \sдиректива соответствует одному пробелу
Рой Тинкер
спасибо, но мне не удалось обновить регулярное выражение, чтобы сделать эту работу.
eagor
2
@eagor:^(?!\s*hede\s*$).*
Рой Тинкер
52

FWIW, поскольку регулярные языки (или рациональные языки) закрыты при дополнении, всегда можно найти регулярное выражение (также называемое рациональное выражение), которое отрицает другое выражение. Но не многие инструменты реализуют это.

Vcsn поддерживает этот оператор (который обозначает {c}postfix).

Вы сначала определить тип ваших выражений: этикетки письмо ( lal_char) , чтобы выбрать из , aчтобы z, например (определение алфавита при работе с комплементарности, конечно, очень важно), и «значение» вычисляется для каждого слова просто Boolean : trueслово принято false, отклонено.

В 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}  𝔹

затем вы вводите выражение:

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

преобразовать это выражение в автомат:

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

Соответствующий автомат

наконец, преобразовать этот автомат обратно в простое выражение.

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

где +обычно обозначается |, \eобозначает пустое слово и [^]обычно пишется .(любой символ). Итак, немного переписав ()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*.

Вы можете увидеть этот пример здесь и попробовать Vcsn онлайн там .

аким
источник
6
Правда, но некрасиво и выполнимо только для небольших наборов символов. Вы не хотите делать это со строками Unicode :-)
reinierpost
Есть больше инструментов, которые позволяют это, одним из самых впечатляющих является Ragel . Там это будет записано как (any * - ('hehe' any *)) для начального совпадения или (any * - ('hehe' any *)) для невыровненного.
Питер К
1
@reinierpost: почему это некрасиво и в чем проблема с юникодом? Я не могу согласиться с обоими. (У меня нет опыта работы с vcsn, но есть с DFA).
Питер К
3
@PedroGimeno Когда вы поставили на якорь, вы сначала поставили это регулярное выражение в скобки? В противном случае приоритеты между якорями и |не будут играть хорошо. '^(()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*)$',
аким
1
Я думаю, стоит отметить, что этот метод предназначен для сопоставления строк, которые не являются словом «хеде», а не строк, а не содержат слово «хеде», о котором просил ОП. Смотрите мой ответ для последнего.
Педро Химено
51

Вот хорошее объяснение того, почему не легко отрицать произвольное регулярное выражение. Я должен согласиться с другими ответами, хотя: если это что-то кроме гипотетического вопроса, то регулярное выражение здесь не правильный выбор.

Джош Ли
источник
10
Некоторые инструменты, в частности mysqldumpslow, предлагают только такой способ фильтрации данных, поэтому в таком случае поиск регулярного выражения для этого является лучшим решением, кроме переписывания инструмента (различные исправления для этого не включены в MySQL AB / Sun). / Oracle.
FGM
1
Точно аналогично моей ситуации. Шаблонный движок Velocity использует регулярные выражения, чтобы решить, когда применять преобразование (escape html), и я хочу, чтобы он всегда работал, КРОМЕ в одной ситуации.
Хенно Вермёлен
1
Какая альтернатива есть? Я никогда не сталкивался с чем-то, что могло бы сделать точное сопоставление строк, кроме регулярных выражений. Если OP использует язык программирования, могут быть доступны другие инструменты, но если он / она использует не пишущий код, другого выбора, вероятно, нет.
kingfrito_5005
2
Один из многих негипотетических сценариев, где регулярное выражение - лучший доступный выбор: я нахожусь в IDE (Android Studio), которая показывает вывод журнала, и единственные предоставляемые инструменты фильтрации: простые строки и регулярное выражение. Попытка сделать это с простыми строками будет полным провалом.
LarsH
48

С отрицательным взглядом, регулярное выражение может соответствовать чему-то, не содержащему определенного шаблона. На это отвечает и объясняет Барт Киерс. Отличное объяснение!

Тем не менее, с ответом Барта Киерса, предварительная часть будет проверять от 1 до 4 символов вперед при сопоставлении с любым отдельным символом. Мы можем избежать этого и позволить предзаголовочной части проверить весь текст, убедиться, что нет «хеде», и тогда нормальная часть (. *) Может съесть весь текст за один раз.

Вот улучшенное регулярное выражение:

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

Обратите внимание, что (*?) Ленивый квантификатор в части с отрицательным прогнозом не является обязательным, вы можете вместо этого использовать (*) жадный квантификатор, в зависимости от ваших данных: если 'hede' присутствует и в первой половине текста, ленивый квантификатор может быть быстрее; в противном случае жадный квантификатор будет быстрее. Однако, если «hede» не присутствует, оба будут равны медленно.

Вот демонстрационный код .

Для получения дополнительной информации о Lookahead, пожалуйста, прочитайте большую статью: Освоение Lookahead и Lookbehind .

Кроме того, ознакомьтесь с RegexGen.js , генератором регулярных выражений JavaScript, который помогает создавать сложные регулярные выражения. С помощью RegexGen.js вы можете создать регулярное выражение более читабельным способом:

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()
);
amobiz
источник
3
так что просто проверить, не содержит ли данная строка str1 и str2:^(?!.*(str1|str2)).*$
S.Serpooshan
1
Да, или вы можете использовать ленивый квантификатор:, в ^(?!.*?(?:str1|str2)).*$зависимости от ваших данных. Добавил, ?:так как нам не нужно захватывать его.
amobiz
Это, безусловно, лучший ответ в 10 раз. Если вы добавили свой код jsfiddle и результаты в ответ, люди могут заметить это. Интересно, почему ленивая версия быстрее жадной версии, когда нет хеде. Разве они не должны занимать одинаковое количество времени?
user5389726598465
Да, они занимают одинаковое количество времени, поскольку они оба проверяют весь текст.
amobiz
41

Ориентиры

Я решил оценить некоторые из представленных опций и сравнить их производительность, а также использовать некоторые новые функции. Сравнительный анализ на .NET Regex Engine: http://regexhero.net/tester/

Контрольный текст:

Первые 7 строк не должны совпадать, поскольку они содержат искомое выражение, а нижние 7 строк должны совпадать!

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.

Результаты:

Результатами являются итерации в секунду в качестве медианы 3 прогонов - большее число = лучшее

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

Поскольку .NET не поддерживает глаголы действий (* FAIL и т. Д.), Я не смог протестировать решения P1 и P2.

Резюме:

Я пытался протестировать большинство предложенных решений, возможна некоторая оптимизация для определенных слов. Например, если первые две буквы строки поиска не совпадают, ответ 03 можно расширить до ^(?>[^R]+|R+(?!egex Hero))*$небольшого прироста производительности.

Но в целом наиболее читаемым и быстродействующим решением, по-видимому, является 05 с использованием условного оператора или 04 с положительным квантификатором. Я думаю, что Perl-решения должны быть еще быстрее и более легко читаемыми.

Falco
источник
5
Вы должны время ^(?!.*hede)тоже. /// Кроме того, вероятно, лучше ранжировать выражения для совпадающего и несовпадающего корпусов по отдельности, потому что это обычно тот случай, когда большинство совпадений строк или большинство строк этого не делают.
Икегами
32

Не регулярное выражение, но я нашел логичным и полезным использовать последовательные greps с pipe для устранения шума.

например. искать файл конфигурации apache без всех комментариев-

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

а также

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

Логика последовательных grep'ов есть (не комментарий) и (соответствует dir)

kiwalk
источник
2
Я думаю, что он запрашивает версию регулярного выраженияgrep -v
Angel.King.47
9
Это опасно Также пропускает такие строки, какgood_stuff #comment_stuff
Хави Монтеро
29

при этом вы избегаете проверять прогноз на каждой позиции:

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

эквивалентно (для .net):

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

Старый ответ:

/^(?>[^h]+|h+(?!ede))*$/
Casimir et Hippolyte
источник
7
Хорошая точка зрения; Я удивлен, что никто не упомянул этот подход раньше. Тем не менее, это конкретное регулярное выражение склонно к катастрофическому откату назад применительно к тексту, который не соответствует. Вот как бы я это сделал:/^[^h]*(?:h+(?!ede)[^h]*)*$/
Алан Мур
... или вы можете просто сделать все квантификаторы притяжательными. ;)
Алан Мур
@ Алан Мур - я тоже удивлен. Я видел ваш комментарий (и лучшее регулярное выражение в куче) здесь только после публикации этого же паттерна в ответе ниже.
Ridgerunner
@ridgerunner, не обязательно быть лучшим. Я видел тесты, где лучший ответ работает лучше. (Я был удивлен этим.)
Qtax
23

Вышеупомянутое (?:(?!hede).)*замечательно, потому что это может быть закреплено.

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

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

Но в этом случае будет достаточно:

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

Это упрощение готово к добавлению предложений «И»:

^(?!.*hede)(?=.*foo)(?=.*bar)   # A line with foo and bar, but without hede
^(?!.*hede)(?=.*foo).*bar       # Same
икегами
источник
20

Вот как я это сделаю:

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

Точнее и эффективнее других ответов. Он реализует метод эффективности «развернутой петли» Фридла и требует гораздо меньшего возврата.

ridgerunner
источник
17

Если вы хотите сопоставить символ, чтобы отрицать слово, подобное отрицанию класса символов:

Например, строка:

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

Не используйте:

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

Использование:

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

Обратите внимание, что "(?!bbb)."это не взгляд назад и не взгляд вперед, это выглядит как ток, например:

"(?=abc)abcde", "(?!abc)abcde"
diyism
источник
3
В регулярном выражении Perl нет «lookcurrent». Это действительно негативный взгляд (префикс (?!). Префикс положительного предпросмотра был бы, в (?=то время как соответствующие префиксы предпросмотра были бы (?<!и (?<=соответственно. Взгляд в будущее означает, что вы читаете следующие символы (следовательно, «впереди»), не потребляя их. Взгляд назад означает, что вы проверяете символы, которые уже были использованы.
Дидье Л
14

На мой взгляд, более читаемый вариант верхнего ответа:

^(?!.*hede)

По сути, «совпадать в начале строки тогда и только тогда, когда в ней нет слова« хеде »», поэтому требование почти напрямую переводится в регулярное выражение.

Конечно, возможно наличие нескольких требований отказа:

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

Детали: Якорь ^ гарантирует, что механизм регулярных выражений не повторяет совпадение в каждом месте строки, что соответствует каждой строке.

Якорь ^ в начале предназначен для обозначения начала строки. Инструмент grep сопоставляет каждую строку по одной за раз, в тех случаях, когда вы работаете с многострочной строкой, вы можете использовать флаг "m":

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

или

(?m)^(?!.*hede) # Inline flag
Dannie P
источник
Отличный пример с множественным отрицанием.
Питер Парада
Одно из отличий от ответа на этот вопрос состоит в том, что это ничего не соответствует и соответствует всей строке, если без «хеде»
З. Хулла,
13

ОП не указывал или Tagпост, чтобы указать контекст (язык программирования, редактор, инструмент), в котором будет использоваться Regex.

Для меня, иногда мне нужно сделать это при редактировании файла с помощью Textpad.

Textpad поддерживает некоторые Regex, но не поддерживает lookahead или lookbehind, поэтому требуется несколько шагов.

Если я хочу сохранить все строки, которые НЕ содержат строку hede, я бы сделал это так:

1. Поиск / замена всего файла, чтобы добавить уникальный «тег» в начало каждой строки, содержащей любой текст.

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

2. Удалите все строки, содержащие строку hede(строка замены пуста):

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

3. На этом этапе все оставшиеся строки НЕ содержат строку hede. Удалите уникальный «тег» из всех строк (строка замены пуста):

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

Теперь у вас есть оригинальный текст со всеми строками, содержащими hedeудаленную строку .


Если я хочу сделать что-то еще только для строк, которые НЕ содержат строку hede, я бы сделал это так:

1. Поиск / замена всего файла, чтобы добавить уникальный «тег» в начало каждой строки, содержащей любой текст.

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

2. Для всех строк, которые содержат строку hede, удалите уникальный «Tag»:

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

3. На этом этапе все строки, начинающиеся с уникального «тега», НЕ содержат строку hede. Теперь я могу сделать что- то еще только для этих строк.

4. Когда я закончу, я удаляю уникальный «Tag» из всех строк (строка замены пуста):

    Search string:<@#-unique-#@>
    Replace string:<nothing>  
    Replace-all  
Кевин Феган
источник
12

Поскольку никто другой не дал прямого ответа на заданный вопрос , я сделаю это.

Ответ в том, что с POSIX grepневозможно буквально удовлетворить этот запрос:

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

Причина в том, что POSIX grepтребуется только для работы с базовыми регулярными выражениями , которые просто недостаточно мощны для выполнения этой задачи (они не способны анализировать обычные языки из-за отсутствия чередования и скобок).

Тем не менее, GNU grepреализует расширения, которые позволяют это. В частности, \|оператор Чередование в реализации проекта GNU в Бре, а \(и \)являются круглые скобки. Если ваш механизм регулярных выражений поддерживает чередование, выражения с отрицательными скобками, круглые скобки и звездочку Клини и может привязывать начало и конец строки, это все, что вам нужно для этого подхода. Тем не менее, обратите внимание, что отрицательные множества [^ ... ]очень удобны в дополнение к тем, потому что в противном случае вам нужно заменить их выражением формы, в (a|b|c| ... )котором перечислены все символы, которых нет в наборе, что является чрезвычайно утомительным и чрезмерно длинным, особенно если весь набор символов Unicode.

С GNU grepответом будет что-то вроде:

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

(найдено с Grail и некоторыми дополнительными оптимизациями, сделанными вручную).

Вы также можете использовать инструмент, который реализует расширенные регулярные выражения , например egrep, чтобы избавиться от обратной косой черты:

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

Вот скрипт для его проверки (обратите внимание, что он генерирует файл testinput.txtв текущем каталоге):

#!/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)

В моей системе это печатает:

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

как и ожидалось.

Для тех, кто интересуется деталями, используется метод преобразования регулярного выражения, соответствующего слову, в конечный автомат, затем инвертирование автомата путем изменения каждого состояния принятия в непринятие и наоборот, а затем преобразование полученного FA обратно в регулярное выражение.

Наконец, как все уже заметили, если ваш движок регулярных выражений поддерживает отрицательный прогноз, это значительно упрощает задачу. Например, с помощью GNU grep:

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

Обновление: я недавно нашел превосходную библиотеку FormalTheory Кендалла Хопкинса , написанную на PHP, которая обеспечивает функциональность, аналогичную Grail. Используя его и написанный мной упрощатель, я смог написать онлайн-генератор отрицательных регулярных выражений с учетом входной фразы (в настоящее время поддерживаются только буквенно-цифровые и пробельные символы): http://www.formauri.es/personal/ pgimeno / разное / неигровые-регулярное выражение /

Для hedeэтого выводит:

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

что эквивалентно вышеизложенному.

Педро Химено
источник
11

С момента появления ruby-2.4.1 мы можем использовать новый оператор Absent в регулярных выражениях Ruby.

из официального документа

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

Таким образом, в вашем случае ^(?~hede)$делает работу за вас

2.4.1 :016 > ["hoho", "hihi", "haha", "hede"].select{|s| /^(?~hede)$/.match(s)}
 => ["hoho", "hihi", "haha"]
aelor
источник
9

Через глагол PCRE (*SKIP)(*F)

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

Это полностью пропустит строку, которая содержит точную строку hedeи соответствует всем оставшимся строкам.

DEMO

Исполнение частей:

Давайте рассмотрим приведенное выше регулярное выражение, разбив его на две части.

  1. Часть перед |символом. Часть не должна совпадать .

    ^hede$(*SKIP)(*F)
  2. Часть после |символа. Часть должна соответствовать .

    ^.*$

ЧАСТЬ 1

Движок Regex начнет выполнение с первой части.

^hede$(*SKIP)(*F)

Объяснение:

  • ^ Утверждает, что мы на старте.
  • hede Соответствует строке hede
  • $ Утверждает, что мы находимся в конце строки.

Таким образом, строка, содержащая строку hede, будет сопоставлена. Как только механизм регулярных выражений видит следующий (*SKIP)(*F)( Примечание: вы можете написать (*F)как(*FAIL) ) глагол, он пропускает и делает совпадение неудачным. |Вызывается изменение или логический оператор ИЛИ, добавленный рядом с глаголом PCRE, который соответствует всем границам, существующим между каждым и каждым символом во всех строках, за исключением того, что строка содержит точную строку hede. Смотрите демо здесь . То есть он пытается сопоставить символы из оставшейся строки. Теперь регулярное выражение во второй части будет выполнено.

ЧАСТЬ 2

^.*$

Объяснение:

  • ^ Утверждает, что мы на старте. то есть он соответствует всем началам строки, кроме той, что в hedeстроке. Смотрите демо здесь .
  • .*В многострочном режиме .будет соответствовать любому символу, кроме символов новой строки или возврата каретки. И *будет повторять предыдущий символ ноль или более раз. Так .*будет соответствовать всей линии. Смотрите демо здесь .

    Эй, почему ты добавил. * Вместо. +?

    Потому что .*будет соответствовать пустой строке, но .+не будет соответствовать пустой. Мы хотим сопоставить все строки, за исключением того hede, что возможна также пустая строка на входе. поэтому вы должны использовать .*вместо .+. .+будет повторять предыдущий символ один или несколько раз. Смотрите .*совпадения пустой строкой здесь .

  • $ Привязка конца строки здесь не нужна.

Авинаш Радж
источник
7

Это может быть более приемлемым для двух регулярных выражений в вашем коде, один для первого сопоставления, а затем, если он совпадает, запустите второе регулярное выражение, чтобы проверить наличие выбросов, которые вы хотите заблокировать, например, ^.*(hede).*затем иметь соответствующую логику в своем коде.

Хорошо, я признаю, что это не совсем ответ на опубликованный вопрос, и он также может использовать немного больше обработки, чем одно регулярное выражение. Но для разработчиков, которые пришли сюда в поисках быстрого экстренного решения для случайного случая, это решение не следует упускать из виду.

andrew pate
источник
6

Другой вариант заключается в том, чтобы добавить положительный прогноз и проверить, есть ли heheгде-нибудь в строке ввода, то мы бы отрицали это, используя выражение, подобное:

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

с границами слова.


Выражение объяснено на верхней правой панели regex101.com , если вы хотите изучить / упростить / изменить его, и по этой ссылке вы можете посмотреть, как оно будет соответствовать некоторым образцам входных данных, если хотите.


RegEx Circuit

jex.im визуализирует регулярные выражения:

введите описание изображения здесь

Эмма
источник
5

Язык TXR поддерживает отрицание регулярных выражений.

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

Более сложный пример: сопоставить все строки, которые начинаются с aи заканчиваются z, но не содержат подстроки 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

Отрицание регулярных выражений не особенно полезно само по себе, но когда у вас также есть пересечение, вещи становятся интересными, поскольку у вас есть полный набор операций с булевыми множествами: вы можете выразить «множество, которое соответствует этому, за исключением вещей, которые соответствуют этому».

Kaz
источник
Обратите внимание, что это также решение для регулярного выражения ElasticSearch на основе Lucene.
Виктор Стрибьев
4

Функция ниже поможет вам получить желаемый результат

<?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;
    }


?>
Daniel Nyamasyo
источник
2

^ ((?! hede).) * $ - элегантное решение, за исключением того, что оно использует символы, поэтому вы не сможете комбинировать его с другими критериями. Например, скажем, вы хотели проверить отсутствие «хеде» и наличие «хаха». Это решение будет работать, потому что оно не будет потреблять символы:

^ (?!. \ bhede \ b) (? =. \ bhaha \ b)

cloudhopperpilot
источник
1

Как использовать контрольные глаголы PCRE для соответствия строке, не содержащей слова

Вот метод, который я раньше не видел:

/.*hede(*COMMIT)^|/

Как это работает

Сначала он пытается найти «хеде» где-то в очереди. В случае успеха на этом этапе (*COMMIT)двигатель сообщает не только не возвращаться в случае сбоя, но и не предпринимает попыток дальнейшего сопоставления в этом случае. Затем мы пытаемся сопоставить что-то, что не может совпадать (в этом случае^ ).

Если строка не содержит «hede», тогда вторая альтернатива, пустой подшаблон, успешно соответствует строке темы.

Этот метод не более эффективен, чем негативный взгляд, но я решил, что просто добавлю его здесь на случай, если кто-то найдет его изящным и найдет применение для других, более интересных приложений.

jaytea
источник
0

Более простое решение - использовать оператор not !

Ваше заявление if должно соответствовать «содержит», а не соответствовать «исключает».

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

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

Я считаю, что дизайнеры RegEx ожидали использования не операторов.

оборота user1691651-Джон
источник
0

Возможно, вы найдете это в Google, пытаясь написать регулярное выражение, которое может соответствовать сегментам строки (в отличие от целых строк), которые не содержат подстроки. Уделите мне немного времени, чтобы понять, поэтому я поделюсь:

Учитывая строку: <span class="good">bar</span><span class="bad">foo</span><span class="ugly">baz</span>

Я хочу соответствовать <span> теги, которые не содержат подстроку «плохо».

/<span(?:(?!bad).)*?>будет соответствовать <span class=\"good\">и<span class=\"ugly\"> .

Обратите внимание, что есть два набора (слоя) скобок:

  • Самый внутренний - для негативного взгляда (это не группа захвата)
  • Самая внешняя сторона была интерпретирована Ruby как группа захвата, но мы не хотим, чтобы она была группой захвата, поэтому я добавил?: В начале, и она больше не интерпретируется как группа захвата.

Демо в 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\">"]
BrunoFacca
источник
0

С помощью ConyEdit вы можете использовать командную строку, cc.gl !/hede/чтобы получить строки, которые не содержат совпадения с регулярным выражением, или использовать командную строку cc.dl /hede/для удаления строк, которые содержат сопоставление с регулярным выражением. У них одинаковый результат.

Дональд
источник
0

Я хотел бы добавить еще один пример, если вы пытаетесь сопоставить всю строку, которая содержит строку X , но также не содержит строку Y .

Например, допустим, мы хотим проверить, содержит ли наш URL / строка « вкусные угощения », если он также нигде не содержит « шоколад ».

Этот шаблон регулярного выражения будет работать (работает и в JavaScript)

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

(глобальные, многострочные флаги в примере)

Интерактивный пример: https://regexr.com/53gv4

Матчи

(Эти URL содержат «вкусные угощения», а также не содержат «шоколад»)

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

Не совпадает

(Эти URL-адреса содержат где-то «шоколад» - поэтому они не будут совпадать, даже если они содержат «вкусные угощения»)

  • 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
Мэтью Райдоут
источник