Reguliere expressies met Go deel 1

Overzicht

Reguliere expressies (AKA-regex) zijn een formele taal die een reeks tekens met een bepaald patroon definieert. In de echte wereld kunnen ze worden gebruikt om veel problemen met semi-gestructureerde tekst op te lossen. U kunt de belangrijke stukjes en beetjes uit tekst halen met veel versieringen of niet-gerelateerde inhoud. Go heeft een sterk regex-pakket in zijn standaardbibliotheek waarmee je tekst kunt knippen en delen met regexes. 

In deze tweedelige serie leert u wat reguliere uitdrukkingen zijn en hoe u reguliere expressies effectief kunt gebruiken in Ga om veel algemene taken te volbrengen. Als je helemaal niet bekend bent met reguliere expressies, zijn er veel geweldige tutorials. Hier is een goede.

Reguliere expressies begrijpen

Laten we beginnen met een snel voorbeeld. Je hebt wat tekst en je wilt controleren of het een e-mailadres bevat. Een e-mailadres is strikt vastgelegd in RFC 822. Kortom, het heeft een lokaal gedeelte gevolgd door een @ -symbool gevolgd door een domein. Het e-mailadres wordt per spatie van de rest van de tekst gescheiden. 

Om erachter te komen of het een e-mailadres bevat, doet de volgende regex het volgende: ^ \ W + @ \ w + \. \ W + $. Merk op dat deze regex een beetje permissief is en enkele ongeldige e-mailadressen doorlaat. Maar het is goed genoeg om het concept te demonstreren. Laten we het proberen op een aantal potentiële e-mailadressen voordat we uitleggen hoe het werkt:

pakket main import ("os" "regexp" "fmt") func check (err error) if err! = nil fmt.Println (err.Error ()) os.Exit (1) func main ()  e-mails: = [] string "brown @ fox", "brown @ fox.", "[email protected]", "br @ own @ fox.com", pattern: = '^ \ w + @ \ w + \ . \ w + $ 'voor _, email: = bereik e-mails matched, err: = regexp.Match (pattern, [] byte (email)) check (err) als matched fmt.Printf ("√'% s 'is een geldige email \ n ", email) else fmt.Printf (" X '% s' is geen geldige email \ n ", email) Uitgang: X 'brown @ fox' is geen geldige email X 'bruine @ vos.' is geen geldige e-mail √ '[email protected]' is een geldige e-mail X 'br @ own @ fox.com' is geen geldige e-mail

Onze reguliere expressie werkt op dit kleine monster. De eerste twee adressen zijn geweigerd omdat het domein geen punt had of geen tekens na de punt had. Het derde e-mailadres was correct geformatteerd. De laatste kandidaat had twee @ -symbolen.

Laten we deze regex naar beneden halen: ^ \ W + @ \ w + \. \ W + $

Karakter / Symbol Betekenis
^ Begin van de doeltekst
\ w Alle woordtekens [0-9A-Za-z_]
+ Tenminste één van de vorige karakters
@ Letterlijk het @ -teken 
\. Het letterlijke puntkarakter. Moet worden ontsnapt met \
$ Einde van doeltekst

In totaal komt deze regex overeen met stukjes tekst die beginnen met een of meer woordtekens, gevolgd door het teken "@", gevolgd door een of meer woordtekens, gevolgd door een punt gevolgd door nog een of meer woordtekens.  

Omgaan met speciale karakters

De volgende tekens hebben een speciale betekenis in reguliere expressies: .+? * () | [] ^ $ \. We hebben al veel van hen gezien in het voorbeeld van de e-mail. Als we ze letterlijk willen matchen, moeten we eraan ontsnappen met een backslash. Laten we een kleine helperfunctie introduceren genaamd wedstrijd() dat zal ons veel typen besparen. Het neemt een patroon en wat tekst, gebruikt de regexp.Match () methode om het patroon aan de tekst aan te passen (na het converteren van de tekst naar een byte-array) en de resultaten af ​​te drukken:

func match (pattern string, text string) matched, _: = regexp.Match (pattern, [] byte (text)) if matched fmt.Println ("√", pattern, ":", text) else  fmt.Println ("X", patroon, ":", tekst)

Hier is een voorbeeld van het matchen van een regulier karakter zoals z versus het matchen van een speciaal personage zoals ?:

