Wyrażenie regularne pasujące do linii, która nie zawiera słowa


4292

Wiem, że można dopasować słowo, a następnie odwrócić dopasowania za pomocą innych narzędzi (np grep -v.). Czy można jednak dopasować wiersze, które nie zawierają określonego słowa, np. hedeUżywając wyrażenia regularnego?

Wejście:

hoho
hihi
haha
hede

Kod:

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

Pożądane wyjście:

hoho
hihi
haha

84
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 łańcuch jest niedokończony (gdzie N jest długością łańcucha). Te przypadki N-1 to „h, po którym następuje nie-e”, „on następuje po nie-d”, i „hed, po którym następuje nie-e”. Jeśli udało Ci się zaliczyć te przypadki N-1, nie udało Ci się dopasować niechcianego ciągu, więc możesz zacząć szukać [^h]*ponownie
stevendesu,

323
@stevendesu: wypróbuj to dla „bardzo-bardzo-długiego słowa” lub jeszcze lepiej pół zdania. Miłego pisania. BTW, jest prawie nieczytelny. Nie wiem o wpływie na wydajność.
Peter Schuetze,

13
@PeterSchuetze: Jasne, że nie jest ładna w przypadku bardzo długich słów, ale jest to realne i poprawne rozwiązanie. Chociaż nie przeprowadziłem testów wydajności, nie wyobrażam sobie, aby działała zbyt wolno, ponieważ większość tych ostatnich reguł jest ignorowana, dopóki nie zobaczysz h (lub pierwszej litery słowa, zdania itp.). I możesz łatwo wygenerować ciąg wyrażenia regularnego dla długich ciągów, używając iteracyjnej konkatenacji. Jeśli działa i można ją szybko wygenerować, czy czytelność jest ważna? Do tego służą komentarze.
stevendesu

57
@stevendesu: Jestem nawet później, ale ta odpowiedź jest prawie całkowicie błędna. z jednej strony wymaga, aby podmiot zawierał „h”, czego nie powinien, biorąc pod uwagę, że zadaniem jest „dopasowanie wierszy, które [nie] zawierają określonego słowa”. załóżmy, że masz zamiar uczynić grupę wewnętrzną opcjonalną i że wzór jest zakotwiczony: ^([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$))?)*$ nie udaje się to, gdy wystąpienia „hede” są poprzedzone częściowymi wystąpieniami „hede”, na przykład w „hhede”.
jaytea

8
To pytanie zostało dodane do często zadawanych pytań dotyczących wyrażeń regularnych przepełnienia stosu , w części „Zaawansowane Regex-Fu”.
aliteralmind

Odpowiedzi:


5891

Pojęcie, że wyrażenie regularne nie obsługuje odwrotnego dopasowywania, nie jest do końca prawdziwe. Możesz naśladować to zachowanie, stosując negatywne zmiany:

^((?!hede).)*$

Wyrażenie regularne powyżej będzie pasować do dowolnego łańcucha lub linii bez podziału linii, nie zawierających (pod) łańcucha „hede”. Jak wspomniano, wyrażenie regularne nie jest „dobre” w (lub powinno być), ale nadal jest możliwe.

