Reguläre Ausdrücke mit Go Teil 1

Überblick

Reguläre Ausdrücke (AKA-Regex) sind eine formale Sprache, die eine Folge von Zeichen mit einem Muster definiert. In der realen Welt können sie verwendet werden, um eine Menge Probleme mit halbstrukturiertem Text zu lösen. Sie können die wichtigsten Teile aus Text mit vielen Dekorationen oder nicht zusammenhängendem Inhalt extrahieren. Go hat ein starkes Regex-Paket in seiner Standardbibliothek, mit dem Sie Text mit Regex schneiden und würfeln können. 

In dieser zweiteiligen Serie erfahren Sie, was reguläre Ausdrücke sind und wie Sie reguläre Ausdrücke effektiv in Go verwenden können, um viele allgemeine Aufgaben auszuführen. Wenn Sie mit regulären Ausdrücken überhaupt nicht vertraut sind, gibt es viele großartige Tutorials. Hier ist ein guter.

Reguläre Ausdrücke verstehen

Beginnen wir mit einem kurzen Beispiel. Sie haben einen Text und möchten prüfen, ob er eine E-Mail-Adresse enthält. Eine E-Mail-Adresse ist in RFC 822 genau angegeben. Kurz gesagt, sie enthält einen lokalen Teil, gefolgt von einem @ -Zeichen, gefolgt von einer Domäne. Die E-Mail-Adresse wird durch Leerzeichen vom Rest des Textes getrennt. 

Um herauszufinden, ob es eine E-Mail-Adresse enthält, wird der folgende reguläre Ausdruck ausgeführt: ^ \ w + @ \ w + \. \ w + $. Beachten Sie, dass dieser reguläre Ausdruck ein wenig tolerant ist und einige ungültige E-Mail-Adressen durchlässt. Aber es ist gut genug, um das Konzept zu demonstrieren. Versuchen wir es mit einigen potenziellen E-Mail-Adressen, bevor wir erklären, wie es funktioniert:

Paket-Hauptimport ("os" "regexp" "fmt") Funktionsüberprüfung (Fehlerfehler) if err! = nil fmt.Println (err.Error ()) os.Exit (1) E-Mails: = [] Zeichenfolge "brown @ fox", "brown @ fox.", "[email protected]", "br @ own @ fox.com", pattern: = '^ \ w + @ \ w + \ . \ w + $ 'für _, email: = Bereich E-Mails abgeglichen, err: = regexp.Match (Muster, [] Byte (E-Mail)), prüfen, ob er übereinstimmt fmt.Printf ("√'% s 'ist) eine gültige E-Mail \ n ", E-Mail) else fmt.Printf (" X '% s' ist keine gültige E-Mail \ n ", E-Mail) Ausgabe: X 'brown @ fox' ist keine gültige E-Mail X 'brown @ fox.' ist keine gültige E-Mail. √ '[email protected]' ist eine gültige E-Mail. X 'br @ own @ fox.com' ist keine gültige E-Mail

Unser regulärer Ausdruck wirkt auf dieses kleine Muster. Die ersten beiden Adressen wurden abgelehnt, da die Domäne keinen Punkt oder keine Zeichen nach dem Punkt hatte. Die dritte E-Mail wurde korrekt formatiert. Der letzte Kandidat hatte zwei @ -Symbole.

Brechen wir diese Regex ab: ^ \ w + @ \ w + \. \ w + $

Zeichen / Symbol Bedeutung
^ Beginn des Zieltextes
\ w Beliebige Wortzeichen [0-9A-Za-z_]
+ Mindestens eines der vorherigen Zeichen
@ Wörtlich das @ -Zeichen 
\. Das wörtliche Punktzeichen. Muss mit \ entkommen sein
$ Ende des Zieltextes

Insgesamt passt diese Regex zu Textstücken, die mit einem oder mehreren Wortzeichen beginnen, gefolgt von dem Zeichen "@", gefolgt von einem oder mehreren Wortzeichen, gefolgt von einem Punkt und erneut von einem oder mehreren Wortzeichen.  

Umgang mit Sonderzeichen

Die folgenden Zeichen haben in regulären Ausdrücken eine spezielle Bedeutung: .+*? () | [] ^ $ \. Wir haben bereits viele davon im E-Mail-Beispiel gesehen. Wenn wir sie buchstäblich zusammenbringen wollen, müssen wir ihnen mit einem Backslash entkommen. Lassen Sie uns eine kleine Hilfsfunktion namens einführen Spiel() das erspart uns viel tippen. Es nimmt ein Muster und etwas Text, benutzt das regexp.Match () Methode, um das Muster dem Text zuzuordnen (nachdem der Text in ein Byte-Array konvertiert wurde), und die Ergebnisse werden gedruckt:

func match (Musterzeichenfolge, Textzeichenfolge) stimmt überein, _: = regexp.Match (Muster, [] Byte (Text)), wenn es übereinstimmt fmt.Println ("√", Muster, ":", Text) else  fmt.Println ("X", Muster, ":", Text)

