Regulärer Ausdruck für eine Zeile, die kein Wort enthält


4294

Ich weiß, dass es möglich ist, ein Wort zu finden und die Übereinstimmungen dann mit anderen Werkzeugen (z grep -v. B. ) umzukehren . Ist es jedoch möglich, Zeilen, die kein bestimmtes Wort enthalten hede, mit einem regulären Ausdruck abzugleichen?

Eingang:

hoho
hihi
haha
hede

Code:

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

Gewünschte Ausgabe:

hoho
hihi
haha

85
Wahrscheinlich ein paar Jahre zu spät, aber was ist los mit : ([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$)))*? Die Idee ist einfach. Passen Sie weiter an, bis Sie den Anfang der unerwünschten Zeichenfolge sehen, und stimmen Sie dann nur in den N-1-Fällen überein, in denen die Zeichenfolge nicht fertig ist (wobei N die Länge der Zeichenfolge ist). Diese N-1-Fälle sind "h gefolgt von Nicht-e", "Er gefolgt von Nicht-d" und "Hed gefolgt von Nicht-e". Wenn Sie es geschafft haben, diese N-1-Fälle zu bestehen, haben Sie die unerwünschte Zeichenfolge nicht erfolgreich gefunden, sodass Sie [^h]*erneut nach ihr suchen können
stevendesu

323
@stevendesu: versuche dies für 'ein-sehr-sehr-langes Wort' oder noch besser einen halben Satz. Viel Spaß beim Tippen. Übrigens ist es fast unlesbar. Ich weiß nichts über die Auswirkungen auf die Leistung.
Peter Schuetze

13
@PeterSchuetze: Sicher, es ist nicht schön für sehr, sehr lange Wörter, aber es ist eine praktikable und korrekte Lösung. Obwohl ich die Leistung nicht getestet habe, würde ich mir nicht vorstellen, dass sie zu langsam ist, da die meisten der letztgenannten Regeln ignoriert werden, bis Sie ein h (oder den ersten Buchstaben des Wortes, Satzes usw.) sehen. Mit iterativer Verkettung können Sie die Regex-Zeichenfolge für lange Zeichenfolgen problemlos generieren. Wenn es funktioniert und schnell generiert werden kann, ist die Lesbarkeit wichtig? Dafür sind Kommentare da.
Stevendesu

57
@stevendesu: Ich bin noch später, aber diese Antwort ist fast völlig falsch. Zum einen muss das Subjekt "h" enthalten, was es nicht haben sollte, da die Aufgabe "Übereinstimmungszeilen, die kein bestimmtes Wort enthalten" ist. Nehmen wir an, Sie ^([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$))?)*$ wollten die innere Gruppe optional machen und das Muster ist verankert: Dies schlägt fehl, wenn Instanzen von "hede" Teilinstanzen von "hede" vorausgehen, wie in "hhede".
Jaytea

8
Diese Frage wurde zu den häufig gestellten Fragen zum Stapelüberlauf für reguläre Ausdrücke unter "Advanced Regex-Fu" hinzugefügt .
Aliteralmind

Antworten:


5894

Die Vorstellung, dass Regex keine inverse Übereinstimmung unterstützt, ist nicht ganz richtig. Sie können dieses Verhalten nachahmen, indem Sie negative Umschauen verwenden:

^((?!hede).)*$

Der obige reguläre Ausdruck stimmt mit jeder Zeichenfolge oder Zeile ohne Zeilenumbruch überein, die nicht die (Unter-) Zeichenfolge 'hede' enthält. Wie bereits erwähnt, ist dies nicht etwas, was Regex "gut" kann (oder tun sollte), aber es ist dennoch möglich.

Wenn Sie auch Zeilenumbruchzeichen verwenden müssen, verwenden Sie den Modifikator DOT-ALL (der folgende sim folgenden Muster):

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

oder verwenden Sie es inline:

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

(wo /.../sind die Regex-Begrenzer, dh nicht Teil des Musters)

Wenn der Modifikator DOT-ALL nicht verfügbar ist, können Sie dasselbe Verhalten mit der Zeichenklasse nachahmen [\s\S]:

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

Erläuterung

Eine Zeichenfolge ist nur eine Liste von nZeichen. Vor und nach jedem Zeichen steht eine leere Zeichenfolge. Eine Liste von nZeichen enthält also n+1leere Zeichenfolgen. Betrachten Sie die Zeichenfolge "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

wo die e's sind die leeren Zeichenfolgen. Die Regex (?!hede).schaut nach vorne, um zu sehen, ob kein Teilstring "hede"zu sehen ist. Wenn dies der Fall ist (also etwas anderes zu sehen ist), stimmt der .(Punkt) mit jedem Zeichen außer einem Zeilenumbruch überein. Look-arounds werden auch als Zusicherungen mit der Breite Null bezeichnet, da sie keine Zeichen verbrauchen . Sie behaupten / validieren nur etwas.

In meinem Beispiel wird also jede leere Zeichenfolge zuerst überprüft, um festzustellen, ob kein "hede"Zeichen vor Ihnen liegt, bevor ein Zeichen vom .(Punkt) verbraucht wird . Der Regex (?!hede).macht das nur einmal, also wird er in eine Gruppe eingeschlossen und null oder mehrmals wiederholt : ((?!hede).)*. Schließlich werden der Anfang und das Ende der Eingabe verankert, um sicherzustellen, dass die gesamte Eingabe verbraucht wird:^((?!hede).)*$