A jeśli chcesz również dopasować znaki podziału linii, użyj modyfikatora DOT-ALL ( snastępujący wzór:

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

lub użyj go w linii:

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

(gdzie /.../są ogranicznikami wyrażeń regularnych, tj. nie są częścią wzorca)

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

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

Wyjaśnienie

Łańcuch to tylko lista nznaków. Przed i po każdym znaku jest pusty ciąg. Zatem lista nznaków będzie miała n+1puste ciągi. 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 ciągi. Wyrażenie regularne (?!hede).patrzy w przyszłość, aby zobaczyć, czy nie ma podłańcucha "hede", a jeśli tak jest (więc widać coś innego), wówczas .(kropka) będzie pasować do dowolnego znaku z wyjątkiem podziału linii. Rozejrzenia są również nazywane asercjami o zerowej szerokości, ponieważ nie zużywają żadnych znaków. Twierdzą tylko / potwierdzają coś.

Tak więc, w moim przykładzie, każdy pusty łańcuch jest najpierw sprawdzany, czy nie ma "hede"przed nim znaku, zanim znak zostanie zużyty przez .kropkę. Regex (?!hede).będzie zrobić tylko raz, więc jest owinięty w grupie, a powtórzony zero lub więcej razy: ((?!hede).)*. Wreszcie, początek i koniec wejścia są zakotwiczone, aby mieć pewność, że całe wejście zostanie wykorzystane:^((?!hede).)*$

Jak widać, wejście "ABhedeCD"nie powiedzie się, ponieważ on e3, regex (?!hede)zawiedzie ( jest "hede" przed nami!).


26
Nie posunąłbym się nawet do stwierdzenia, że ​​jest to coś, w czym wyrażenie regularne jest złe. Wygoda tego rozwiązania jest dość oczywista, a wydajność w porównaniu do wyszukiwania programowego często będzie nieistotna.
Archimaredes,

29
Ściśle mówiąc, negatywna reakcja powoduje, że wyrażenie regularne jest nieregularne.
Peter K,

55
@PeterK, jasne, ale to jest SO, nie MathOverflow lub CS-Stackexchange. Ludzie zadający pytanie tutaj zazwyczaj szukają praktycznej odpowiedzi. Większość bibliotek lub narzędzi (takich grep, o których wspomina OP) z obsługą wyrażeń regularnych, wszystkie mają funkcje, które sprawiają, że są nieregularne w sensie teoretycznym.
Bart Kiers,

19
@Bart Kiers, bez urazy do odpowiedzi, tylko to nadużycie terminologii trochę mnie denerwuje. Naprawdę mylące jest tutaj to, że wyrażenia regularne w ścisłym tego słowa znaczeniu mogą bardzo robić to, czego chce OP, ale wspólny język do ich pisania nie pozwala na to, co prowadzi do (matematycznie brzydkich) obejść takich jak przewidywanie przyszłości. Proszę zobaczyć tę odpowiedź poniżej i mój komentarz tam (teoretycznie wyrównany) właściwy sposób zrobienia tego. Nie trzeba dodawać, że działa szybciej na dużych nakładach.
Peter K,

17
Jeśli kiedykolwiek zastanawiałeś się, jak to zrobić w vimie:^\(\(hede\)\@!.\)*$
baldrs

738

Zauważ, że rozwiązanie nie zaczyna się od „hede” :

^(?!hede).*$

jest ogólnie znacznie wydajniejszy niż rozwiązanie, które nie zawiera „hede” :

^((?!hede).)*$

Pierwszy sprawdza „hede” tylko na pierwszej pozycji ciągu wejściowego, a nie na każdej pozycji.


5
Dzięki, użyłem go do sprawdzenia, czy łańcuch nie zawiera kwantyfikacji cyfr ^ ((?! \ D {5,}).) *
Samih A 10'15

2
Dzień dobry! Nie mogę komponować nie kończy się na regex „hede” . Czy możesz w tym pomóc?
Aleks Ya

1
@AleksYa: po prostu użyj wersji „zawiera” i dołącz końcową kotwicę do ciągu wyszukiwania: zmień ciąg na „nie pasuje” z „hede” na „hede $”
Nyerguds

2
@AleksYa: wersja nie kończy można zrobić za pomocą negatywnego lookbehind jak: (.*)(?<!hede)$. Wersja @Nyerguds również działałaby, ale całkowicie pomija punkt wydajności, o którym mowa w odpowiedzi.
thisismydesign

5
Dlaczego mówi tak wiele odpowiedzi ^((?!hede).)*$? Czy nie jest bardziej wydajny w użyciu ^(?!.*hede).*$? Robi to samo, ale w mniejszej liczbie kroków
JackPRead,

208

Jeśli używasz go tylko do grep, możesz użyć, grep -v hedeaby uzyskać wszystkie wiersze, które nie zawierają hede.

ETA. Ponowne przeczytanie pytania grep -vjest prawdopodobnie tym, co rozumiesz przez „opcje narzędzi”.


22
Wskazówka: do stopniowego odfiltrowywania tego, czego nie chcesz: grep -v "hede" | grep -v "hihi" | ...itp.
Olivier Lalonde

51
Lub przy użyciu tylko jednego procesugrep -v -e hede -e hihi -e ...
Olaf Dietsche

15
Lub po prostu grep -v "hede\|hihi":)
Putnik,

2
Jeśli masz wiele wzorców, które chcesz odfiltrować, umieść je w pliku i użyjgrep -vf pattern_file file
codeforester

4
Albo po prostu egrepalbo grep -Ev "hede|hihi|etc"uniknąć niezręcznej ucieczkę.
Amit Naidu

160

Odpowiedź:

^((?!hede).)*$

Wyjaśnienie:

^początek ciągu, (grupowanie i przechwytywanie do \ 1 (0 lub więcej razy (dopasowanie największej możliwej ilości)),
(?!spójrz w przyszłość, aby zobaczyć, czy nie ma,

hede twój sznurek,

)koniec patrzenia w przód, .dowolny znak oprócz \ n,
)*koniec \ 1 (Uwaga: ponieważ używasz kwantyfikatora w tym przechwytywaniu, tylko OSTATNIE powtórzenie przechwyconego wzoru zostanie zapisane w \ 1)
$przed opcjonalnym \ n, i koniec łańcucha


14
niesamowite, które działało dla mnie w wysublimowanym tekście 2 przy użyciu wielu słów ' ^((?!DSAU_PW8882WEB2|DSAU_PW8884WEB2|DSAU_PW8884WEB).)*$'
Damodar Bashyal

