Pytanie Wyrażenie regularne pasujące do wiersza, który nie zawiera słowa?


Wiem, że możliwe jest dopasowanie słowa, a następnie odwrócenie dopasowania za pomocą innych narzędzi (np. grep -v). Chciałbym jednak wiedzieć, czy możliwe jest dopasowanie linii nie rób tego zawierają konkretne słowo (np. hede) za pomocą wyrażenia regularnego.

Wkład:

hoho
hihi
haha
hede

Kod:

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

Pożądane wyjście:

hoho
hihi
haha

3567


pochodzenie


Prawdopodobnie kilka lat później, ale co jest nie tak z: ([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$)))*? Pomysł jest prosty. Kontynuuj dopasowywanie, aż zobaczysz początek niechcianego ciągu, a następnie dopasuj tylko w przypadkach N-1, w których ciąg znaków jest niedokończony (gdzie N jest długością ciągu). Przypadki N-1 to "h, po którym nie ma", "po którym następuje non-d", a "hed, a następnie non-e". Jeśli udało ci się przekazać te przypadki N-1, to z powodzeniem nie zrobił dopasuj niechciany ciąg, aby zacząć szukać [^h]* jeszcze raz - stevendesu
@stevendesu: spróbuj tego dla "bardzo-bardzo-bardzo długiego słowa" lub nawet lepszego pół zdania. Baw się dobrze pisząc. Przy okazji, jest prawie nieczytelne. Nie wiem o wpływie na wydajność. - Peter Schuetze
@PeterSchuetze: Pewnie, że nie jest to dość długie słowa, ale jest to wykonalne i poprawne rozwiązanie. Chociaż nie przeprowadzałem testów wydajności, nie wyobrażałbym sobie, że jest zbyt powolny, ponieważ większość ostatnich reguł jest ignorowana, dopóki nie zobaczysz h (lub pierwszej litery słowa, zdania itp.). I można łatwo wygenerować ciąg regex dla długich ciągów przy użyciu iteracyjnej konkatenacji. Jeśli to działa i może być generowane szybko, czy czytelność jest ważna? Po to są komentarze. - stevendesu
@stevendesu: ja nawet później, ale ta odpowiedź jest prawie całkowicie błędna. po pierwsze, wymaga, aby podmiot zawierał "h", co nie powinno być konieczne, biorąc pod uwagę, że zadaniem jest "dopasować linie, które nie zawierają określonego słowa". załóżmy, że chciałeś, aby grupa wewnętrzna była opcjonalna i że wzór jest zakotwiczony: ^([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$))?)*$to się nie udaje, gdy instancje "hede" są poprzedzone częściowymi instancjami "hede", takimi jak "hhede". - jaytea
To pytanie zostało dodane do Często zadawane pytania dotyczące wyrażeń regularnych stosu, w "Zaawansowane Regex-Fu". - aliteralmind


Odpowiedzi:


Przekonanie, że regex nie obsługuje odwrotnego dopasowywania, nie jest całkowicie prawdziwe. Możesz naśladować to zachowanie, stosując negatywne rozejrzenie:

^((?!hede).)*$

Wyrażenie powyższe dopasuje dowolny ciąg lub linię bez podziału wiersza, nie zawierające ciąg (pod) "hede". Jak wspomniano, nie jest to coś, co regex jest "dobre" w (lub powinno), ale mimo to jest możliwy.

A jeśli potrzebujesz dopasować znaki końca linii, użyj również Modyfikator DOT-ALL (ciąg dalszy s w następującym schemacie):

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

lub użyj go w linii:

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

(gdzie /.../ są ogranicznikami regex, tj. nie są częścią wzorca)

Jeśli modyfikator DOT-ALL nie jest dostępny, możesz naśladować to samo zachowanie w klasie znaków [\s\S]:

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

Wyjaśnienie

Ciąg jest po prostu listą n postacie. Przed i po każdym znaku jest pusty ciąg znaków. A więc listę n postacie będą miały n+1 puste struny. Rozważ ciąg "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

gdzie esą puste struny. Wyrażenie regularne (?!hede). patrzy przed siebie, aby zobaczyć, czy nie ma podciągu "hede" być widocznym, a jeśli tak jest (to widzimy coś innego), to . (kropka) będzie pasować do dowolnego znaku oprócz linii podziału. Rozważane są również tzw zero-szerokości-twierdzeń ponieważ nie konsumować dowolne postacie. Oni tylko potwierdzają / potwierdzają coś.

Tak więc w moim przykładzie każdy pusty ciąg jest najpierw sprawdzany, aby sprawdzić, czy nie ma "hede" do przodu, zanim postać zostanie pochłonięta przez . (kropka). Wyrażenie regularne (?!hede). zrobi to tylko raz, więc jest zawijany w grupę i powtarzany zero lub więcej razy: ((?!hede).)*. Na koniec, początek i koniec wejścia są zakotwiczone, aby upewnić się, że całe wejście zostało zużyte: ^((?!hede).)*$