Wie Sie sehen können, schlägt die Eingabe "ABhedeCD"fehl, da e3der reguläre Ausdruck (?!hede)einfällt (es liegt "hede" vor Ihnen !).


26
Ich würde nicht so weit gehen zu sagen, dass dies etwas ist, in dem Regex schlecht ist. Die Bequemlichkeit dieser Lösung liegt auf der Hand und der Leistungsverlust im Vergleich zu einer programmgesteuerten Suche wird oft unwichtig sein.
Archimaredes

29
Streng genommen macht ein negativer Blick nach vorne Sie zu einem regulären Ausdruck, der nicht regelmäßig ist.
Peter K

55
@PeterK, klar, aber das ist SO, nicht MathOverflow oder CS-Stackexchange. Leute, die hier eine Frage stellen, suchen im Allgemeinen nach einer praktischen Antwort. Die meisten Bibliotheken oder Tools (wie grepim OP erwähnt) mit Regex-Unterstützung verfügen alle über Funktionen, die sie theoretisch nicht regulär machen.
Bart Kiers

19
@ Bart Kiers, keine Beleidigung für Sie, nur dieser Missbrauch der Terminologie irritiert mich ein bisschen. Der wirklich verwirrende Teil hier ist, dass reguläre Ausdrücke im engeren Sinne sehr viel tun können, was OP will, aber die gemeinsame Sprache, um sie zu schreiben, erlaubt es nicht, was zu (mathematisch hässlichen) Problemumgehungen wie Vorausschau führt. Bitte lesen Sie diese Antwort unten und meinen Kommentar dort, um (theoretisch ausgerichtet) die richtige Vorgehensweise zu finden. Es ist unnötig zu erwähnen, dass es bei großen Eingaben schneller funktioniert.
Peter K

17
Für den Fall, dass Sie sich jemals gefragt haben, wie Sie dies in vim tun sollen:^\(\(hede\)\@!.\)*$
baldrs

738

Beachten Sie, dass die Lösung für nicht mit "hede" beginnt :

^(?!hede).*$

ist in der Regel wesentlich effizienter als die Lösung nicht enthalten „Hede“ :

^((?!hede).)*$

Ersteres sucht nur an der ersten Position der Eingabezeichenfolge und nicht an jeder Position nach „hede“.


5
Danke, ich habe damit überprüft, dass die Zeichenfolge keine Ziffern enthält ^ ((?! \ D {5,}).) *
Samih A

2
Hallo! Ich kann nicht compose nicht enden mit „Hede“ regex. Kannst du dabei helfen?
Aleks Ya

1
@AleksYa: Verwenden Sie einfach die "enthalten" -Version und fügen Sie den Endanker in die Suchzeichenfolge ein: Ändern Sie die Zeichenfolge in "nicht übereinstimmen" von "hede" in "hede $"
Nyerguds

2
@AleksYa: Die nicht endende Version könnte mit negativem Lookbehind wie folgt erstellt werden : (.*)(?<!hede)$. Die Version von @Nyerguds würde ebenfalls funktionieren, verfehlt jedoch den in der Antwort erwähnten Leistungspunkt völlig.
Thisismydesign

5
Warum sagen so viele Antworten ^((?!hede).)*$? Ist es nicht effizienter zu bedienen ^(?!.*hede).*$? Es macht das gleiche, aber in weniger Schritten
JackPRead

208

Wenn Sie es nur für grep verwenden, können Sie grep -v hedealle Zeilen abrufen, die kein Hede enthalten.

ETA Oh, die Frage noch einmal zu lesen, grep -vist wahrscheinlich das, was Sie unter "Werkzeugoptionen" verstanden haben.


22
Tipp: Um schrittweise herauszufiltern, was Sie nicht möchten: grep -v "hede" | grep -v "hihi" | ...usw.
Olivier Lalonde

51
Oder mit nur einem Prozessgrep -v -e hede -e hihi -e ...
Olaf Dietsche

15
Oder einfach nur grep -v "hede\|hihi":)
Putnik

2
Wenn Sie viele Muster haben, die Sie herausfiltern möchten, legen Sie sie in eine Datei und verwenden Siegrep -vf pattern_file file
codeforester

4
Oder einfach egrepoder grep -Ev "hede|hihi|etc"um das unangenehme Entkommen zu vermeiden.
Amit Naidu

160

Antworten:

^((?!hede).)*$

Erläuterung:

^(Am Anfang der Zeichenfolge, Gruppierung und Erfassung auf \ 1 (0 oder mehr Mal (entspricht der größtmöglichen Menge)),
(?!schauen Sie nach vorne , um festzustellen, ob dies nicht der Fall ist.

hede deine Saite,

)Ende der Vorausschau, .jedes Zeichen außer \ n,
)*Ende von \ 1 (Hinweis: Da Sie für diese Erfassung einen Quantifizierer verwenden, wird nur die LETZTE Wiederholung des erfassten Musters in \ 1 gespeichert)
$vor einem optionalen \ n, und das Ende der Zeichenfolge