3
@DamodarBashyal Wiem, że się tu spóźniłem, ale można całkowicie usunąć drugi termin i uzyskać dokładnie takie same wyniki
forresthopkinsa

99

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

Wyrażenia regularne w znaczeniu teoretycznych informatyki NIE MOGĄ tego robić 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 pasuje PEŁNY. Robienie tego w meczach podrzędnych byłoby jeszcze bardziej niezręczne.


1
Ważne, aby pamiętać, że używa tylko podstawowych wyrażeń regularnych POSIX.2, a zatem podczas gdy terse jest bardziej przenośny, gdy PCRE nie jest dostępny.
Steve-o

5
Zgadzam się. Wiele wyrażeń regularnych, jeśli nie większość, nie jest językami regularnymi i nie może ich rozpoznać automat skończony.
ThomasMcLeod

@ThomasMcLeod, Hades32: Czy w granicach jakiegokolwiek możliwego regularnego języka jest możliwość powiedzenia „ nie ” i „ oraz ”, a także „ lub ” wyrażenia takiego jak „ (hede|Hihi)”? (To może pytanie do CS.)
James Haigh

7
@JohnAllen: ME !!! … Cóż, nie rzeczywiste wyrażenie regularne, ale odniesienie akademickie, które również ściśle wiąże się ze złożonością obliczeniową; PCRE zasadniczo nie może zagwarantować takiej samej wydajności jak wyrażenia regularne POSIX.
James Haigh,

4
Niestety - ta odpowiedź po prostu nie działa, będzie pasować do niego, a nawet częściowo do hehe (druga połowa)
Falco,

60

Jeśli chcesz, aby test wyrażenia regularnego zakończył się niepowodzeniem tylko wtedy, gdy cały ciąg znaków jest zgodny, następujące działania będą działać:

^(?!hede$).*

np. Jeśli chcesz zezwolić na wszystkie wartości oprócz „foo” (tzn. „foofoo”, „barfoo” i „foobar” przejdą, ale „foo” nie powiedzie się), użyj: ^(?!foo$).*

Oczywiście, jeśli sprawdzasz dokładność równości, lepszym ogólnym rozwiązaniem w tym przypadku jest sprawdzenie równości ciągów, tj

myStr !== 'foo'

Możesz nawet umieścić negację poza testem, jeśli potrzebujesz funkcji wyrażenia regularnego (w tym przypadku rozróżnianie wielkości liter i dopasowanie zakresu):

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

Rozwiązanie wyrażenia regularnego u góry tej odpowiedzi może być jednak pomocne w sytuacjach, w których wymagany jest pozytywny test wyrażenia regularnego (być może przez API).


co z końcowymi spacjami? Na przykład, jeśli chcę, aby test zakończył się niepowodzeniem z łańcuchem " hede "?
eagor

@eagor \sdyrektywa pasuje do jednej spacji
Roy Tinker

dzięki, ale nie udało mi się zaktualizować wyrażenia regularnego, aby to zadziałało.
eagor

2
@eagor:^(?!\s*hede\s*$).*
Roy Tinker

52

FWIW, ponieważ języki regularne (zwane również językami wymiernymi) są zamknięte z dopełnianiem, zawsze można znaleźć wyrażenie regularne (zwane także wyrażeniem wymiernym), które neguje inne wyrażenie. Ale niewiele narzędzi to implementuje.

Vcsn obsługuje ten operator (co oznacza {c}postfiks).

Najpierw zdefiniować typ wyrażeń: Etykiety są list ( lal_char), aby wybrać z a, aby zna przykład (definiowanie alfabetu podczas pracy z uzupełnianie jest oczywiście bardzo ważne), a „wartość” obliczana dla każdego słowa jest po prostu logiczna : truesłowo jest akceptowane false, odrzucone.

W Pythonie:

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

następnie wpisz wyrażenie:

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

przekonwertuj to wyrażenie na automat:

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

Odpowiedni automat

na koniec przekonwertuj ten automat z powrotem na proste wyrażenie.

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

gdzie +jest zwykle oznaczane |, \eoznacza puste słowo i [^]jest zwykle zapisywane .(dowolny znak). Więc przy odrobinie przepisywania ()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*.

Możesz zobaczyć ten przykład tutaj i wypróbować Vcsn online tam .


6
To prawda, ale brzydka i możliwa do wykonania tylko dla małych zestawów postaci. Nie chcesz tego robić z ciągami Unicode :-)
reinierpost

Jest na to więcej narzędzi, jednym z najbardziej imponujących jest Ragel . Tam byłby zapisany jako (dowolny * - („hehe” dowolny *)) dla dopasowania wyrównanego do początku lub (dowolny * - („hehe” dowolny *)) dla niezrównanego.
Peter K

1
@reinierpost: dlaczego jest brzydki i na czym polega problem z Unicode? Nie mogę się zgodzić na oba. (Nie mam doświadczenia z vcsn, ale mam z DFA).
Peter K,

