Регулярний вираз, щоб відповідати рядку, який не містить слова


4292

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

Вхід:

hoho
hihi
haha
hede

Код:

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

Бажаний вихід:

hoho
hihi
haha

84
Ймовірно , через пару років пізно, але що сталося з: ([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$)))*? Ідея проста. Продовжуйте відповідати, доки не побачите початок небажаного рядка, тоді збігайтеся лише у випадках N-1, коли рядок не закінчений (де N - довжина рядка). Ці випадки N-1 - це "h, за яким йде не-e", "за ним слідує non-d" і "hed, за яким слідує non-e". Якщо вам вдалося передати ці випадки N-1, ви успішно не відповідали небажаній рядку, тому можете почати шукати [^h]*знову
stevendesu

323
@stevendesu: спробуйте це для "дуже-дуже-довгого слова", а ще краще половини речення. Приємно друкуючи. До речі, це майже не читається. Не знаю про ефективність роботи.
Пітер Шуетце

13
@PeterSchuetze: Впевнений, що це не дуже дуже довгі слова, але це життєздатне і правильне рішення. Хоча я не проводив тести на продуктивність, я б не уявляв, що це занадто повільно, оскільки більшість останніх правил ігноруються, поки ви не побачите h (або першу букву слова, пропозицію тощо). І ви могли легко створити рядок регулярних виразів для довгих рядків, використовуючи ітераційне конкатенацію. Якщо це працює і може бути сформовано швидко, чи важлива розбірливість? Ось до чого йдуть коментарі.
stevendesu

57
@stevendesu: Я навіть пізніше, але ця відповідь майже повністю помилкова. з одного боку, він вимагає, щоб тема містила "h", чого вона не повинна мати, враховуючи завдання "відповідні рядки, які [do] не містять конкретного слова". припустимо, ви хотіли зробити внутрішню групу необов'язковою і що модель є прив’язаною: ^([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$))?)*$ це не вдається, коли екземплярам "hede" передують часткові екземпляри "hede", наприклад, у "hhede".
jaytea

8
Це запитання було додано до поширених запитань щодо регулярного вираження стека в розділі "Розширений Regex-Fu".
aliteralmind

Відповіді:


5891

Поняття про те, що регулярний вираз не підтримує зворотну відповідність, не зовсім правдивий. Ви можете імітувати таку поведінку, використовуючи негативні погляди:

^((?!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's - порожні рядки. Режекс (?!hede).дивиться вперед, щоб побачити, чи немає підстроки, "hede"яку слід бачити, і якщо це так (так щось інше видно), то .(точка) буде відповідати будь-якому символу, крім розриву рядка. Погляди навколо також називаються твердженнями нульової ширини, оскільки вони не використовують жодних символів. Вони лише щось стверджують / підтверджують.

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

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


26
Я б не пішов так далеко, щоб сказати, що це щось із регексом. Зручність цього рішення є досить очевидною, і показник ефективності порівняно з програмним пошуком часто стає неважливим.
Архімаред

29
Строго кажучи, негативний погляд вперед робить вас регулярним вираженням не регулярним.
Петро К

55
@ ПетерК, звичайно, але це ТАК, а не MathOverflow або CS-Stackexchange. Люди, які задають питання тут, як правило, шукають практичної відповіді. Більшість бібліотек чи інструментів (наприклад grep, про які згадує ОП) із підтримкою регулярних виразів мають функції, які роблять їх нерегулярними в теоретичному розумінні.
Барт Кіерс

19
@Bart Kiers, не ображаючи на тебе відповіді, просто це зловживання термінологією мене трохи дратує. Дійсно заплутаність тут полягає в тому, що регулярні вирази в суворому розумінні можуть дуже сильно робити те, що хоче ОП, але загальна мова їх писати не дозволяє, що призводить до (математично некрасивих) обхідних шляхів, як огляди. Будь ласка, дивіться цю відповідь нижче і мій коментар щодо (теоретично вирівняного) правильного способу її виконання. Зайве говорити, що він працює швидше на великих входах.
Петро К

17
Якщо ви коли-небудь замислювалися, як це зробити в vim:^\(\(hede\)\@!.\)*$
лисини

738

Зауважте, що рішення не починається з "hede" :

^(?!hede).*$

як правило, набагато ефективніше, ніж рішення не містить "hede" :

^((?!hede).)*$

Перший перевіряє "hede" лише в першій позиції вхідного рядка, а не в кожній позиції.


5
Дякую, я використав це для перевірки того, що рядок dos не містить ряд цифр ^ ((?! \ D {5,}).) *
Саміх,

2
Привіт! Я не можу скласти , не закінчується «hede» регулярним виразом. Ви можете допомогти в цьому?
Алекс Я.

1
@AleksYa: просто скористайтеся версією "содержать" і включіть кінцевий якір у рядок пошуку: змініть рядок на "не збігається" з "hede" на "hede $"
Nyerguds

2
@AleksYa: не закінчується версія може бути зроблено з допомогою негативного перегляду назад , як: (.*)(?<!hede)$. Версія @Nyerguds також буде добре працювати, але повністю пропускає пункт щодо виконання, який згадується у відповіді.
thisismydesign

5
Чому так багато відповідей ^((?!hede).)*$? Це не більш ефективно використовувати ^(?!.*hede).*$? Це робиться те саме, але меншими кроками
JackPRead

208

Якщо ви просто використовуєте його для grep, ви можете використовувати grep -v hedeдля отримання всіх рядків, які не містять hede.

ETA О, перечитуючи питання, grep -vмабуть, це ви мали на увазі під "параметрами інструментів".


22
Порада: для поступової фільтрації того, що ви не хочете: grep -v "hede" | grep -v "hihi" | ... тощо.
Олів'є Лалонде

51
Або використовуючи лише один процесgrep -v -e hede -e hihi -e ...
Олаф Дієш

15
Або просто grep -v "hede\|hihi":)
Putnik

2
Якщо у вас є багато шаблонів, які ви хочете відфільтрувати, покладіть їх у файл та використовуйтеgrep -vf pattern_file file
codeforester

4
Або просто, egrepабо grep -Ev "hede|hihi|etc"щоб уникнути незручного втечі.
Аміт Найду

160

Відповідь:

^((?!hede).)*$

Пояснення:

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

hede ваша струна,

)кінець перегляду вперед, .будь-який символ, за винятком \ n,
)*кінець \ 1 (Примітка: оскільки ви використовуєте кількісний показник для цього захоплення, перед останньою
$\ n, зберігається лише ОСТАННЕ повторення захопленого шаблону \ 1) і кінець рядка


14
приємно, що працювало для мене в піднесеному тексті 2, використовуючи кілька слів ' ^((?!DSAU_PW8882WEB2|DSAU_PW8884WEB2|DSAU_PW8884WEB).)*$'
Дамодар Баш'ял,

3
@DamodarBashyal Я знаю, що я тут досить пізно, але ви можете повністю зняти другий термін там, і ви отримаєте такі самі результати
forresthopkinsa

99

Дані відповіді ідеально чудові, просто академічний момент:

Регулярні вирази у значенні теоретичних комп’ютерних наук НЕ МОЖУТЬ робити це так. Для них це мало виглядати приблизно так:

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

Це відповідає лише ПОВНОМУ збігу. Робити це для під-матчів було б навіть незручніше.


1
Важливо зазначити, що для цього використовуються лише базові регулярні вирази POSIX.2, і, тим часом, терсе є більш портативним для, коли PCRE недоступний.
Стів-о

5
Я згоден. Багато, якщо не більшість регулярних виразів, не є звичайними мовами, і їх не можна було розпізнати за допомогою кінцевих автомати.
ThomasMcLeod