14
Super, das hat für mich in erhabenem Text 2 mit mehreren Wörtern ^((?!DSAU_PW8882WEB2|DSAU_PW8884WEB2|DSAU_PW8884WEB).)*$
funktioniert

3
@ DamodarBashyal Ich weiß, ich bin ziemlich spät hier, aber Sie könnten die zweite Amtszeit dort vollständig entfernen und Sie würden genau die gleichen Ergebnisse erhalten
forresthopkinsa

99

Die gegebenen Antworten sind vollkommen in Ordnung, nur ein akademischer Punkt:

Reguläre Ausdrücke im Sinne der theoretischen Informatik sind nicht in der Lage, dies so zu tun. Für sie musste es ungefähr so ​​aussehen:

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

Dies entspricht nur einer vollständigen Übereinstimmung. Es wäre noch umständlicher, dies für Teilspiele zu tun.


1
Es ist wichtig zu beachten, dass hier nur grundlegende reguläre POSIX.2-Ausdrücke verwendet werden und dass Terse daher portabler ist, wenn PCRE nicht verfügbar ist.
Steve-o

5
Genau. Viele, wenn nicht die meisten regulären Ausdrücke sind keine regulären Sprachen und konnten von endlichen Automaten nicht erkannt werden.
Thomas McLeod

@ThomasMcLeod, Hades32: Liegt es im Bereich einer möglichen regulären Sprache, in der Lage zu sein, " nicht " und " und " sowie das " oder " eines Ausdrucks wie " (hede|Hihi)" zu sagen ? (Dies ist vielleicht eine Frage an CS.)
James Haigh

7
@ JohnAllen: ICH !!! … Nun, nicht die eigentliche Regex, sondern die akademische Referenz, die auch eng mit der Komplexität der Berechnungen zusammenhängt; PCREs können grundsätzlich nicht die gleiche Effizienz wie reguläre POSIX-Ausdrücke garantieren.
James Haigh

4
Entschuldigung - diese Antwort funktioniert einfach nicht, sie passt zu ihm und sogar teilweise (in der zweiten Hälfte)
Falco,

60

Wenn der Regex-Test nur fehlschlagen soll, wenn die gesamte Zeichenfolge übereinstimmt, funktioniert Folgendes:

^(?!hede$).*

Beispiel: Wenn Sie alle Werte außer "foo" zulassen möchten (dh "foofoo", "barfoo" und "foobar" werden bestanden, "foo" jedoch fehlschlägt), verwenden Sie: ^(?!foo$).*

Wenn Sie auf exakte Gleichheit prüfen , besteht eine bessere allgemeine Lösung in diesem Fall natürlich darin, die Gleichheit der Zeichenfolgen zu überprüfen, d. H.

myStr !== 'foo'

Sie können die Negation sogar außerhalb des Tests platzieren, wenn Sie Regex-Funktionen benötigen (hier Groß- und Kleinschreibung und Bereichsanpassung):

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

Die Regex-Lösung oben in dieser Antwort kann jedoch in Situationen hilfreich sein, in denen ein positiver Regex-Test erforderlich ist (möglicherweise von einer API).


Was ist mit nachgestellten Leerzeichen? ZB, wenn ich möchte, dass der Test mit einer Zeichenfolge fehlschlägt " hede "?
eagor

@eagor die \sDirektive entspricht einem einzelnen Leerzeichen
Roy Tinker

Danke, aber ich habe es nicht geschafft, den regulären Ausdruck zu aktualisieren, damit dies funktioniert.
eagor

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

52

FWIW, da reguläre Sprachen (auch als rationale Sprachen bezeichnet) unter Ergänzung geschlossen werden, ist es immer möglich, einen regulären Ausdruck (auch als rationaler Ausdruck bezeichnet) zu finden, der einen anderen Ausdruck negiert. Dies wird jedoch nicht von vielen Tools implementiert.

Vcsn unterstützt diesen Operator (den er {c}als Postfix bezeichnet).

Sie definieren zunächst die Art Ihrer Ausdrücke: Etiketten Buchstaben sind ( lal_char) zur Auswahl , aum zzum Beispiel (Definition des Alphabets , wenn sie mit Komplementierung funktioniert, natürlich sehr wichtig), und der „Wert“ für jedes Wort berechnet ist nur ein Boolean : truedas Wort wird akzeptiert false, abgelehnt.

In Python:

In [5]: import vcsn
        c = vcsn.context('lal_char(a-z), b')
        c
Out[5]: {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z}  𝔹

dann geben Sie Ihren Ausdruck ein:

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

Konvertieren Sie diesen Ausdruck in einen Automaten:

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

Der entsprechende Automat

Konvertieren Sie diesen Automaten schließlich wieder in einen einfachen Ausdruck.

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

wo +wird normalerweise bezeichnet |, \ebezeichnet das leere Wort und [^]wird normalerweise geschrieben .(ein beliebiges Zeichen). Also mit ein bisschen Umschreiben ()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*.

Sie können dieses Beispiel sehen hier , und versuchen VCSN Online gibt .


6
Richtig, aber hässlich und nur für kleine Zeichensätze machbar. Du willst das nicht mit Unicode-Strings machen :-)
Reinierpost