Jak widać, dane wejściowe "ABhedeCD" zakończy się niepowodzeniem, ponieważ trwa e3, wyrażenie regularne (?!hede) zawiedzie (nie jest  "hede" przed nami!).


4859



Nie posunąłbym się tak daleko, aby powiedzieć, że jest to coś, co jest złe w regex. Wygoda tego rozwiązania jest dość oczywista, a wydajność w porównaniu do wyszukiwania programowego często nie ma znaczenia. - Archimaredes
Ściśle mówiąc, negatywne przesunięcie w przód powoduje, że wyrażenie regularne nie jest regularne. - Peter K
@PeterK, oczywiście, ale to SO, a nie MathOverflow czy CS-Stackexchange. Ludzie zadający tutaj pytanie zazwyczaj szukają praktycznej odpowiedzi. Większość bibliotek lub narzędzi (np grep, o których wspomina OP) z obsługą regex-u, wszystkie mają cechy, które sprawiają, że są nieregularne w sensie teoretycznym. - Bart Kiers
@Bart Kiers, bez obrazy dla ciebie odpowiedź, właśnie to nadużycie terminologii trochę mnie irytuje. Naprawdę mylącą częścią jest to, że wyrażenia regularne w ścisłym tego słowa znaczeniu bardzo mogą robić to, co OP chce, ale wspólny język do ich zapisu nie pozwala na to, co prowadzi do (matematycznie brzydkiego) obejścia jak patrzenie w przyszłość. Proszę zobaczyć ta odpowiedź poniżej i mój komentarz tam (teoretycznie wyrównany) właściwy sposób robienia tego. Nie trzeba dodawać, że działa szybciej na dużych wejściach. - Peter K
Jeśli kiedykolwiek zastanawiałeś się, jak to zrobić w vim: ^\(\(hede\)\@!.\)*$ - baldrs


Zauważ, że rozwiązanie do nie zacząć od "Hede":

^(?!hede).*$

jest na ogół o wiele bardziej wydajne niż rozwiązanie nie zawierać "Hede":

^((?!hede).)*$

Pierwszy sprawdza "hede" tylko na pierwszej pozycji łańcucha wejściowego, a nie na każdej pozycji.


606



Dzięki, użyłem go do sprawdzenia, czy ciąg znaków nie zawiera skwaru cyfr ^ ((?! \ D {5,}).) * - Samih A
^((?!hede).)*$ pracował dla mnie za pomocą wtyczki jQuery DataTable, aby wykluczyć ciąg znaków z zestawu danych - Alex
Witaj! Nie mogę komponować nie koniec z "hede" regex. Czy możesz w tym pomóc? - Aleks Ya
@AleksYa: po prostu użyj wersji "zawiera" i umieść końcową kotwicę w ciągu wyszukiwania: zmień ciąg na "nie pasuje" z "hede" na "hede $" - Nyerguds
@AleksYa: nie można zakończyć wersji przy użyciu negatywnego lookbehind jako: (.*)(?<!hede)$. Wersja @Nyerguds również by działała, ale zupełnie pomija punkt dotyczący wydajności, o którym wspomina odpowiedź. - thisismydesign


Gdyby po prostu używasz go do grep, możesz użyć grep -v hede aby uzyskać wszystkie linie, które nie zawierają hede.

ETA Och, ponownie czytając pytanie, grep -v to prawdopodobnie masz na myśli "opcje narzędzi".


165



Wskazówka: aby stopniowo odfiltrować to, czego nie chcesz: grep -v "hede" | grep -v "hihi" | ...itp. - Olivier Lalonde
Lub używając tylko jednego procesu grep -v -e hede -e hihi -e ... - Olaf Dietsche
Lub tylko grep -v "hede\|hihi" :) - Putnik
Jeśli masz wiele wzorców, które chcesz odfiltrować, umieść je w pliku i użyj grep -vf pattern_file file - codeforester
Lub po prostu egrep lub grep -Ev "hede|hihi|etc" aby uniknąć niezręcznego ucieczki. - Amit Naidu


Odpowiedź:

^((?!hede).)*$

Wyjaśnienie:

^początek struny, ( zgrupuj i przechwytuj do \ 1 (0 lub więcej razy (pasując do jak największej ilości)),
(?! patrz przed siebie, aby zobaczyć, czy nie ma,

hedetwój ciąg,

) koniec patrzenia w przyszłość, . dowolny znak z wyjątkiem \ n,
)* koniec \ 1 (Uwaga: ponieważ używasz kwantyfikatora w tym przechwytywaniu, tylko OSTATNIE powtórzenie przechwyconego wzorca będzie przechowywane w \ 1)
$ przed opcją \ n i końcem ciągu znaków


122