@ThomasMcLeod, Hades32: Чи в межах будь-якої можливої ​​регулярної мови можна сказати " не " і " і ", а також " або " такого виразу, як " (hede|Hihi)"? (Це, можливо, питання для CS.)
Джеймс Хей

7
@JohnAllen: МЕН !!! … Ну, не власне регекс, а академічна довідка, яка також тісно пов'язана з обчислювальною складністю; PCRE принципово не можуть гарантувати таку ж ефективність, як регулярні вирази POSIX.
Джеймс Хей

4
Вибачте, ця відповідь просто не працює, вона відповідатиме hhehe і навіть частково відповідатиме hehe (другий тайм)
Falco

60

Якщо ви хочете, щоб тест регулярний вираз для тільки помилкові , якщо вся рядок матчів, такі будуть працювати:

^(?!hede$).*

наприклад - Якщо ви хочете дозволити всі значення, крім "foo" (тобто "foofoo", "barfoo" і "foobar" пройдуть, але "foo" не вдасться), використовуйте: ^(?!foo$).*

Звичайно, якщо ви перевіряєте на точну рівність, кращим загальним рішенням у цьому випадку є перевірка рівності рядків, тобто

myStr !== 'foo'

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

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

Рішення регулярного виразів у верхній частині цієї відповіді може бути корисним, але в ситуаціях, коли потрібен позитивний тест на регулярний вираз (можливо, API).


а що з пробілами пробілів? Наприклад, якщо я хочу, щоб тест не вийшов зі строкою " hede "?
егор

@eagor \sдиректива відповідає одному символу пробілу
Рой Тінкер

дякую, але мені не вдалося оновити регулярний вираз, щоб зробити цю роботу.
егор

2
@eagor:^(?!\s*hede\s*$).*
Рой Тінкер

52

FWIW, оскільки регулярні мови (також раціональні мови) закриваються при доповненні, завжди можна знайти регулярний вираз (він же раціональний вираз), який заперечує інший вираз. Але не багато інструментів реалізують це.

Vcsn підтримує цей оператор (який він позначає {c}, постфікс).