Hier ist ein Beispiel für den Vergleich eines regulären Zeichens wie z Vergleich mit einem speziellen Charakter wie ?:

func main () text: = "Kann ich ein Cheezburger sein?" Muster: = "z" Übereinstimmung (Muster, Text) Muster = "\\?" Übereinstimmung (Muster, Text) Muster = '\?' Übereinstimmung (Muster, Text) Ausgabe: √ Z: Kann ich einen Cheezburger nehmen? √ \? : Kann ich cheezburger √ \? : Kann ich cheezburger? 

Das Regex-Muster \? Enthält einen Backslash, der mit einem anderen Backslash maskiert werden muss, wenn er als regulärer Go-String dargestellt wird. Der Grund ist, dass der Backslash auch verwendet wird, um Sonderzeichen in Go-Strings wie newline (\ n). Wenn Sie den umgekehrten Schrägstrich selbst anpassen möchten, benötigen Sie vier Schrägstriche! 

Die Lösung ist die Verwendung von Go-Raw-Strings mit dem Backtick (') statt Anführungszeichen. Natürlich müssen Sie, wenn Sie den Zeilenumbruch anordnen möchten, zu regulären Zeichenfolgen zurückkehren und mit mehreren umgekehrten Schrägstrichen umgehen.

Platzhalter und Wiederholungen

In den meisten Fällen versuchen Sie nicht, eine Folge bestimmter Zeichen wie "abc" buchstäblich zuzuordnen, sondern eine Folge unbekannter Länge, in die möglicherweise einige bekannte Zeichen eingefügt wurden. Regexes unterstützen diesen Anwendungsfall mit dem Punkt  . Sonderzeichen, das für jeden beliebigen Charakter steht. Das * Sonderzeichen wiederholt das vorherige Zeichen (oder die Gruppe) null oder mehrmals. Wenn Sie sie kombinieren, wie in .*, Dann passen Sie alles zusammen, weil es einfach null oder mehr Zeichen bedeutet. Das + ist sehr ähnlich *, aber es stimmt mit einem oder mehreren der vorherigen Zeichen oder Gruppen überein. So .+ wird mit nicht leerem Text übereinstimmen.

Grenzen verwenden

Es gibt drei Arten von Grenzen: den Anfang des durch gekennzeichneten Textes ^, das Ende des durch gekennzeichneten Textes $, und die Wortgrenze ist mit gekennzeichnet \ b. Betrachten Sie beispielsweise diesen Text aus dem klassischen Film Die prinzessin braut: "Ich heiße Inigo Montoya. Sie haben meinen Vater getötet. Machen Sie sich bereit zu sterben." Wenn Sie nur mit "Vater" übereinstimmen, erhalten Sie eine Übereinstimmung, aber wenn Sie am Ende des Textes nach "Vater" suchen, müssen Sie das hinzufügen $ Zeichen, und dann wird es keine Übereinstimmung geben. Auf der anderen Seite funktioniert das Übereinstimmen von "Hallo" am Anfang gut.

func main () text: = "Hallo, mein Name ist Inigo Montoya, Sie haben meinen Vater getötet, machen Sie sich bereit zu sterben." Muster: = "Vater" Übereinstimmung (Muster, Text) Muster = "Vater $" Übereinstimmung (Muster, Text) Muster = "^ Hallo" Übereinstimmung (Muster, Text) Ausgabe: √ Vater: Hallo, mein Name ist Inigo Montoya, Du hast meinen Vater getötet, mach dich bereit zu sterben. X father $: Hallo, mein Name ist Inigo Montoya, du hast meinen Vater getötet und willst sterben. √ Hallo: Mein Name ist Inigo Montoya. Sie haben meinen Vater getötet. 

Wortgrenzen betrachten jedes Wort. Sie können ein Muster mit der Taste starten und / oder beenden \ b. Beachten Sie, dass Interpunktionszeichen wie Kommas als Begrenzung angesehen werden nicht Teil des Wortes. Hier einige Beispiele:

func main () text: = 'Hallo, mein Name ist Inigo Montoya, Sie haben meinen Vater getötet, machen Sie sich bereit zu sterben.' pattern: = 'kill' Übereinstimmung (Muster, Text) pattern = '\ bkill' Übereinstimmung (Muster, Text) pattern = 'kill \ b' Übereinstimmung (Muster, Text) pattern = '\ bkill \ b' Übereinstimmung (Muster, Text ) pattern = '\ bkilled \ b' match (Muster, Text) pattern = '\ bMontoya, \ b' match (Muster, Text) Ausgabe: √ kill: Hallo, mein Name ist Inigo Montoya, du hast meinen Vater getötet sterben. √ \ bkill: Hallo, ich heiße Inigo Montoya, Sie haben meinen Vater getötet und sich auf den Tod vorbereitet. X kill \ b: Hallo, mein Name ist Inigo Montoya, du hast meinen Vater getötet und willst sterben. X \ bkill \ b: Hallo, mein Name ist Inigo Montoya, Sie haben meinen Vater getötet und sich auf den Tod vorbereitet. √ \ bkilled \ b: Hallo, mein Name ist Inigo Montoya, Sie haben meinen Vater getötet und sich auf den Tod vorbereitet. X \ bMontoya, \ b: Hallo, mein Name ist Inigo Montoya, Sie haben meinen Vater getötet und sich auf den Tod vorbereitet.

Klassen verwenden

Es ist oft nützlich, alle Gruppen von Zeichen zusammen wie alle Ziffern, Leerzeichen oder alle alphanumerischen Zeichen zu behandeln. Golang unterstützt die POSIX-Klassen:

Zeichenklasse Bedeutung
[: alnum:]
alphanumerisch (≡ [0-9A-Za-z])
[:Alpha:]
alphabetisch (≡ [A-Za-z])
[:ASCII:] 
ASCII (≡ [\ x00- \ x7F])
[:leer:] 
leer (≡ [\ t])
[: cntrl:]
Steuerung (≡ [\ x00- \ x1F \ x7F])
[:Ziffer:]
Ziffern (≡ [0-9])
[:Graph:]
grafisch (≡ [! - ~] == [A-Za-z0-9! "# $% & '() * +, \ -. / :;<=>?@ [\\\] ^ _ '| ~])
[:niedriger:] 
Kleinbuchstaben (≡ [a-z])
[:drucken:] 
druckbar (≡ [- ~] == [[: graph:]])
[: punct:]
Interpunktion (≡ [! - /: - @ [- '- ~])
[:Platz:]
Leerzeichen (≡ [\ t \ n \ v \ f \ r])
[:Oberer, höher:]
Großbuchstaben (≡ [A-Z])
[:Wort:]
Wortzeichen (≡ [0-9A-Za-z_])
[: xdigit:]
Hexadezimalziffer (≡ [0-9A-Fa-f])

Im folgenden Beispiel verwende ich die [:Ziffer:] Klasse, um im Text nach Zahlen zu suchen. Außerdem zeige ich hier, wie Sie nach einer genauen Anzahl von Zeichen suchen, indem Sie die angeforderte Anzahl in geschweiften Klammern hinzufügen.

func main () text: = 'Die Antwort auf das Leben, das Universum und alles ist 42. "pattern: =" [[: digit:]] 3 "stimmt überein (pattern, text) pattern =" [[: digit: ]] 2 "Übereinstimmung (Muster, Text) Ausgabe: X [[: Ziffer:]] 3: Die Antwort auf Leben, Universum und alles ist 42. √ [[: Ziffer:]] 2: Die Antwort auf das Leben, das Universum und alles ist 42. 

Sie können auch eigene Klassen definieren, indem Sie die Zeichen in eckige Klammern setzen. Wenn Sie beispielsweise prüfen möchten, ob ein Text eine gültige DNA-Sequenz ist, die nur die Zeichen enthält ACGT dann verwenden Sie die ^ [ACGT] * $ Regex:

func main () text: = "AGGCGTTGGGAACGTT" Muster: = "^ [ACGT] * $" Übereinstimmung (Muster, Text) Text = "Nicht genau eine DNA-Sequenz" Übereinstimmung (Muster, Text) Ausgabe: √ ^ [ACGT ] * $: AGGCGTTGGGAACGTT X ^ [ACGT] * $: Nicht genau eine DNA-Sequenz

Verwenden von Alternativen

In einigen Fällen gibt es mehrere realisierbare Alternativen. Übereinstimmende HTTP-URLs können durch ein Protokollschema charakterisiert werden http: // oder https: //. Der Pfeifencharakter | können Sie zwischen Alternativen wählen. Hier ist eine Regex, die sie klären wird: (http) | (https): // \ w + \. \ w 2,. Es wird in eine Zeichenfolge übersetzt, die mit beginnt http: // oder https: // gefolgt von mindestens einem Wortzeichen, gefolgt von einem Punkt, gefolgt von mindestens zwei Wortzeichen.

func main () pattern: = '(http) | (https): // \ w + \. \ w 2,' stimmt überein (pattern, "http://tutsplus.com"), match (pattern, "https") : //tutsplus.com ") Übereinstimmung (Muster," htt: //tutsplus.com ") Ausgabe: √ (http) | (https): // \ w + \. \ w 2,: http: / /tutsplus.com √ (http) | (https): // \ w + \. \ w 2,: https://tutsplus.com X (http) | (https): // \ w + \. \ w 2,: htt: //tutsplus.com

Fazit

In diesem Teil des Tutorials haben wir eine Menge Grundlegendes besprochen und viel über reguläre Ausdrücke mit praktischen Beispielen mit der Golang-Regexp-Bibliothek gelernt. Wir konzentrierten uns auf das reine Matching und darauf, wie wir unsere Absichten mit regulären Ausdrücken ausdrücken. 

In Teil zwei werden wir uns auf die Verwendung regulärer Ausdrücke für die Arbeit mit Text konzentrieren, einschließlich unscharfes Finden, Ersetzen, Gruppieren und Umgang mit neuen Zeilen.