Es gibt weitere Tools, die dies ermöglichen. Eines der beeindruckendsten ist Ragel . Dort würde es als (beliebiges * - ('hehe' beliebiges *)) für ein Start-ausgerichtetes Spiel oder (ein beliebiges * - ('hehe' ein beliebiges *)) für ein nicht ausgerichtetes Spiel geschrieben werden.
Peter K

1
@reinierpost: warum ist es hässlich und was ist das Problem mit Unicode? Ich kann mich nicht auf beide einigen. (Ich habe keine Erfahrung mit vcsn, aber mit DFA).
Peter K

3
@PedroGimeno Als du verankert warst, hast du dafür gesorgt, dass dieser Regex zuerst in die Parens kommt? Andernfalls werden die Vorrang zwischen Ankern und |nicht gut gespielt. '^(()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*)$'.
Akim

1
Ich denke, es ist erwähnenswert, dass diese Methode dazu dient, Linien abzugleichen , die nicht das Wort "hede" sind, und nicht Linien, die nicht das Wort "hede" enthalten, was das OP verlangt hat. Siehe meine Antwort für Letzteres.
Pedro Gimeno

51

Hier ist eine gute Erklärung, warum es nicht einfach ist, einen beliebigen regulären Ausdruck zu negieren. Ich muss jedoch den anderen Antworten zustimmen: Wenn dies etwas anderes als eine hypothetische Frage ist, dann ist eine Regex hier nicht die richtige Wahl.


10
Einige Tools, insbesondere mysqldumpslow, bieten nur diese Möglichkeit zum Filtern von Daten. In einem solchen Fall ist es neben dem Umschreiben des Tools die beste Lösung, einen regulären Ausdruck dafür zu finden (verschiedene Patches hierfür wurden von MySQL AB / Sun nicht aufgenommen / Oracle.
FGM

1
Genau analog zu meiner Situation. Die Velocity Template Engine verwendet reguläre Ausdrücke, um zu entscheiden, wann eine Transformation angewendet werden soll (Escape-HTML), und ich möchte, dass sie immer AUSSER in einer Situation funktioniert.
Henno Vermeulen

1
Welche Alternative gibt es? Ich habe noch nie etwas gefunden, das neben Regex einen präzisen String-Abgleich ermöglichen könnte. Wenn OP eine Programmiersprache verwendet, sind möglicherweise andere Tools verfügbar. Wenn er jedoch keinen Code schreibt, gibt es wahrscheinlich keine andere Wahl.
kingfrito_5005

2
Eines von vielen nicht hypothetischen Szenarien, in denen ein regulärer Ausdruck die beste verfügbare Wahl ist: Ich bin in einer IDE (Android Studio), die die Protokollausgabe anzeigt, und die einzigen Filterwerkzeuge, die bereitgestellt werden, sind: einfache Zeichenfolgen und regulärer Ausdruck. Der Versuch, dies mit einfachen Zeichenfolgen zu tun, wäre ein völliger Fehlschlag.
LarsH

48

Bei negativem Lookahead kann der reguläre Ausdruck mit etwas übereinstimmen, das kein bestimmtes Muster enthält. Dies wird von Bart Kiers beantwortet und erklärt. Tolle Erklärung!

Mit der Antwort von Bart Kiers testet der Lookahead-Teil jedoch 1 bis 4 Zeichen voraus, während er mit einem einzelnen Zeichen übereinstimmt. Wir können dies vermeiden und den Lookahead-Teil den gesamten Text überprüfen lassen, sicherstellen, dass kein "Hede" vorhanden ist, und dann kann der normale Teil (. *) Den gesamten Text auf einmal essen.

Hier ist der verbesserte reguläre Ausdruck:

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

Beachten Sie, dass der (*?) Lazy Quantifier im negativen Lookahead-Teil optional ist. Sie können stattdessen (*) Greedy Quantifier verwenden, abhängig von Ihren Daten: Wenn 'hede' vorhanden ist und in der ersten Hälfte des Textes, kann der Lazy Quantifier sei schneller; Andernfalls ist der gierige Quantifizierer schneller. Wenn 'hede' jedoch nicht vorhanden ist, wären beide gleich langsam.

Hier ist der Demo-Code .

Weitere Informationen zu Lookahead finden Sie in dem großartigen Artikel: Mastering Lookahead und Lookbehind .

Schauen Sie sich auch RegexGen.js an , einen JavaScript-Generator für reguläre Ausdrücke, mit dem Sie komplexe reguläre Ausdrücke erstellen können. Mit RegexGen.js können Sie den Regex besser lesbar erstellen:

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
Um einfach zu überprüfen, ob die angegebene Zeichenfolge nicht str1 und str2 enthält:^(?!.*(str1|str2)).*$
S.Serpooshan

1
Ja, oder Sie können einen Lazy Quantifier verwenden: ^(?!.*?(?:str1|str2)).*$abhängig von Ihren Daten. Das wurde hinzugefügt, ?:da wir es nicht erfassen müssen.
Amobiz

Dies ist mit Abstand die beste Antwort mit einem Faktor von 10xms. Wenn Sie Ihren jsfiddle-Code und die Ergebnisse zur Antwort hinzugefügt haben, wird dies möglicherweise bemerkt. Ich frage mich, warum die faule Version schneller ist als die gierige Version, wenn es kein Hede gibt. Sollten sie nicht die gleiche Zeit in Anspruch nehmen?
user5389726598465

Ja, sie benötigen dieselbe Zeit, da beide den gesamten Text testen.
Amobiz

41

Benchmarks

Ich habe mich entschlossen, einige der vorgestellten Optionen zu bewerten, ihre Leistung zu vergleichen und einige neue Funktionen zu verwenden. Benchmarking für .NET Regex Engine: http://regexhero.net/tester/

Benchmark-Text:

Die ersten 7 Zeilen sollten nicht übereinstimmen, da sie den gesuchten Ausdruck enthalten, während die unteren 7 Zeilen übereinstimmen sollten!

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.

Ergebnisse:

Ergebnisse sind Iterationen pro Sekunde als Median von 3 Läufen - Größere Anzahl = Besser

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

Da .NET keine Aktionsverben (* FAIL usw.) unterstützt, konnte ich die Lösungen P1 und P2 nicht testen.

Zusammenfassung:

Ich habe versucht, die meisten vorgeschlagenen Lösungen zu testen. Einige Optimierungen sind für bestimmte Wörter möglich. Wenn beispielsweise die ersten beiden Buchstaben der Suchzeichenfolge nicht identisch sind, kann Antwort 03 erweitert werden, um ^(?>[^R]+|R+(?!egex Hero))*$einen kleinen Leistungsgewinn zu erzielen.

Die insgesamt am besten lesbare und leistungsmäßig schnellste Lösung scheint jedoch 05 zu sein, wenn eine bedingte Anweisung verwendet wird, oder 04 mit dem Possessivquantifizierer. Ich denke, die Perl-Lösungen sollten noch schneller und leichter lesbar sein.


5
Du solltest auch mal ^(?!.*hede). /// Außerdem ist es wahrscheinlich besser, die Ausdrücke für den übereinstimmenden Korpus und den nicht übereinstimmenden Korpus getrennt zu ordnen, da dies normalerweise der Fall ist, wenn die meisten Zeilen übereinstimmen oder die meisten Zeilen nicht.
Ikegami

32

Kein regulärer Ausdruck, aber ich fand es logisch und nützlich, serielle Greps mit Pipe zu verwenden, um Rauschen zu vermeiden.

z.B. Durchsuchen Sie eine Apache-Konfigurationsdatei ohne alle Kommentare.

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

und

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

Die Logik von seriellen Greps ist (kein Kommentar) und (entspricht dir)


2
Ich denke, er fragt nach der Regex-Version desgrep -v
Angel.King.47

9
Das ist gefährlich. Vermisst auch Zeilen wiegood_stuff #comment_stuff
Xavi Montero

29

Damit vermeiden Sie es, an jeder Position einen Lookahead zu testen:

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

äquivalent zu (für .net):

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

Alte Antwort:

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

7
Guter Punkt; Ich bin überrascht, dass niemand diesen Ansatz zuvor erwähnt hat. Diese bestimmte Regex ist jedoch anfällig für katastrophales Backtracking, wenn sie auf Text angewendet wird, der nicht übereinstimmt. So würde ich es machen:/^[^h]*(?:h+(?!ede)[^h]*)*$/
Alan Moore

... oder Sie können einfach alle Quantifizierer besitzergreifend machen. ;)
Alan Moore