3
@PedroGimeno Kiedy zakotwiczałeś, najpierw umieściłeś ten regex w parens? W przeciwnym razie pierwszeństwo między kotwicami i |nie będzie ładnie grało. '^(()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*)$'.
akim

1
Myślę, że warto zauważyć, że ta metoda służy do dopasowywania linii, które nie są słowem „hede”, zamiast linii, które nie zawierają słowa „hede”, o co poprosił OP. Zobacz moją odpowiedź na to drugie.
Pedro Gimeno

51

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


10
Niektóre narzędzia, a szczególnie mysqldumpslow, oferują tylko ten sposób filtrowania danych, więc w takim przypadku znalezienie wyrażenia regularnego w tym celu jest najlepszym rozwiązaniem oprócz przepisania narzędzia (różne łaty do tego nie zostały dołączone przez MySQL AB / Sun / Oracle
FGM,

1
Dokładnie analogiczny do mojej sytuacji. Silnik szablonów Velocity 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

1
Jaka jest alternatywa? Nigdy nie spotkałem niczego, co poza precyzyjnym wyrażeniem regularnym mogłoby precyzyjnie dopasowywać ciągi. Jeśli OP używa języka programowania, mogą być dostępne inne narzędzia, ale jeśli on / ona nie używa kodu, prawdopodobnie nie ma innego wyboru.
kingfrito_5005,

2
Jeden z wielu hipotetycznych scenariuszy, w których wyrażenie regularne jest najlepszym dostępnym wyborem: jestem w środowisku IDE (Android Studio), które pokazuje dane wyjściowe dziennika, a jedynymi dostępnymi narzędziami filtrującymi są: ciągi zwykłe i wyrażenie regularne. Próba zrobienia tego za pomocą zwykłych ciągów byłaby całkowitą porażką.
LarsH,

48

W przypadku negatywnego spojrzenia wyrażenie regularne może pasować do czegoś, co nie zawiera określonego wzorca. Odpowiada na to i wyjaśnia Bart Kiers. Świetne wyjaśnienie!

Jednak z odpowiedzią Barta Kiersa część lookahead przetestuje od 1 do 4 znaków z przodu, dopasowując dowolny pojedynczy znak. Możemy tego uniknąć i pozwolić, aby część z wyprzedzeniem sprawdziła cały tekst, upewniając się, że nie ma „hede”, a wtedy normalna część (. *) Może zjeść cały tekst jednocześnie.

Oto ulepszone wyrażenie regularne:

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

Zauważ, że (*?) Leniwy kwantyfikator w negatywnej części z wyprzedzeniem jest opcjonalny, możesz zamiast tego użyć (*) chciwego kwantyfikatora, w zależności od twoich danych: jeśli „hede” występuje i na początku połowy tekstu, leniwy kwantyfikator może bądź szybszy; w przeciwnym razie chciwy kwantyfikator będzie szybszy. Jeśli jednak „hede” się nie pojawi, oba będą równe powolne.

Oto kod demo .

Aby uzyskać więcej informacji o lookahead, zapoznaj się ze świetnym artykułem: Mastering Lookahead and Lookbehind .

Sprawdź także RegexGen.js , generator wyrażeń regularnych JavaScript, który pomaga konstruować złożone wyrażenia regularne. Za pomocą RegexGen.js możesz zbudować wyrażenie regularne w bardziej czytelny sposób:

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
więc po prostu sprawdź, czy podany ciąg nie zawiera str1 i ^(?!.*(str1|str2)).*$
str2

1
Tak, lub możesz użyć leniwego kwantyfikatora: ^(?!.*?(?:str1|str2)).*$w zależności od danych. Dodano, ?:ponieważ nie musimy tego robić.
amobiz

To zdecydowanie najlepsza odpowiedź dziesięciokrotnie. Jeśli dodasz swój kod jsfiddle i wyniki do odpowiedzi, ludzie mogą to zauważyć. Zastanawiam się, dlaczego leniwa wersja jest szybsza niż wersja zachłanna, skoro nie ma hede. Czy nie powinny zajmować tyle samo czasu?
user5389726598465,

Tak, zajmują tyle samo czasu, odkąd oboje testują cały tekst.
amobiz

41

Benchmarki

Postanowiłem ocenić niektóre z przedstawionych Opcji i porównać ich wydajność, a także użyć nowych funkcji. Benchmarking w .NET Regex Engine: http://regexhero.net/tester/

Tekst testu:

Pierwsze 7 wierszy nie powinno się zgadzać, ponieważ zawierają wyszukiwane wyrażenie, a dolne 7 wierszy powinno pasować!

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.

Wyniki:

Wyniki to Iteracje na sekundę jako mediana 3 przebiegów - Większa liczba = lepsza

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

Ponieważ .NET nie obsługuje działań Czasowniki (* FAIL itp.), Nie mogłem przetestować rozwiązań P1 i P2.

Podsumowanie:

Próbowałem przetestować większość proponowanych rozwiązań, niektóre Optymalizacje są możliwe dla niektórych słów. Na przykład, jeśli dwie pierwsze litery szukanego ciągu nie są takie same, odpowiedź 03 można rozszerzyć, aby ^(?>[^R]+|R+(?!egex Hero))*$uzyskać niewielki wzrost wydajności.

Ale ogólnie najbardziej czytelnym i pod względem wydajności najszybszym rozwiązaniem wydaje się być 05 przy użyciu instrukcji warunkowej lub 04 z dodatnim kwantyfikatorem. Myślę, że rozwiązania Perla powinny być jeszcze szybsze i bardziej czytelne.


5
Ty też powinieneś czas ^(?!.*hede). /// Również prawdopodobnie lepiej uszeregować wyrażenia dla dopasowanego korpusu i niepasującego korpusu osobno, ponieważ zwykle jest tak, że większość pasujących wierszy lub większość linii nie.
ikegami

32

Nie regex, ale uznałem, że logiczne i użyteczne jest używanie seryjnych grepów z rurką w celu wyeliminowania hałasu.

na przykład. przeszukać plik konfiguracyjny Apache bez wszystkich komentarzy

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

i

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

Logika seryjnego grepa to (nie komentarz) i (pasuje do reż)


2
Myślę, że prosi o grep -v
regexową

9
To jest niebezpieczne. Brakuje również linii takich jakgood_stuff #comment_stuff
Xavi Montero

29

dzięki temu unikniesz testowania z wyprzedzeniem każdej pozycji:

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

odpowiednik (dla .net):

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

Stara odpowiedź:

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

7
Słuszna uwaga; Dziwię się, że nikt wcześniej nie wspomniał o takim podejściu. Jednak ten konkretny regex jest podatny na katastrofalne cofanie, gdy jest stosowany do tekstu, który nie pasuje. Oto jak bym to zrobił:/^[^h]*(?:h+(?!ede)[^h]*)*$/
Alan Moore,

... lub możesz po prostu uczynić wszystkie kwantyfikatory zaborczymi. ;)
Alan Moore