Ви спочатку визначити тип ваших виразів: етикетки лист ( 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

Є більше інструментів, які це дозволяють, одним з найбільш вражаючих є Рагель . Там це буде записано як (будь-яке * - ('hehe' будь-яке *)) для початку матчу, що вирівнюється, або (будь-яке * - ('hehe' будь-яке *)) для незрівняного.
Петро К

1
@reinierpost: чому це некрасиво і в чому проблема з Unicode? Я не можу погодитись обох. (У мене немає досвіду роботи з vcsn, але я маю DFA).
Петро К

3
@PedroGimeno Коли ви закріпилися, ви обов'язково поклали цей регулярний вираз у парен? Інакше пріоритети між якорями |не будуть грати добре. '^(()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*)$'.
акім

1
Я думаю, що варто зауважити, що цей метод призначений для узгодження рядків, які не є словом «hede», а не рядків, ніж не містять слова «hede», про що вимагала ОП. Дивіться мою відповідь на останнє.
Педро Гімено

51

Ось хороше пояснення того, чому не легко заперечувати довільну формулу. Я маю згоду з іншими відповідями: якщо це щось інше, ніж гіпотетичне питання, то регулярний вираз тут не є правильним вибором.


10
Деякі інструменти, а саме mysqldumpslow, пропонують лише цей спосіб фільтрації даних, тому в такому випадку найкраще рішення, крім переписування інструменту, є пошук регулярного вираження (різні патчі для цього MySQL AB / Sun не включені / Oracle.
FGM

1
Точно аналогічно моїй ситуації. Двигун шаблону швидкості використовує регулярні вирази, щоб вирішити, коли застосувати перетворення (html-повідомлення), і я хочу, щоб він завжди працював ВЖЕ В одній ситуації.
Henno Vermeulen

1
Яка альтернатива існує? Я ніколи не стикався з тим, що могло б зробити точне узгодження рядків, крім регулярного виразів. Якщо ОП використовує мову програмування, можуть бути інші інструменти, але якщо він / вона не пише код, можливо, іншого вибору немає.
kingfrito_5005

2
Один із багатьох негіпотетичних сценаріїв, коли кращий регулярний вираз є найкращим вибором: я перебуваю в IDE (Android Studio), який показує вихід журналу, і єдиними наданими інструментами фільтрації є: звичайні рядки та регулярний вираз. Намагатися зробити це простими струнами було б цілком невдало.
LarsH

48

При негативному пошуку, регулярне вираження може відповідати чомусь, що не містить конкретного шаблону. На це відповідає і пояснює Барт Кірс. Чудове пояснення!

Однак, з відповіддю Барта Кірса, частина пошуку буде перевіряти від 1 до 4 символів, порівнюючи будь-який один символ. Ми можемо цього уникнути, і дозволити частині пошуку головою перевірити весь текст, переконатися, що немає «hede», і тоді нормальна частина (. *) Може з'їсти весь текст за один раз.

Ось покращений регулярний вираз:

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

Зауважте, що (*?) Лінивий квантор у частині негативної підказки необов’язковий, ви можете використовувати натомість (*) жадібний квантор, залежно від ваших даних: якщо "hede" присутній і на початку половини тексту, лінивий кількісний показник може бути швидшим; інакше жадібний квантор буде швидшим. Однак якщо "hede" немає, обидва будуть однаково повільними.

Ось демонстраційний код .

Для отримання додаткової інформації про lookahead, будь ласка, ознайомтеся із чудовою статтею: Mastering Lookahead and 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()
);

3
тож просто перевірити, чи вказаний рядок не містить str1 та str2:^(?!.*(str1|str2)).*$
S.Serpooshan

1
Так, або ви можете використовувати лінивий квантор: ^(?!.*?(?:str1|str2)).*$залежно від ваших даних. Додано, ?:оскільки нам не потрібно його захоплювати.
amobiz

Це, безумовно, найкраща відповідь коефіцієнтом 10xms. Якщо ви додали код jsfiddle та результати на відповідь, люди його можуть помітити. Цікаво, чому лінива версія швидша за жадібну версію, коли немає хеди. Чи не повинні вони забирати однакову кількість часу?
користувач5389726598465

Так, вони займають однакову кількість часу, оскільки вони обидва тестують весь текст.
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 не підтримує дії Verbs (* FAIL тощо), я не зміг перевірити рішення P1 та P2.

Підсумок:

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

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


5
Ви також маєте час ^(?!.*hede). /// Також, мабуть, краще класифікувати вирази для відповідного корпусу та невідповідного корпусу окремо, оскільки зазвичай це так, що більшість рядків чи більшість рядків не відповідають.
ikegami

32

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

напр. шукати конфігураційний файл 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

Логіка serial grep's - це (не коментар) та (відповідає dir)


2
Я думаю, що він просить версію для регулярного grep -v
виразів

9
Це небезпечно. Також пропускає рядки на кшталтgood_stuff #comment_stuff
Хаві Монтеро

29

завдяки цьому ви уникаєте тестування пошуку на кожній позиції:

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

еквівалент (для .net):

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

Стара відповідь:

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

7
Гарна думка; Я здивований, що ніхто раніше не згадував про такий підхід. Однак цей конкретний регулярний вираз схильний до катастрофічного зворотного відстеження при застосуванні до тексту, який не відповідає. Ось як я це зробив би:/^[^h]*(?:h+(?!ede)[^h]*)*$/
Алан Мур

... або ви можете просто зробити всі кількісні показники наділеними. ;)
Алан Мур

@Alan Moore - я теж здивований. Я побачив тут ваш коментар (і найкраще регулярний вираз у купі) лише після публікації цього самого шаблону у відповіді нижче.
егергернер

