5431 Stimmen

Regulärer Ausdruck, um eine Zeile zu finden, die kein Wort enthält

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

Eingabe:
hoho
hihi
haha
hede
Code:
grep "" input
Gewünschte Ausgabe:
hoho
hihi
haha

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.

42voto

Pedro Gimeno Punkte 2305

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.

``

1 Stimmen

Dies ist die einzige Antwort, die versucht, auf die Frage zu antworten.

36voto

kiwalk Punkte 1

Nicht Regex, aber ich habe es logisch und nützlich gefunden, serielle Greps mit Pipe zu verwenden, um Störgeräusche zu eliminieren.

Zum Beispiel, suchen Sie eine Apache-Konfigurationsdatei ohne alle Kommentare-

grep -v '\#' /opt/lampp/etc/httpd.conf      # gibt alle Zeilen ohne Kommentar aus

und

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

Die Logik der seriellen Greps ist (kein Kommentar) und (passt zu dir)

2 Stimmen

Ich denke, er fragt nach der Regex-Version des grep -v

9 Stimmen

Das ist gefährlich. Es fehlt auch Zeilen wie gute_Sachen #Kommentar_Sachen

31voto

Casimir et Hippolyte Punkte 85500

Mit diesem Ansatz vermeiden Sie es, einen Ausblick an jeder Position zu testen:

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

entspricht (für .net):

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

Alte Antwort:

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

8 Stimmen

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

0 Stimmen

... oder du kannst einfach alle Quantoren besitzergreifend machen. ;)

0 Stimmen

@Alan Moore - Ich bin auch überrascht. Ich habe deinen Kommentar (und das beste Regex im Haufen) hier gesehen, nur nachdem ich dieses Muster bereits in einer Antwort weiter unten gepostet habe.

30voto

ikegami Punkte 340842

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

^(?:(?!hede).)*$               # Eine Zeile ohne hede

foo(?:(?!hede).)*bar           # foo gefolgt von bar, ohne hede dazwischen

Aber das Folgende würde in diesem Fall ausreichen:

^(?!.*hede)                    # Eine Zeile ohne hede

Diese Vereinfachung ist bereit, "UND"-Klauseln hinzuzufügen:

^(?!.*hede)(?=.*foo)(?=.*bar)   # Eine Zeile mit foo und bar, aber ohne hede
^(?!.*hede)(?=.*foo).*bar       # Das Gleiche

28voto

staafl Punkte 3021

Eine meiner Meinung nach lesbarere Variante der Top-Antwort:

^(?!.*hede)

Grundsätzlich bedeutet dies "Übereinstimmen am Anfang der Zeile, wenn und nur wenn es kein 'hede' darin gibt" - die Anforderung wurde also fast direkt in Regex übersetzt.

Natürlich ist es möglich, mehrere Fehleranforderungen zu haben:

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

Details: Das ^ Anker stellt sicher, dass der Regex-Engine nicht versucht, die Übereinstimmung an jeder Position im String erneut zu finden, was zu einer Übereinstimmung bei jedem String führen würde.

Der ^ Anker am Anfang soll den Anfang der Zeile darstellen. Das grep-Tool überprüft jede Zeile nacheinander, in Kontexten, in denen mit einem mehrzeiligen String gearbeitet wird, kann die "m" Flagge verwendet werden:

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

oder

(?m)^(?!.*hede) # Inline-Flagge

0 Stimmen

Ein Unterschied zur ersten Antwort ist, dass dies nichts entspricht und die gesamte Zeile übereinstimmt, wenn kein "hede" vorhanden ist.

1 Stimmen

@BernardoDalCorno Dies kann leicht geändert werden, indem .* zum Ausdruck hinzugefügt wird: ^(?!.*hede).* Das Ergebnis enthält dann den gesamten Text.

0 Stimmen

Diese Antwort scheint die effizienteste für JavaScript zu sein, da alle anderen Antworten bei sehr großen Eingaben auf "maximale Stapelgröße überschritten" stoßen werden. Diese Antwort verwendet keine Gruppen, nur einen einfachen Ausblick.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X