@Alan Moore - Też jestem zaskoczony. Widziałem twój komentarz (i najlepsze wyrażenie regularne w stosie) tutaj dopiero po opublikowaniu tego samego wzoru w odpowiedzi poniżej.
ridgerunner,

@ridgerunner, nie musi być najlepszy. Widziałem testy porównawcze, w których najlepsza odpowiedź działa lepiej. (Byłem zaskoczony tym).
Qtax

23

Wyżej wspomniane (?:(?!hede).)*jest świetne, ponieważ można je zakotwiczyć.

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

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

W tym przypadku wystarczyłyby:

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

W tym uproszczeniu można dodać klauzule „ORAZ”:

^(?!.*hede)(?=.*foo)(?=.*bar)   # A line with foo and bar, but without hede
^(?!.*hede)(?=.*foo).*bar       # Same

20

Oto jak bym to zrobił:

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

Dokładne i bardziej wydajne niż inne odpowiedzi. Implementuje technikę efektywności „rozwijania pętli” Friedla i wymaga znacznie mniejszego cofania.


17

Jeśli chcesz dopasować znak, aby zanegować słowo podobne do negacji klasy znaków:

Na przykład ciąg:

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

Nie używaj:

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

Posługiwać się:

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

Uwaga "(?!bbb)."nie jest ani lookbehind ani lookahead, jest lookcurrent, na przykład:

"(?=abc)abcde", "(?!abc)abcde"

