Da niemand sonst eine direkte Antwort auf die gestellte Frage gegeben hat, werde ich es tun.
Die Antwort lautet, dass es mit POSIX grep
unmöglich ist, dieses Anfrage wortwörtlich zu erfüllen:
grep "" Eingabe
Der Grund dafür ist, dass POSIX grep
ohne Flags nur mit Grundlegende Reguläre Ausdrücke (BREs) arbeiten muss, die einfach nicht leistungsstark genug sind, um diese Aufgabe zu erfüllen, aufgrund des Mangels an Alternation in Teilausdrücken. Die einzige Art von Alternation, die es unterstützt, beinhaltet das Bereitstellen mehrerer regulärer Ausdrücke, die durch Zeilenumbrüche getrennt sind, und das deckt nicht alle regulären Sprachen ab, z.B. es gibt keine endliche Sammlung von BREs, die dieselbe reguläre Sprache wie der erweiterte Reguläre Ausdruck (ERE) ^(ab|cd)*$
abbilden.
Allerdings implementiert GNU grep
Erweiterungen, die es ermöglichen. Insbesondere ist \|
der Alternationsoperator in der GNU-Implementierung von BREs. Wenn Ihr regulärer Ausdrucksmotor Alternation, Klammern und den Kleene-Stern unterstützt und an den Anfang und das Ende des Strings verankern kann, dann ist das alles, was Sie für diesen Ansatz benötigen. Beachten Sie jedoch, dass negative Mengen [^ ... ]
sehr praktisch sind, zusätzlich zu denen, denn ansonsten müssen Sie sie durch einen Ausdruck der Form (a|b|c| ... )
ersetzen, der jeden nicht im Satz enthaltenen Buchstaben auflistet, was äußerst mühsam und übermäßig langwierig ist, insbesondere wenn der gesamte Zeichensatz Unicode ist.
Dank der formalen Sprachtheorie sehen wir, wie ein solcher Ausdruck aussieht. Mit GNU grep
wäre die Antwort etwas wie:
grep "^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$" Eingabe
(gefunden mit Grail und einigen weiteren Optimierungen von Hand gemacht).
Sie können auch ein Tool verwenden, das EREs implementiert, wie z.B. egrep
, um die Backslashes loszuwerden, oder alternativ das -E
Flag an POSIX grep
übergeben (obwohl ich den Eindruck hatte, dass die Frage erforderte, jegliche Flags an grep
zu vermeiden):
egrep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" Eingabe
Hier ist ein Skript, um es zu testen (beachten Sie, dass es eine Datei testinput.txt
im aktuellen Verzeichnis generiert). Mehrere der Ausdrücke, die in anderen Antworten präsentiert wurden, fallen bei diesem Test durch.
#!/bin/bash
REGEX="^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$"
# Die ersten vier Zeilen sind wie im Ursprungsfall.
cat > testinput.txt <
``
In meinem System gibt es aus:
Dateien /dev/fd/63 und /dev/fd/62 sind identisch
wie erwartet.
Für diejenigen, die sich für die Details interessieren, besteht die Technik darin, den regulären Ausdruck, der das Wort abgleicht, in einen endlichen Automaten umzuwandeln, dann den Automaten umzukehren, indem jeder Akzeptanzzustand in Nicht-Akzeptanz und umgekehrt geändert wird, und dann den resultierenden FA wieder in einen regulären Ausdruck umzuwandeln.
Wie von allen bemerkt wurde, ist der reguläre Ausdruck wesentlich einfacher, wenn Ihr regulärer Ausdrucksmotor Negative Lookaheads unterstützt. Zum Beispiel mit GNU grep:
grep -P '^((?!hede).)*$' Eingabe
Dieser Ansatz hat jedoch den Nachteil, dass er einen regelbasierten regulären Ausdrucksmotor erfordert. Dies macht ihn in Installationen ungeeignet, die sichere reguläre Ausdrucksmotoren wie RE2 verwenden, was ein Grund ist, den generierten Ansatz in bestimmten Situationen vorzuziehen.
Unter Verwendung von Kendall Hopkins' hervorragender FormalTheory Bibliothek, die in PHP geschrieben ist und eine Funktionalität ähnlich wie Grail bietet, und einem von mir geschriebenen Vereinfacher, konnte ich einen Online-Generator für negative reguläre Ausdrücke erstellen, basierend auf einer Eingabephase (nur alphanumerische und Leerzeichenzeichen werden derzeit unterstützt, und die Länge ist begrenzt): http://www.formauri.es/personal/pgimeno/misc/non-match-regex/
Für hede
gibt es als Ausgabe:
^([^h]|h(h|e(h|dh))*([^eh]|e([^dh]|d[^eh])))*(h(h|e(h|dh))*(ed?)?)?$
das äquivalent zu dem oben genannten ist.
``
108 Stimmen
Wahrscheinlich ein paar Jahre zu spät, aber was ist falsch mit:
([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$)))*
? Die Idee ist einfach. Fahren Sie mit dem Abgleich fort, bis Sie den Beginn des unerwünschten Strings sehen, und gleichen Sie dann nur in den N-1 Fällen ab, in denen der String nicht abgeschlossen ist (wobei N die Länge des Strings ist). Diese N-1 Fälle sind "h gefolgt von nicht-e", "he gefolgt von nicht-d" und "hed gefolgt von nicht-e". Wenn es Ihnen gelungen ist, diese N-1 Fälle zu bestehen, haben Sie den unerwünschten String erfolgreich nicht abgeglichen, sodass Sie erneut mit der Suche nach[^h]*
beginnen können.441 Stimmen
@stevendesu: Versuche dies für 'ein-sehr-sehr-langes-Wort' oder noch besser einen halben Satz. Viel Spaß beim Tippen. Übrigens, es ist fast unleserlich. Weiß nicht über den Leistungseinfluss.
14 Stimmen
@PeterSchuetze: Sicher ist es nicht schön für sehr sehr lange Wörter, aber es ist eine praktikable und korrekte Lösung. Obwohl ich keine Tests zur Leistung durchgeführt habe, würde ich mir vorstellen, dass es nicht allzu langsam ist, da die meisten nachfolgenden Regeln ignoriert werden, bis Sie ein h sehen (oder den ersten Buchstaben des Wortes, Satzes usw. sehen). Und Sie könnten den Regex-String für lange Zeichenfolgen leicht mithilfe iterativer Konkatenation generieren. Wenn es funktioniert und schnell generiert werden kann, ist Lesbarkeit wichtig? Dafür sind Kommentare da.
66 Stimmen
@stevendesu: Ich bin noch später dran, aber diese Antwort ist fast komplett falsch. Zum einen verlangt sie, dass das Subjekt "h" enthält, was es nicht sollte, da die Aufgabe lautet, "Zeilen abzugleichen, die ein bestimmtes Wort nicht enthalten". Lassen Sie uns annehmen, dass Sie die innere Gruppe optional machen wollten und dass das Muster verankert ist:
^([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$))?)*$
. Dies scheitert, wenn Instanzen von "hede" von teilweisen Instanzen von "hede" wie in "hhede" vorausgehen.22 Stimmen
Diese Frage wurde zum Stack Overflow Regular Expression FAQ hinzugefügt, unter "Advanced Regex-Fu".
0 Stimmen
Verwandt: Regex: Übereinstimmung durch Ausschluss, ohne Vorwärtsblick - ist das möglich?