@ Alan Moore - Ich bin auch überrascht. Ich habe Ihren Kommentar (und den besten regulären Ausdruck im Stapel) hier erst gesehen, nachdem ich dasselbe Muster in einer Antwort unten veröffentlicht habe.
Ridgerunner

@ridgerunner, muss nicht der beste sein. Ich habe Benchmarks gesehen, bei denen die beste Antwort besser abschneidet. (Ich war darüber überrascht.)
Qtax

23

Das oben Genannte (?:(?!hede).)*ist großartig, weil es verankert werden kann.

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

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

In diesem Fall würde jedoch Folgendes ausreichen:

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

Diese Vereinfachung kann mit "UND" -Klauseln versehen werden:

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

20

So würde ich es machen:

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

Genau und effizienter als die anderen Antworten. Es implementiert Friedls Effizienztechnik "Unrolling-the-Loop" und erfordert viel weniger Backtracking.


17

Wenn Sie ein Zeichen abgleichen möchten, um ein Wort zu negieren, das der Zeichenklasse ähnelt:

Zum Beispiel eine Zeichenfolge:

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

Verwende nicht:

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

Verwenden:

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

Hinweis "(?!bbb)."ist weder Lookbehind noch Lookahead, es ist Lookcurrent, zum Beispiel:

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