3
Perl regexp nie zawiera „lookcurrent”. To jest naprawdę negatywne spojrzenie w przyszłość (przedrostek (?!). Dodatni prefiks (?=lookahead byłby, podczas gdy odpowiadające prefiksy lookhind byłyby odpowiednio (?<!i (?<=. Lookahead oznacza, że ​​czytasz kolejne postacie (stąd „naprzód”) bez ich konsumowania. Lookbehind oznacza, że ​​sprawdzasz postacie, które zostały już zużyte.
Didier L

14

Moim zdaniem, bardziej czytelny wariant najwyższej odpowiedzi:

^(?!.*hede)

Zasadniczo „dopasuj na początku wiersza, jeśli i tylko wtedy, gdy nie ma w nim„ hede ”- więc wymaganie przełożyło się prawie bezpośrednio na wyrażenie regularne.

Oczywiście istnieje wiele wymagań dotyczących awarii:

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

Szczegóły: Kotwica ^ zapewnia, że ​​silnik regex nie powtórzy dopasowania w każdym miejscu ciągu, co pasowałoby do każdego łańcucha.

Kotwica ^ na początku ma oznaczać początek linii. Narzędzie grep dopasowuje każdą linię pojedynczo, w kontekstach, w których pracujesz z ciągiem wielowierszowym, możesz użyć flagi „m”:

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

lub

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

Doskonały przykład z wielokrotną negacją.
Peter Parada,

Jedną z różnic od góry odpowiedź jest taka, że to niczego nie pasuje, i który pasuje do całej linii, jeśli bez „Hede”
Z. Khullah

13

OP nie określił ani nie Tagwskazał kontekstu (języka programowania, edytora, narzędzia), w którym będzie używany Regex.

Dla mnie czasami muszę to zrobić podczas edytowania pliku za pomocą Textpad.

Textpad obsługuje niektóre Regex, ale nie obsługuje lookahead ani lookbeind, więc zajmuje to kilka kroków.

Jeśli chcę zachować wszystkie wiersze, które NIE zawierają łańcucha hede, zrobiłbym to w ten sposób:

1. Wyszukaj / zamień cały plik, aby dodać niepowtarzalny „Tag” na początku każdego wiersza zawierającego dowolny tekst.

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

2. Usuń wszystkie wiersze zawierające ciąg hede(ciąg zastępujący jest pusty):

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

3. W tym momencie wszystkie pozostałe linie NIE zawierają łańcucha hede. Usuń unikalny „Tag” ze wszystkich wierszy (ciąg zastępujący jest pusty):

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

Teraz masz oryginalny tekst ze wszystkimi wierszami zawierającymi ciąg znaków hede.


Jeśli chcę zrobić coś innego tylko w wierszach, które NIE zawierają łańcucha hede, zrobiłbym to w ten sposób:

1. Wyszukaj / zamień cały plik, aby dodać niepowtarzalny „Tag” na początku każdego wiersza zawierającego dowolny tekst.

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

2. W przypadku wszystkich wierszy zawierających ciąg hedeusuń unikalny „Tag”:

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

3. W tym momencie wszystkie wiersze rozpoczynające się od unikalnego „Tagu” NIE zawierają łańcucha hede. Mogę teraz zrobić coś innego tylko dla tych linii.

4. Kiedy skończę, usuwam unikalny „Tag” ze wszystkich wierszy (ciąg zastępujący jest pusty):

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

12

Ponieważ nikt inny nie udzielił bezpośredniej odpowiedzi na zadane pytanie , zrobię to.

Odpowiedź jest taka, że ​​w POSIX grepniemożliwe jest dosłownie spełnienie tego żądania:

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

Powodem jest to, że POSIX grepjest wymagany tylko do pracy z podstawowymi wyrażeniami regularnymi , które po prostu nie są wystarczające do wykonania tego zadania (nie są w stanie analizować zwykłych języków z powodu braku naprzemienności i nawiasów).

Jednak GNU grepimplementuje rozszerzenia, które na to pozwalają. W szczególności \|jest operatorem naprzemiennym w implementacji BREU przez GNU \(i \)jest nawiasami. Jeśli silnik wyrażeń regularnych obsługuje naprzemiennie, wyrażenia z nawiasami ujemnymi, nawiasy i gwiazdę Kleene i jest w stanie zakotwiczyć na początku i na końcu łańcucha, to wszystko, czego potrzebujesz do tego podejścia. Zauważ jednak, że zestawy ujemne [^ ... ]są bardzo wygodne oprócz tych, ponieważ w przeciwnym razie musisz je zastąpić wyrażeniem formy, (a|b|c| ... )która zawiera listę wszystkich znaków, których nie ma w zestawie, co jest niezwykle żmudne i zbyt długie, tym bardziej, jeśli cały zestaw znaków to Unicode.

W przypadku GNU grepodpowiedzią byłoby:

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

(znaleziono z Graalem i kilkoma dalszymi optymalizacjami wykonanymi ręcznie).

Możesz także użyć narzędzia, które implementuje Rozszerzone wyrażenia regularne , na przykład egrep, aby pozbyć się odwrotnych ukośników:

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

Oto skrypt do jego przetestowania (pamiętaj, że generuje plik testinput.txtw bieżącym katalogu):

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

W moim systemie drukuje:

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

zgodnie z oczekiwaniami.

Dla zainteresowanych szczegółami zastosowana technika polega na konwersji wyrażenia regularnego pasującego do słowa na automat skończony, a następnie odwróceniu automatu poprzez zmianę każdego stanu akceptacji na brak akceptacji i odwrotnie, a następnie konwersję wynikowego FA z powrotem na wyrażenie regularne.

W końcu, jak wszyscy zauważyli, jeśli silnik wyrażeń regularnych obsługuje negatywne spojrzenie, to znacznie upraszcza to zadanie. Na przykład z GNU grep:

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

Aktualizacja: Niedawno znalazłem doskonałą bibliotekę FormalTheory Kendalla Hopkinsa , napisaną w PHP, która zapewnia funkcjonalność podobną do Graala. Używając go i napisanego przeze mnie prostownika, byłem w stanie napisać internetowy generator negatywnych wyrażeń regularnych, podając frazę wejściową (obecnie obsługiwane są tylko znaki alfanumeryczne i spacje): http://www.formauri.es/personal/ pgimeno / misc / non-match-regex /

Do hedewyjścia:

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

co jest równoważne z powyższym.


11

Od czasu wprowadzenia ruby-2.4.1 możemy używać nowego nieobecnego operatora w wyrażeniach regularnych Ruby

z oficjalnego dokumentu

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

Tak więc w twoim przypadku ^(?~hede)$robi to za Ciebie

2.4.1 :016 > ["hoho", "hihi", "haha", "hede"].select{|s| /^(?~hede)$/.match(s)}
 => ["hoho", "hihi", "haha"]

9

Przez czasownik PCRE (*SKIP)(*F)

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

To całkowicie pomija linię, która zawiera dokładny ciąg hedei pasuje do wszystkich pozostałych linii.

PRÓBNY

Wykonanie części:

Rozważmy powyższe wyrażenie regularne, dzieląc je na dwie części.

  1. Część przed |symbolem. Części nie należy dopasowywać .

    ^hede$(*SKIP)(*F)
  2. Część po |symbolu. Część powinna być dopasowana .

    ^.*$

CZĘŚĆ 1

Silnik Regex rozpocznie wykonywanie od pierwszej części.

^hede$(*SKIP)(*F)

Wyjaśnienie:

  • ^ Zapewnia, że ​​jesteśmy na początku.
  • hede Dopasowuje ciąg hede
  • $ Zapewnia, że ​​jesteśmy na końcu linii.

Tak więc linia zawierająca ciąg znaków hedebyłaby dopasowana. Gdy silnik wyrażenia regularnego zobaczy następujący czasownik (*SKIP)(*F)( uwaga: możesz napisać (*F)jako(*FAIL) ), przeskakuje i powoduje niepowodzenie dopasowania. |nazywany zmianą lub logicznym operatorem OR dodanym obok czasownika PCRE, którego inturn pasuje do wszystkich granic istniejących między każdym znakiem na wszystkich liniach, z wyjątkiem tego, że wiersz zawiera dokładny ciąg hede. Zobacz demo tutaj . Oznacza to, że próbuje dopasować znaki z pozostałego ciągu. Teraz wyrażenie regularne w drugiej części zostanie wykonane.

CZĘŚĆ 2

^.*$

Wyjaśnienie:

  • ^ Zapewnia, że ​​jesteśmy na początku. tzn. pasuje do wszystkich początków linii oprócz tej w hedelinii. Zobacz demo tutaj .
  • .*W trybie wielowierszowym pasowałby do .każdego znaku oprócz znaków nowego wiersza lub znaków powrotu karetki. I *powtórzyłby poprzedni znak zero lub więcej razy. Więc .*pasowałby do całej linii. Zobacz demo tutaj .

    Hej, dlaczego dodałeś. * Zamiast. +?

    Ponieważ .*pasowałby do pustej linii, ale .+nie pasuje do pustej. Chcemy dopasować wszystkie linie, z wyjątkiem tego hede, że na wejściu mogą występować puste linie. więc musisz użyć .*zamiast .+. .+powtórzy poprzedni znak jeden lub więcej razy. Zobacz .*pasuje do pustej linii tutaj .

  • $ Zakotwiczenie końca linii nie jest tutaj konieczne.


7

Może być łatwiejsze do utrzymania do dwóch wyrażeń regularnych w kodzie, jednego do wykonania pierwszego dopasowania, a następnie, jeśli się zgadza, uruchom drugie wyrażenie regularne, aby sprawdzić przypadki odstające, które chcesz na przykład zablokować, ^.*(hede).*a następnie mieć odpowiednią logikę w kodzie.

OK, przyznaję, że tak naprawdę nie jest to odpowiedź na wysłane pytanie i może również wymagać nieco więcej przetwarzania niż jednego wyrażenia regularnego. Ale dla programistów, którzy przybyli tutaj, szukając szybkiej naprawy awaryjnej dla przypadku odstającego, to rozwiązanie nie powinno zostać przeoczone.


5

TXR Język obsługuje regex negację.

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

Bardziej skomplikowany przykład: dopasuj wszystkie linie, które zaczynają się ai kończą z, ale nie zawierają podłańcucha 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

Negacja Regex sama w sobie nie jest szczególnie przydatna, ale gdy masz również skrzyżowanie, sprawy stają się interesujące, ponieważ masz pełny zestaw operacji na zestawach boolowskich: możesz wyrazić „zestaw, który pasuje do tego, z wyjątkiem rzeczy, które pasują do tego”.


Należy pamiętać, że jest to również rozwiązanie dla wyrażenia regularnego opartego na ElasticSearch Lucene.
Wiktor Stribiżew

5

Inną opcją jest dodanie pozytywnego spojrzenia w przyszłość i sprawdzenie, czy hehejest gdzieś w wierszu wprowadzania, wtedy negujemy to, używając wyrażenia podobnego do:

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

z granicami słów.


Wyrażenie to wyjaśniono w prawym górnym panelu strony regex101.com , jeśli chcesz je zbadać / uprościć / zmodyfikować, a pod tym linkiem możesz zobaczyć, jak będzie pasował do niektórych przykładowych danych wejściowych, jeśli chcesz.


RegEx Circuit

jex.im wizualizuje wyrażenia regularne:

wprowadź opis zdjęcia tutaj


4

Poniższa funkcja pomoże ci uzyskać pożądaną wydajność

<?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).) * $ jest eleganckim rozwiązaniem, z wyjątkiem tego, że zużywa znaki, nie będziesz w stanie połączyć go z innymi kryteriami. Powiedzmy na przykład, że chcesz sprawdzić, czy nie ma „hede” i „haha”. To rozwiązanie działałoby, ponieważ nie zużywa znaków:

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


1

Jak używać czasowników kontrolnych cofania PCRE, aby dopasować wiersz nie zawierający słowa

Oto metoda, której wcześniej nie widziałem:

/.*hede(*COMMIT)^|/

Jak to działa

Najpierw próbuje znaleźć „hede” gdzieś w linii. Jeśli się powiedzie, w tym momencie (*COMMIT)mówi silnikowi, aby nie tylko nie cofał się w przypadku awarii, ale także nie próbował w tym przypadku dokonywać dalszych dopasowań. Następnie próbujemy dopasować coś, co nie może być zgodne (w tym przypadku, ^).

Jeśli wiersz nie zawiera „hede”, wówczas druga alternatywa, pusty wzorzec, z powodzeniem dopasowuje ciąg tematu.

Ta metoda nie jest bardziej wydajna niż negatywne spojrzenie w przyszłość, ale pomyślałem, że po prostu ją tu wrzucę, na wypadek, gdyby ktoś uznał ją za sprytną i znalazł zastosowanie w innych, bardziej interesujących aplikacjach.


0

Prostszym rozwiązaniem jest użycie operatora non !

Twoja instrukcja if będzie musiała pasować do „zawiera”, a nie do „wyklucza”.

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

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

Wierzę, że projektanci RegEx przewidzieli użycie nie operatorów.


0

Być może znajdziesz to w Google podczas próby napisania wyrażenia regularnego, które jest w stanie dopasować segmenty linii (w przeciwieństwie do całych linii), które nie zawierają podłańcucha. Zajęło mi to trochę czasu, aby dowiedzieć się, więc podzielę się:

Podany ciąg: <span class="good">bar</span><span class="bad">foo</span><span class="ugly">baz</span>

Chcę dopasować <span>tagi, które nie zawierają podłańcucha „zły”.

/<span(?:(?!bad).)*?>będzie pasować <span class=\"good\">i <span class=\"ugly\">.

Zauważ, że istnieją dwa zestawy (warstwy) nawiasów:

  • Najbardziej wewnętrzny dotyczy negatywnego spojrzenia w przyszłość (nie jest to grupa przechwytująca)
  • Najbardziej zewnętrzna została zinterpretowana przez Ruby jako grupa przechwytywania, ale nie chcemy, aby była to grupa przechwytywania, więc dodałem?: Na początku i nie jest już interpretowana jako grupa przechwytywania.

Demo w 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

Za pomocą ConyEdit można użyć wiersza polecenia, cc.gl !/hede/aby uzyskać wiersze, które nie zawierają dopasowania wyrażenia regularnego, lub użyć wiersza polecenia, cc.dl /hede/aby usunąć wiersze zawierające dopasowanie wyrażenia regularnego. Mają ten sam wynik.


0

Chciałem dodać kolejny przykład, jeśli próbujesz dopasować całą linię, która zawiera ciąg X , ale nie zawiera również ciągu Y. .

Na przykład, powiedzmy, że chcemy sprawdzić, czy nasz adres URL / ciąg znaków zawiera „ smaczne smakołyki ”, o ile nie zawiera nigdzie „ czekolady ”.

Ten wzorzec wyrażenia regularnego działałby (działa również w JavaScript)

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

(na przykład globalne flagi wieloliniowe)

Interaktywny przykład: https://regexr.com/53gv4

mecze

(Te adresy URL zawierają „smaczne smakołyki”, a także nie zawierają „czekolady”)

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

Nie pasuje

(Te adresy URL zawierają gdzieś „czekoladę” - więc nie będą pasować, nawet jeśli zawierają „smaczne smakołyki”)

  • example.com/tasty-treats/chocolate-cake
  • example.com/home-cooking/oven-roasted-chicken
  • example.com/tasty-treats/banana-chocolate-fudge
  • example.com/desserts/chocol/tasty-treats
  • example.com/chocol/tasty-treats/desserts
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.