@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]*)*$

Точні та ефективніші, ніж інші відповіді. Він реалізує методику ефективності Фрідля "розкручування циклу" і вимагає набагато менше зворотних треків.


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"

3
У perge regexp не існує "вигляду потоку". Це справді негативний підхід (префікс (?!). Позитивний префікс lookahead був би, (?=тоді як відповідні вигляд за префіксами буде (?<!і (?<=відповідно. Підказка означає, що ви читаєте наступні символи (звідси «вперед»), не вживаючи їх. Позаду означає, що ви перевіряєте вже використані символи.
Дідьє Л

14

На мій погляд, більш читаний варіант верхньої відповіді:

^(?!.*hede)

В основному, "співставити на початку рядка, якщо і лише тоді, коли в ньому немає" hede "- тому вимога перекладається майже безпосередньо в регулярний вираз.

Звичайно, можливі кілька вимог відмови:

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

Деталі: ^ якор гарантує, що двигун регулярних виразів не повторює збіг у кожному місці рядка, який би відповідав кожній рядку.

^ Якор на початку означає, що представляє початок рядка. Інструмент grep відповідає кожному рядку по одному, у контекстах, де ви працюєте з багаторядковим рядком, ви можете використовувати прапор "m":

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

або

(?m)^(?!.*hede) # Inline flag

Відмінний приклад з багаторазовим запереченням.
Петро Парада

Одна відмінність від головної відповіді полягає в тому, що це нічого не відповідає, і це відповідає всій лінії, якщо без "hede"
З. Хулла

13

В ОП не вказано або Tagпосада, щоб вказати контекст (мова програмування, редактор, інструмент), в якій буде використовуватися Regex.

Для мене мені іноді потрібно це робити під час редагування файлу за допомогою Textpad.

Textpad підтримує деякий Regex, але не підтримує пошук або відстань, тому потрібно зробити кілька кроків.

Якщо я хочу зберегти всі рядки, які НЕ містять рядок 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, видаліть унікальний "Тег":

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

3. У цей момент усі рядки, що починаються з унікальної "Тегу", НЕ містять рядка hede. Зараз я можу зробити своє щось інше лише для цих рядків.

4. Коли я закінчую, я видаляю унікальний "Тег" з усіх рядків (рядок заміни порожній):

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

12

Так як ніхто не дав прямої відповіді на питання , яке було задане , я зроблю це.

Відповідь полягає в тому, що з POSIX grepнеможливо буквально задовольнити цей запит:

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

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

Однак GNU grepреалізує розширення, які це дозволяють. Зокрема, \|це оператор чергування у впровадженні BRE з GNU \(та \)є дужкою. Якщо ваш механізм регулярних виразів підтримує чергування, негативні вирази дужок, круглі дужки та зірку Клейна і здатний прив’язуватися до початку та кінця рядка, це все, що вам потрібно для цього підходу. Однак зауважте, що негативні набори [^ ... ]є дуже зручними на додаток до цих, тому що в іншому випадку вам потрібно замінити їх виразом форми, (a|b|c| ... )яка перераховує кожного символу, який не знаходиться в наборі, що є надзвичайно стомлюючим і занадто довгим, тим більше, якщо весь набір символів - Unicode.

Що grepстосується GNU , відповідь буде приблизно такою:

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

(знайдено з Грааль та деякі подальші оптимізації, зроблені вручну).

Ви також можете скористатися інструментом, який реалізує розширені регулярні вирази , як 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 Kendall Hopkins , написану на 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, ми можемо використовувати нового Відсутнього оператора в регулярних виразах 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"]

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) ), воно пропускає і робить збіг невдалим. |називається alteration або логічний оператор OR, доданий поруч з дієсловом PCRE, який inturn відповідає всім межам між кожним символом у всіх рядках, за винятком того, що рядок містить точний рядок hede. Дивіться демонстрацію тут . Тобто вона намагається відповідати символам з решти, що залишилася. Тепер би виконувався регулярний вираз у другій частині.

ЧАСТИНА 2

^.*$

Пояснення:

  • ^ Стверджує, що ми на старті. тобто він відповідає всім рядкам, що починаються, крім того, що знаходиться в hedeрядку. Дивіться демонстрацію тут .
  • .*У режимі " .Мультилінія" відповідатиме будь-яким символам, окрім символів повернення нової лінії чи перевезення І *повторив би попередній символ нуль чи більше разів. Так .*би відповідала вся лінія. Дивіться демонстрацію тут .

    Гей, чому ви додали. * Замість +?

    Тому .*що відповідатиме порожнім рядком, але .+не відповідає порожньому. Ми хочемо співставити всі рядки, за винятком випадків hede, якщо вхід може бути порожнім рядком. тому ви повинні використовувати .*замість цього .+. .+повторив би попередній символ один чи кілька разів. Див .*відповідає порожній рядку тут .

  • $ Кінець рядкового якоря тут не потрібен.


7

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

Гаразд, я визнаю, що насправді це не відповідь на розміщене запитання, яке також може використовуватись трохи більше, ніж один регулярний вираз. Але для розробників, які приїхали сюди, шукаючи швидкого виправлення аварійних ситуацій для випадку, що випадає, тоді це рішення не слід нехтувати.


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

Заперечення Regex само по собі не є особливо корисним, але коли ви також маєте перехрестя, речі стають цікавими, оскільки у вас є повний набір операцій булевого набору: ви можете висловити "набір, який відповідає цьому, за винятком речей, які відповідають цьому".


Зауважте, що це також рішення для регексу на основі ElasticSearch Lucene.
Wiktor Stribiżew

5

Ще один варіант полягає в тому, щоб додати позитивний погляд вперед і перевірити, чи heheє де-небудь у рядку введення, ми б це заперечували, виразом, подібним до:

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

зі межами слова.


Вираз пояснюється на верхній правій панелі regex101.com , якщо ви хочете вивчити / спростити / модифікувати його, і за цим посиланням ви можете спостерігати, як воно буде відповідати деяким зразкам даних, якщо вам це подобається.


RegEx Circuit

jex.im візуалізує регулярні вирази:

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


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


?>

2

^ ((?! hede).) * $ - це елегантне рішення, за винятком того, що воно споживає символів, ви не зможете поєднати його з іншими критеріями. Наприклад, скажіть, що ви хотіли перевірити відсутність "hede" та наявність "haha". Це рішення буде працювати, оскільки воно не буде використовувати символи:

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


1

Як використовувати контрольні дієслова зворотного відстеження PCRE, щоб відповідати рядку, що не містить слова

Ось метод, який я раніше не бачив:

/.*hede(*COMMIT)^|/

Як це працює

По-перше, він намагається знайти "hede" десь у рядку. У разі успіху в цей момент (*COMMIT)підкаже двигуну не тільки не відмовлятися в разі виходу з ладу, але й не намагатися в цьому випадку будь-якого подальшого узгодження. Потім ми намагаємось зіставити щось, що не може відповідати (в даному випадку ^).

Якщо рядок не містить "hede", то друга альтернатива, порожній піддрук, успішно відповідає темі рядка.

Цей метод не є більш ефективним, ніж негативний підхід, але я подумав, що я просто накину його на випадок, якщо хтось виявить його витонченим і знайде застосування для інших, більш цікавих застосувань.


0

Більш просте рішення - використовувати не оператор !

Ваше повідомлення, якщо оператор повинен відповідати "містить", а не відповідати "виключає".

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

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

Я вважаю, що дизайнери RegEx передбачили використання не операторів.


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 як група захоплення, але ми не хочемо, щоб це була група захоплення, тому я додав ?: На початку це вже не інтерпретується як група захоплення.

Демо в Рубі:

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\">"]

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.