niesamowite, które działało dla mnie w wysublimowanym tekście 2, używając wielu słów "^((?!DSAU_PW8882WEB2|DSAU_PW8884WEB2|DSAU_PW8884WEB).)*$" - Damodar Bashyal
@DamodarBashyal Wiem, że jestem już dość późno, ale możesz całkowicie usunąć drugi termin, a otrzymasz dokładnie takie same wyniki - forresthopkinsa


Podane odpowiedzi są całkowicie w porządku, tylko punkt akademicki:

Wyrażenia regularne w rozumieniu teoretycznych nauk komputerowych NIE SĄ PRZYCZYNĄ zrób to w ten sposób. Dla nich musiało to wyglądać mniej więcej tak:

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

To tylko PEŁNA zgodność. Robienie tego dla sub-meczy byłoby nawet bardziej niezręczne.


90



Ważne jest, aby pamiętać, że wykorzystuje tylko podstawowe wyrażeń regularnych POSIX.2, a tym samym, podczas gdy zwinny jest bardziej przenośny, gdy PCRE nie jest dostępny. - Steve-o
Zgadzam się. Wiele, jeśli nie większość wyrażeń regularnych, nie jest zwykłymi językami i nie może być rozpoznana przez skończone automaty. - ThomasMcLeod
@ThomasMcLeod, Hades32: Czy jest to możliwe w obrębie każdego możliwego języka regularnego, aby móc powiedzieć "nie' i 'i' tak dobrze jak 'lub"Wyrażenia takiego jak"(hede|Hihi)"? (To może być pytanie do CS.) - James Haigh
@JohnAllen: MNIE!!! ... No cóż, nie rzeczywiste wyrażenie, ale akademickie odwołanie, które również ściśle wiąże się z złożonością obliczeniową; PCRE zasadniczo nie może zagwarantować takiej samej wydajności jak wyrażenia regularne POSIX. - James Haigh
Przepraszamy - ta odpowiedź po prostu nie działa, będzie pasować do hhehe, a nawet dopasować hehe częściowo (druga połowa) - Falco


Jeśli chcesz przetestować wyrażenie regularne tylko zawieść, jeśli cały ciąg mecze, następujące będą działać:

^(?!hede$).*

na przykład - Jeśli chcesz zezwolić na wszystkie wartości z wyjątkiem "foo" (tj. "Foofoo", "barfoo" i "foobar" przejdą, ale "foo" nie powiedzie się), użyj: ^(?!foo$).*

Oczywiście, jeśli sprawdzasz dokładny równości, lepszym rozwiązaniem ogólnym w tym przypadku jest sprawdzenie równości łańcuchowej, tj.

myStr !== 'foo'

Możesz nawet położyć negację na zewnątrz test, jeśli potrzebujesz funkcji regex (tutaj, niewrażliwość na wielkość liter i dopasowanie zakresu):

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

Rozwiązanie regexowe na górze może być jednak pomocne w sytuacjach, w których wymagany jest pozytywny test regex (być może przez API).


49



a co z końcowymi białymi znakami? Np. Jeśli chcę, aby test zakończył się niepowodzeniem z ciągiem znaków " hede "? - eagor
@eagor the \s dyrektywa dopasowuje pojedynczy znak odstępu - Roy Tinker
dziękuję, ale nie udało mi się zaktualizować wyrażenia regularnego, aby to działało. - eagor
@eagor: ^(?!\s*hede\s*$).* - Roy Tinker


Oto jest dobre wyjaśnienie dlaczego nie jest łatwo zanegować arbitralne wyrażenie regularne. Muszę się jednak zgodzić z innymi odpowiedziami: jeśli jest to coś innego niż hipotetyczne pytanie, to regex nie jest właściwym wyborem.


48



Niektóre narzędzia, a konkretnie mysqldumpslow, oferują tylko ten sposób filtrowania danych, więc w takim przypadku znalezienie regexu do tego jest najlepszym rozwiązaniem, z wyjątkiem przepisywania narzędzia (różne łaty do tego nie zostały uwzględnione przez MySQL AB / Sun / Oracle. - FGM
Dokładnie analagicznie do mojej sytuacji. Silnik szablonów prędkości używa wyrażeń regularnych, aby zdecydować, kiedy zastosować transformację (escape html) i chcę, aby zawsze działała Z WYJĄTKIEM w jednej sytuacji. - Henno Vermeulen
Jaka jest alternatywa? Nigdy nie spotkałem się z niczym, co mogłoby precyzyjnie dopasować ciąg oprócz regex. Jeśli OP używa języka programowania, mogą być dostępne inne narzędzia, ale jeśli on / ona używa nie pisania kodu, prawdopodobnie nie ma innego wyboru. - kingfrito_5005
Jeden z wielu niehipotentnych scenariuszy, w których regex jest najlepszym dostępnym wyborem: Jestem w IDE (Android Studio), który pokazuje dane wyjściowe dziennika, a jedynymi dostępnymi narzędziami do filtrowania są: zwykłe ciągi i wyrażenie regularne. Próba zrobienia tego przy pomocy zwykłych ciągów znaków byłaby całkowitą porażką. - LarsH