3
In Perl-regulären Ausdrücken gibt es keinen "Lookcurrent". Dies ist wirklich ein negativer Lookahead (Präfix (?!). Positive Vorschau des Präfix wäre , (?=während die entsprechenden Präfixe Lookbehind sein würden (?<!und (?<=jeweils. Ein Lookahead bedeutet, dass Sie die nächsten Zeichen (also „voraus“) lesen, ohne sie zu verbrauchen. Ein Lookbehind bedeutet, dass Sie Zeichen überprüfen, die bereits verbraucht wurden.
Didier L

14

Eine meiner Meinung nach besser lesbare Variante der Top-Antwort:

^(?!.*hede)

Grundsätzlich gilt: "Nur dann am Anfang der Zeile übereinstimmen, wenn sie kein" Hede "enthält" - die Anforderung wurde also fast direkt in Regex übersetzt.

Natürlich können mehrere Fehleranforderungen gestellt werden:

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

Einzelheiten: Der ^ -Anker stellt sicher, dass die Regex-Engine die Übereinstimmung nicht an jeder Stelle in der Zeichenfolge wiederholt, die mit jeder Zeichenfolge übereinstimmen würde.

Der ^ Anker am Anfang soll den Anfang der Linie darstellen. Das grep-Tool stimmt mit jeder Zeile einzeln überein. In Kontexten, in denen Sie mit einer mehrzeiligen Zeichenfolge arbeiten, können Sie das Flag "m" verwenden:

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

oder

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

Hervorragendes Beispiel mit mehrfacher Verneinung.
Peter Parada

Ein Unterschied zur Top-Antwort ist, dass dies zu nichts passt und dass es zur gesamten Linie passt, wenn ohne "hede"
Z. Khullah

13

Das OP hat weder angegeben noch Tagden Beitrag angegeben, in welchem ​​Kontext (Programmiersprache, Editor, Tool) der Regex verwendet wird.

Für mich muss ich dies manchmal tun, während ich eine Datei mit bearbeite Textpad.

Textpad unterstützt einige Regex, unterstützt jedoch weder Lookahead noch Lookbehind, sodass einige Schritte erforderlich sind.

Wenn ich alle Zeilen beibehalten möchte, die die Zeichenfolge NICHT enthalten hede, würde ich dies folgendermaßen tun:

1. Suchen / ersetzen Sie die gesamte Datei, um am Anfang jeder Zeile, die Text enthält, ein eindeutiges "Tag" hinzuzufügen.

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

2. Löschen Sie alle Zeilen, die die Zeichenfolge enthalten hede(die Ersatzzeichenfolge ist leer):

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

3. Zu diesem Zeitpunkt enthalten alle verbleibenden Zeilen NICHT die Zeichenfolge hede. Entfernen Sie das eindeutige "Tag" aus allen Zeilen (Ersatzzeichenfolge ist leer):

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

Jetzt haben Sie den Originaltext mit allen Zeilen, die die Zeichenfolge enthalten, hedeentfernt.


Wenn ich etwas anderes tun möchte, um nur Zeilen zu verwenden, die die Zeichenfolge NICHT enthalten hede, würde ich dies folgendermaßen tun:

1. Suchen / ersetzen Sie die gesamte Datei, um am Anfang jeder Zeile, die Text enthält, ein eindeutiges "Tag" hinzuzufügen.

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

2. hedeEntfernen Sie für alle Zeilen, die die Zeichenfolge enthalten , das eindeutige "Tag":

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

3. Zu diesem Zeitpunkt enthalten alle Zeilen, die mit dem eindeutigen "Tag" beginnen, NICHT die Zeichenfolge hede. Ich kann jetzt etwas anderes nur für diese Zeilen tun .

4. Wenn ich fertig bin, entferne ich das eindeutige "Tag" aus allen Zeilen (Ersatzzeichenfolge ist leer):

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

12

Da hat sonst niemand eine direkte Antwort auf die gestellte Frage gegeben , werde ich es tun.

Die Antwort ist, dass es mit POSIX grepunmöglich ist, diese Anfrage buchstäblich zu erfüllen:

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

Der Grund dafür ist, dass POSIX grepnur für die Arbeit mit regulären Basisausdrücken erforderlich ist , die dieser Aufgabe einfach nicht leistungsfähig genug sind (sie können reguläre Sprachen nicht analysieren, da es an Abwechslung und Klammern mangelt).

GNU grepimplementiert jedoch Erweiterungen, die dies ermöglichen. Insbesondere \|ist der Wechseloperator in der Implementierung von BREs durch GNU und \(und \)sind die Klammern. Wenn Ihre Engine für reguläre Ausdrücke Alternation, negative Klammerausdrücke, Klammern und den Kleene-Stern unterstützt und in der Lage ist, am Anfang und Ende der Zeichenfolge zu verankern, ist dies alles, was Sie für diesen Ansatz benötigen. Beachten Sie jedoch, dass negative Mengen [^ ... ]zusätzlich zu diesen sehr praktisch sind, da Sie sie ansonsten durch einen Ausdruck der Form ersetzen müssen (a|b|c| ... ), in der alle Zeichen aufgelistet sind, die nicht in der Menge enthalten sind. Dies ist äußerst mühsam und zu lang, umso mehr, wenn Der gesamte Zeichensatz ist Unicode.

Mit GNU grepwäre die Antwort ungefähr so:

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

(gefunden mit Grail und einigen weiteren Optimierungen von Hand).

Sie können auch ein Tool verwenden, das erweiterte reguläre Ausdrücke implementiert , z. B. egrepum die Backslashes zu entfernen:

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

Hier ist ein Skript zum Testen (beachten Sie, dass es eine Datei testinput.txtim aktuellen Verzeichnis generiert ):

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

In meinem System wird gedruckt:

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

wie erwartet.

Für diejenigen, die an den Details interessiert sind, besteht die angewandte Technik darin, den regulären Ausdruck, der dem Wort entspricht, in einen endlichen Automaten umzuwandeln, dann den Automaten umzukehren, indem jeder Akzeptanzzustand in Nichtakzeptanz geändert wird und umgekehrt, und dann die resultierende FA zurück in umzuwandeln ein regulärer Ausdruck.

Wie alle bemerkt haben, vereinfacht dies die Aufgabe erheblich, wenn Ihre Engine für reguläre Ausdrücke einen negativen Lookahead unterstützt. Zum Beispiel mit GNU grep:

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

Update: Ich habe kürzlich Kendall Hopkins 'exzellente FormalTheory- Bibliothek gefunden, die in PHP geschrieben wurde und eine ähnliche Funktionalität wie Grail bietet. Mit diesem und einem von mir selbst geschriebenen Vereinfacher konnte ich einen Online-Generator für negative reguläre Ausdrücke mit einer Eingabephrase schreiben (derzeit werden nur alphanumerische Zeichen und Leerzeichen unterstützt): http://www.formauri.es/personal/ pgimeno / misc / non-match-regex /

Dafür hedegibt es aus:

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

das ist äquivalent zu den oben genannten.


11

Seit der Einführung von ruby-2.4.1 können wir den neuen Abwesenden Operator in Rubys regulären Ausdrücken verwenden

aus dem offiziellen doc

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

Somit ^(?~hede)$erledigt in Ihrem Fall die Arbeit für Sie

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

9

Durch PCRE Verb (*SKIP)(*F)

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

Dies würde die Zeile, die die genaue Zeichenfolge enthält hedeund mit allen verbleibenden Zeilen übereinstimmt, vollständig überspringen .

DEMO

Ausführung der Teile:

Betrachten wir den obigen regulären Ausdruck, indem wir ihn in zwei Teile aufteilen.

  1. Teil vor dem |Symbol. Teil sollte nicht übereinstimmen .

    ^hede$(*SKIP)(*F)
  2. Teil nach dem |Symbol. Teil sollte abgestimmt sein .

    ^.*$

TEIL 1

Die Regex-Engine startet ihre Ausführung ab dem ersten Teil.

^hede$(*SKIP)(*F)

Erläuterung:

  • ^ Behauptet, dass wir am Anfang sind.
  • hede Entspricht der Zeichenfolge hede
  • $ Behauptet, dass wir am Zeilenende sind.

Die Zeile, die die Zeichenfolge enthält, hedewürde also übereinstimmen. Sobald die Regex-Engine das folgende Verb (*SKIP)(*F)( Hinweis: Sie könnten (*F)als schreiben(*FAIL) ) sieht , überspringt sie und lässt die Übereinstimmung fehlschlagen. |Der so genannte Änderungs- oder logische ODER-Operator, der neben dem PCRE-Verb hinzugefügt wird und mit allen Grenzen übereinstimmt, besteht zwischen jedem einzelnen Zeichen in allen Zeilen, mit Ausnahme der Zeile, die die genaue Zeichenfolge enthält hede. Sehen Sie die Demo hier . Das heißt, es wird versucht, die Zeichen aus der verbleibenden Zeichenfolge abzugleichen. Nun würde der reguläre Ausdruck im zweiten Teil ausgeführt.

TEIL 2

^.*$

Erläuterung:

  • ^ Behauptet, dass wir am Anfang sind. Das heißt, es stimmt mit allen Zeilenstarts überein, mit Ausnahme desjenigen in der hedeZeile. Sehen Sie die Demo hier .
  • .*Im .mehrzeiligen Modus würde jedes Zeichen außer Zeilenumbruch- oder Wagenrücklaufzeichen übereinstimmen. Und *würde das vorherige Zeichen null oder mehrmals wiederholen. Also .*würde die ganze Linie passen. Sehen Sie die Demo hier .

    Hey, warum hast du hinzugefügt. * Statt. +?

    Denn .*würde mit einer Leerzeile übereinstimmen, würde aber .+nicht mit einer Leerzeile übereinstimmen. Wir möchten alle Zeilen abgleichen hede, außer dass möglicherweise auch in der Eingabe Leerzeilen vorhanden sind. also musst du .*statt verwenden .+. .+würde das vorherige Zeichen ein oder mehrere Male wiederholen. Siehe .*Übereinstimmungen mit einer leeren Zeile hier .

  • $ Ein Anker am Ende der Linie ist hier nicht erforderlich.


7

Es kann für zwei reguläre Ausdrücke in Ihrem Code besser wartbar sein, eine für die erste Übereinstimmung, und wenn sie übereinstimmt, führen Sie die zweite reguläre Aussprache aus, um nach Ausreißerfällen zu suchen, die Sie beispielsweise blockieren möchten, ^.*(hede).*und haben Sie dann die entsprechende Logik in Ihrem Code.

OK, ich gebe zu, dass dies keine wirkliche Antwort auf die gestellte Frage ist und möglicherweise auch etwas mehr Verarbeitung als eine einzelne Regex benötigt. Aber für Entwickler, die hierher gekommen sind, um eine schnelle Notfalllösung für einen Ausreißerfall zu finden, sollte diese Lösung nicht übersehen werden.


5

Das TXR-Sprache unterstützt die Regex-Negation.

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

Ein komplizierteres Beispiel: Ordnen Sie alle Zeilen zu, die mit beginnen aund mit endenz , aber den Teilstring nicht enthalten 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

Die Regex-Negation ist für sich genommen nicht besonders nützlich, aber wenn Sie auch eine Kreuzung haben, werden die Dinge interessant, da Sie eine vollständige Reihe von booleschen Mengenoperationen haben: Sie können "die Menge ausdrücken, die dieser entspricht, außer den Dingen, die dieser entsprechen".


Beachten Sie, dass dies auch die Lösung für ElasticSearch Lucene-basierten regulären Ausdruck ist.
Wiktor Stribiżew

5

Eine andere Möglichkeit ist, einen positiven Ausblick hinzuzufügen und zu prüfen, ob hehe sich irgendwo in der Eingabezeile befindet. Dann würden wir dies mit einem ähnlichen Ausdruck wie folgt negieren:

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

mit Wortgrenzen.


Der Ausdruck wird im oberen rechten Bereich von regex101.com erläutert , wenn Sie ihn untersuchen / vereinfachen / ändern möchten, und in diesem Link , können Sie unter beobachten, wie er mit einigen Beispieleingaben .


RegEx Circuit

jex.im visualisiert reguläre Ausdrücke:

Geben Sie hier die Bildbeschreibung ein


4

Mit der folgenden Funktion erhalten Sie die gewünschte Ausgabe

<?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).) * $ ist eine elegante Lösung, außer da sie Zeichen verbraucht, können Sie sie nicht mit anderen Kriterien kombinieren. Angenommen, Sie möchten überprüfen, ob "hede" und "haha" nicht vorhanden sind. Diese Lösung würde funktionieren, da keine Zeichen verbraucht werden:

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


1

So verwenden Sie die Backtracking-Steuerverben von PCRE, um eine Zeile abzugleichen, die kein Wort enthält

Hier ist eine Methode, die ich noch nie gesehen habe:

/.*hede(*COMMIT)^|/

Wie es funktioniert

Zunächst wird versucht, "hede" irgendwo in der Zeile zu finden. Wenn dies erfolgreich ist, (*COMMIT)weist es die Engine an dieser Stelle an, im Falle eines Fehlers nicht nur nicht zurückzuverfolgen, sondern in diesem Fall auch keine weitere Übereinstimmung zu versuchen. Dann versuchen wir, etwas zu finden, das möglicherweise nicht passt (in diesem Fall ^).

Wenn eine Zeile kein "hede" enthält, stimmt die zweite Alternative, ein leeres Untermuster, erfolgreich mit der Betreffzeichenfolge überein.

Diese Methode ist nicht effizienter als ein negativer Lookahead, aber ich dachte, ich würde sie hier einfach aufsetzen, falls jemand sie geschickt findet und sie für andere, interessantere Anwendungen verwendet.


0

Eine einfachere Lösung ist die Verwendung des Not-Operators !

Ihre if- Anweisung muss mit "enthält" und nicht mit "ausschließen" übereinstimmen.

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

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

Ich glaube, die Designer von RegEx haben die Verwendung von Nicht-Operatoren erwartet.


0

Vielleicht finden Sie dies bei Google, wenn Sie versuchen, einen regulären Ausdruck zu schreiben, der Segmenten einer Linie (im Gegensatz zu ganzen Linien) entspricht, die dies nicht tun einen Teil enthalten. Ich habe eine Weile gebraucht, um das herauszufinden, also werde ich teilen:

Gegeben eine Zeichenfolge: <span class="good">bar</span><span class="bad">foo</span><span class="ugly">baz</span>

Ich möchte zusammenpassen <span> Tags zuordnen, die den Teilstring "bad" nicht enthalten.

/<span(?:(?!bad).)*?>wird passen <span class=\"good\">und<span class=\"ugly\"> .

Beachten Sie, dass es zwei Sätze (Ebenen) von Klammern gibt:

  • Das innerste ist für den negativen Lookahead (es ist keine Erfassungsgruppe)
  • Das äußerste wurde von Ruby als Erfassungsgruppe interpretiert, aber wir möchten nicht, dass es eine Erfassungsgruppe ist, also habe ich hinzugefügt ?: Am Anfang und es wird nicht mehr als Erfassungsgruppe interpretiert.

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

Mit ConyEdit können Sie die Befehlszeile verwenden cc.gl !/hede/, um Zeilen abzurufen , die keine Regex-Übereinstimmung enthalten, oder die Befehlszeile verwenden cc.dl /hede/, um Zeilen zu löschen, die die Regex-Übereinstimmung enthalten. Sie haben das gleiche Ergebnis.


0

Ich wollte ein weiteres Beispiel hinzufügen, wenn Sie versuchen, eine ganze Zeile abzugleichen, die die Zeichenfolge X enthält , aber nicht auch die Zeichenfolge Y. .

Nehmen wir zum Beispiel an, wir möchten überprüfen, ob unsere URL / Zeichenfolge " Leckereien " enthält, solange sie nirgendwo " Schokolade " enthält.

Dieses Regex-Muster würde funktionieren (funktioniert auch in JavaScript)

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

(globale, mehrzeilige Flags im Beispiel)

Interaktives Beispiel: https://regexr.com/53gv4

Streichhölzer

(Diese URLs enthalten "Leckereien" und auch keine "Schokolade")

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

Stimmt nicht überein

(Diese URLs enthalten irgendwo "Schokolade" - daher stimmen sie nicht überein, obwohl sie "Leckereien" enthalten.)

  • 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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.