func main () text: = "Can I haz cheezburger?" pattern: = "z" match (pattern, text) pattern = "\\?" overeenkomen (patroon, tekst) patroon = '\?' match (patroon, tekst) Uitgang: √ z: Kan ik haz cheezburger? √ \? : Kan ik haz cheezburger? √ \? : Kan ik haz cheezburger? 

Het regex-patroon \? bevat een backslash die moet worden geëscaped met een andere backslash wanneer deze wordt weergegeven als een normale Go-reeks. De reden is dat backslash ook wordt gebruikt om te ontsnappen aan speciale tekens in Go-strings zoals een nieuwe regel (\ n). Als u het backslash-teken zelf wilt koppelen, heeft u vier schuine strepen nodig! 

De oplossing is om onbewerkte reeksen Go te gebruiken met de backtick (') in plaats van dubbele aanhalingstekens. Natuurlijk, als u het nieuwe lijnteken wilt matchen, moet u teruggaan naar reguliere strings en omgaan met meerdere backslash-escapes.

Placeholders and Repetitions

In de meeste gevallen probeer je niet letterlijk een reeks specifieke karakters zoals "abc" te matchen, maar een reeks van onbekende lengte met misschien enkele bekende personages die ergens zijn geïnjecteerd. Regexes ondersteunen deze gebruikscasus met de punt  . speciaal personage dat staat voor welk karakter dan ook. De * speciaal teken herhaalt het vorige teken (of groep) nul of meer keer. Als je ze combineert, zoals in .*, dan stem je alles af omdat het simpelweg nul of meer karakters betekent. De + lijkt erg op *, maar het komt overeen met een of meer van de vorige tekens of groepen. Zo .+ komt overeen met niet-lege tekst.

Grenzen gebruiken

Er zijn drie soorten grenzen: het begin van de tekst aangeduid met ^, het einde van de tekst aangegeven met $, en de woordgrens aangeduid met \ b. Overweeg deze tekst bijvoorbeeld uit de klassieke film De prinsessenbruid: "Mijn naam is Inigo Montoya, je hebt mijn vader gedood en je bent klaar om te sterven." Als u alleen 'vader' vergelijkt, krijgt u een overeenkomst, maar als u 'vader' aan het einde van de tekst zoekt, moet u de $ karakter, en dan is er geen match. Aan de andere kant werkt het matchen van "Hallo" aan het begin goed.

func main () text: = "Hallo, mijn naam is Inigo Montoya, jij hebt mijn vader gedood, bereid je voor om te sterven." pattern: = "vader" match (patroon, tekst) pattern = "vader $" match (patroon, tekst) pattern = "^ Hello" match (patroon, tekst) Output: √ vader: Hallo, mijn naam is Inigo Montoya, je hebt mijn vader vermoord, bereid je voor om te sterven. X vader $: Hallo, mijn naam is Inigo Montoya, je hebt mijn vader vermoord, bereid je voor om te sterven. √ ^ Hallo, mijn naam is Inigo Montoya, jij hebt mijn vader vermoord, bereid je voor om te sterven. 

Woordgrenzen kijken naar elk woord. U kunt een patroon beginnen en / of beëindigen met de \ b. Merk op dat leestekens zoals komma's worden beschouwd als een grens en niet deel van het woord. Hier zijn een paar voorbeelden:

func main () text: = 'Hallo, mijn naam is Inigo Montoya, je hebt mijn vader gedood, bereid je voor om te sterven.' pattern: = 'kill' match (pattern, text) pattern = '\ bkill' match (pattern, text) pattern = 'kill \ b' match (patroon, tekst) pattern = '\ bkill \ b' match (patroon, tekst ) pattern = '\ bkilled \ b' match (pattern, text) pattern = '\ bMontoya, \ b' match (pattern, text) Output: √ kill: Hallo, mijn naam is Inigo Montoya, jij hebt mijn vader vermoord, bereid sterven. √ \ bkill: Hallo, mijn naam is Inigo Montoya, jij hebt mijn vader vermoord, bereid je voor om te sterven. X kill \ b: Hallo, mijn naam is Inigo Montoya, jij hebt mijn vader vermoord, bereid je voor om te sterven. X \ bkill \ b: Hallo, mijn naam is Inigo Montoya, jij hebt mijn vader vermoord, bereid je voor om te sterven. √ \ bkilled \ b: Hallo, mijn naam is Inigo Montoya, je hebt mijn vader vermoord, bereid je voor om te sterven. X \ bMontoya, \ b: Hallo, mijn naam is Inigo Montoya, jij hebt mijn vader vermoord, bereid je voor om te sterven.

Classes gebruiken

Het is vaak handig om alle groepen tekens samen te behandelen, zoals alle cijfers, witruimtetekens of alle alfanumerieke tekens. Golang ondersteunt de POSIX-klassen, die zijn:

Tekenklasse Betekenis
[: Alnum:]
alfanumeriek (≡ [0-9A-Za-z])
[: Alpha:]
alfabetisch (≡ [A-Za-z])
[: ASCII:] 
ASCII (≡ [\ x00- \ x7F])
[:blanco:] 
leeg (≡ [\ t])
[: Cntrl:]
controle (≡ [\ x00- \ x1F \ x7F])
[:cijfer:]
cijfers (≡ [0-9])
[: Grafiek:]
grafisch (≡ [! - ~] == [A-Za-z0-9! "# $% & '() * +, \ -. / :;<=>?@ [\\\] ^ _ '| ~])
[:lager:] 
kleine letters (≡ [a-z])
[:afdrukken:] 
afdrukbaar (≡ [- ~] == [[: grafiek:]])
[: Punct:]
interpunctie (≡ [! - /: - @ [- '- ~])
[:ruimte:]
witruimte (≡ [\ t \ n \ v \ f \ r])
[:bovenste:]
hoofdletters (≡ [A-Z])
[:woord:]
woordtekens (≡ [0-9A-Za-z_])
[: Xdigit:]
hexadecimaal cijfer (≡ [0-9A-Fa-f])

In het volgende voorbeeld gebruik ik de [:cijfer:] klasse om nummers in de tekst te zoeken. Ook laat ik hier zien hoe je naar een exact aantal tekens kunt zoeken door het gevraagde aantal tussen accolades toe te voegen.

func main () text: = 'Het antwoord op het leven, universum en alles is 42. "pattern: =" [[: digit:]] 3 "match (pattern, text) pattern =" [[: digit: ]] 2 "match (patroon, tekst) Uitgang: X [[: digit:]] 3: het antwoord op leven, universum en alles is 42. √ [[: digit:]] 2: Het antwoord op het leven, universum en alles is 42. 

U kunt ook uw eigen klassen definiëren door tekens tussen vierkante haken te plaatsen. Als u bijvoorbeeld wilt controleren of een bepaalde tekst een geldige DNA-reeks is die alleen de tekens bevat ACGT gebruik dan de ^ [ACGT] * $ regex:

func main () text: = "AGGCGTTGGGAACGTT" patroon: = "^ [ACGT] * $" match (patroon, tekst) tekst = "Niet echt een DNA-volgorde" -overeenkomst (patroon, tekst) Uitvoer: √ ^ [ACGT ] * $: AGGCGTTGGGAACGTT X ^ [ACGT] * $: niet echt een DNA-volgorde

Alternatieven gebruiken

In sommige gevallen zijn er meerdere haalbare alternatieven. Overeenkomende HTTP-URL's kunnen worden gekenmerkt door een protocolschema, ofwel http: // of https: //. Het pijpkarakter | laat je kiezen tussen alternatieven. Hier is een regex die ze zal sorteren: (Http) | (https). // \ w + \ \ w 2. Het vertaalt zich naar een string die begint met http: // of https: // gevolgd door ten minste één woordteken gevolgd door een punt gevolgd door ten minste twee woordtekens.

func main () pattern: = '(http) | (https): // \ w + \. \ w 2,' match (pattern, "http://tutsplus.com") match (patroon, "https : //tutsplus.com ") overeenkomen (patroon," htt: //tutsplus.com ") Uitvoer: √ (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

Conclusie

In dit deel van de tutorial hebben we veel aandacht besteed aan de reguliere expressies, met praktische voorbeelden van de Golang regexp-bibliotheek. We concentreerden ons op pure matching en hoe we onze intenties tot uitdrukking konden brengen met behulp van reguliere expressies. 

In deel twee zullen we ons concentreren op het gebruik van reguliere expressies om met tekst te werken, inclusief fuzzy finding, vervangingen, groeperen en omgaan met nieuwe regels.