Regelmessige uttrykk (AKA regex) er et formelt språk som definerer en sekvens av tegn med noe mønster. I den virkelige verden kan de brukes til å løse mange problemer med halvstrukturert tekst. Du kan trekke ut viktige biter og biter fra tekst med mange dekorasjoner eller ikke-relatert innhold. Go har en sterk regex-pakke i sitt standardbibliotek som lar deg skille og telle tekst med regexes.
I denne todelte serien lærer du hva vanlige uttrykk er, og hvordan du bruker vanlige uttrykk effektivt i Go for å utføre mange vanlige oppgaver. Hvis du ikke er kjent med regelmessige uttrykk i det hele tatt, er det mange gode opplæringsprogrammer. Her er en god en.
La oss starte med et raskt eksempel. Du har litt tekst, og du vil sjekke om den inneholder en e-postadresse. En e-postadresse er angitt strenge i RFC 822. Kort sagt, den har en lokal del etterfulgt av et @ symbol etterfulgt av et domene. E-postadressen vil bli skilt fra resten av teksten etter plass.
For å finne ut om det inneholder en e-postadresse, vil følgende regex gjøre: ^ \ W + @ \ w + \. \ W + $
. Merk at denne regexen er litt permissiv og vil tillate noen ugyldige e-postadresser gjennom. Men det er godt nok til å demonstrere konseptet. La oss prøve det på et par potensielle e-postadresser før du forklarer hvordan det fungerer:
pakke main import ("os" "regexp" "fmt") func sjekk (feil feil) hvis err! = null fmt.Println (err.Error ()) os.Exit (1) func main e-post: = [] streng "brown @ Fox", "brown @ Fox", "[email protected]", "br @ own @ fox.com", mønster: = '^ \ w + @ \ w + . \ w + $ 'for _, email: = rekkevidde e-postadresser matchet, err: = regexp.Match (mønster, [] byte (email)) sjekk (feil) hvis matchet fmt.Printf ("√'% s 'er en gyldig epost \ n ", e-post) annet fmt.Printf (" X '% s' er ikke en gyldig epost \ n ", epost) Output: X 'brown @ Fox' er ikke en gyldig e- 'brun @ reven.' er ikke en gyldig epost √ '[email protected]' er en gyldig e-post X 'br @ own @ fox.com' er ikke en gyldig epost
Vårt vanlige uttrykk fungerer på denne lille prøven. De to første adressene ble avvist fordi domenet ikke hadde en prikk eller ikke hadde noen tegn etter prikken. Den tredje e-posten ble formatert riktig. Den siste kandidaten hadde to @ symboler.
La oss bryte denne regex ned: ^ \ W + @ \ w + \. \ W + $
Tegn / symbol | Betydning |
---|---|
^ | Begynnelsen av målteksten |
\ w | Eventuelle ordkarakterer [0-9A-Za-z_] |
+ | Minst en av de forrige tegnene |
@ | Bokstavelig talt @ tegn |
\. | Den bokstaverte karakteren. Må være rømt med \ |
$ | Slutt på måltekst |
Samlet sett vil denne regexen samsvare med tekststykker som starter med ett eller flere ordkarakterer, etterfulgt av "@" -karakteren, etterfulgt av et eller flere ordtegn, etterfulgt av en prikk og etterfulgt av enda en eller flere ordtegn.
Følgende tegn har spesielle betydninger i regulære uttrykk: .+? * () | [] ^ $ \
. Vi har allerede sett mange av dem i e-posteksemplet. Hvis vi ønsker å matche dem bokstavelig talt, må vi unnslippe dem med et tilbakeslag. La oss introdusere en liten hjelpefunksjon kalt kamp()
Det vil spare oss mye for å skrive. Det tar et mønster og litt tekst, bruker regexp.Match ()
metode for å matche mønsteret til teksten (etter konvertering av teksten til et byte-array), og skriver ut resultatene:
func match (mønsterstreng, tekststreng) matchet, _: = regexp.Match (mønster, [] byte (tekst)) hvis matchet fmt.Println ("√", mønster, ":", tekst) annet fmt.Println ("X", mønster, ":", tekst)
Her er et eksempel på å matche en vanlig karakter som z
vs. matching et spesielt tegn som ?
:
func main () text: = "Kan jeg haz cheezburger?" mønster: = "z" match (mønster, tekst) mønster = "\\?" match (mønster, tekst) mønster = '\?' match (mønster, tekst) Utgang: √ z: Kan jeg hakke cheezburger? √ \? : Kan jeg ha cheezburger? √ \? : Kan jeg fare med cheezburger?
Den regex mønster \?
inneholder en tilbakeslag som må unngås med en annen tilbakeslag når den representeres som en vanlig Go-streng. Årsaken er at backslash også brukes til å unnslippe spesialtegn i Go strenger som newline (\ n
). Hvis du ønsker å matche selve tilbakestrekstegnet, trenger du fire skråstreker!
Løsningen er å bruke Go råstrenge med backtick ('
) i stedet for doble anførselstegn. Selvfølgelig, hvis du vil matche newline-karakteren, må du gå tilbake til vanlige strenger og håndtere flere backslash-rømming.
I de fleste tilfeller forsøker du ikke å bokstavelig talt matche en sekvens av spesifikke tegn som "abc", men en sekvens av ukjent lengde med kanskje noen kjente tegn injisert et sted. Regexes støtter dette brukstilfellet med prikken .
spesialtegn som står for hvilken som helst karakter. De *
spesialtegn gjentar det forrige tegnet (eller gruppen) null eller flere ganger. Hvis du kombinerer dem, som i .*
, da samsvarer du med noe fordi det bare betyr null eller flere tegn. De +
er veldig lik *
, men den samsvarer med en eller flere av de forrige tegnene eller gruppene. Så .+
vil matche enhver ikke-tom tekst.
Det er tre typer grenser: starten på teksten betegnet av ^
, slutten av teksten betegnet av $
, og ordet grense betegnet av \ b
. For eksempel, vurder denne teksten fra den klassiske filmen The Princess Bride: "Mitt navn er Inigo Montoya. Du drepte min far. Forbered deg på å dø." Hvis du matcher bare "far", får du en kamp, men hvis du leter etter "far" på slutten av teksten, må du legge til $
tegn, og så blir det ingen kamp. På den annen side fungerer "Hello" i begynnelsen godt.
func main () text: = "Hei, jeg heter Inigo Montoya, du drepte min far, forbereder deg til å dø." mønster: = "far" match (mønster, tekst) mønster = "far $" match (mønster, tekst) mønster = "^ Hei" match (mønster, tekst) Utgang: √ far: Hei, jeg heter Inigo Montoya, Du drepte min far, forberede deg på å dø. X far $: Hei, jeg heter Inigo Montoya, du drepte min far, forberede deg på å dø. √ ^ Hei: Hei, jeg heter Inigo Montoya, du drepte min far, forberede deg på å dø.
Ordgrenser ser på hvert ord. Du kan starte og / eller avslutte et mønster med \ b
. Merk at skilletegn som komma er betraktet som en grense og ikke en del av ordet. Her er noen eksempler:
func main () text: = 'Hei, jeg heter Inigo Montoya, du drepte min far, forbered deg på å dø.' mønster: = 'kill' match (mønster, tekst) pattern = '\ bkill' match (mønster, tekst) mønster = 'kill \ b' match (mønster, tekst) mønster = '\ bkill \ b' match ) mønster = '\ bkilled \ b' match (mønster, tekst) mønster = '\ bMontoya, \ b' match (mønster, tekst) Utgang: √ drepe: Hei, jeg heter Inigo Montoya, du drepte min far, forberede å dø. √ \ bkill: Hei, jeg heter Inigo Montoya, du drepte min far, forberede deg på å dø. X drepe \ b: Hei, jeg heter Inigo Montoya, du drepte min far, forberede deg på å dø. X \ bkill \ b: Hei, jeg heter Inigo Montoya, du drepte min far, forberede deg på å dø. √ \ bkilled \ b: Hei, jeg heter Inigo Montoya, du drepte min far, forberede deg på å dø. X \ bMontoya, \ b: Hei, jeg heter Inigo Montoya, du drepte min far, forberede deg på å dø.
Det er ofte nyttig å behandle alle grupper av tegn sammen som alle sifre, hvite mellomrom eller alle alfanumeriske tegn. Golang støtter POSIX-klassene, som er:
Karakterklasse | Betydning |
---|---|
[: Alnum:] | alfanumerisk (≡ [0-9A-Za-z]) |
[: A:] | alfabetisk (≡ [A-Za-z]) |
[: ASCII:] | ASCII (≡ [\ x00- \ x7F]) |
[:blank:] | tomt (≡ [\ t]) |
[: CNTRL:] | kontroll (≡ [\ x00- \ x1F \ x7F]) |
[: Siffer:] | sifre (≡ [0-9]) |
[:kurve:] | grafisk (≡ [! - ~] == [A-Za-z0-9! "# $% & '() * +, \ -. / :;<=>?@ [\\\] ^ _ '| ~]) |
[:Nedre:] | små bokstaver (≡ [a-z]) |
[:skrive ut:] | utskrivbar (≡ [- ~] == [[: graph:]]) |
[: Punct:] | tegnsetting (≡ [! - /: - @ [- '- ~]) |
[:rom:] | whitespace (≡ [\ t \ n \ v \ f \ r]) |
[:øverste:] | store bokstaver (≡ [A-Z]) |
[:ord:] | ord tegn (≡ [0-9A-Za-z_]) |
[: Xdigit:] | heksesiffer (≡ [0-9A-Fa-f]) |
I det følgende eksemplet bruker jeg [: Siffer:]
klassen for å se etter tall i teksten. Også, jeg viser her hvordan du søker etter et eksakt antall tegn ved å legge til det forespurte nummeret i krøllete bånd.
func main () text: = 'Svaret på livet, universet og alt er 42. "mønster: =" [[: siffer:]] 3 "match (mønster, tekst) mønster =" [ ]] 2 "match (mønster, tekst) Utgang: X [[: siffer:]] 3: Svaret på livet, universet og alt er 42. √ [[: siffer:]] 2: Svaret på livet, universet og alt er 42.
Du kan også definere dine egne klasser ved å sette tegn i firkantede parenteser. For eksempel, hvis du vil sjekke om noen tekst er en gyldig DNA-sekvens som bare inneholder tegnene ACGT
bruk deretter ^ [ACGT] * $
regex:
Funk main () text: = "AGGCGTTGGGAACGTT" mønster: = "^ [ACGT] * $" match (mønster, tekst) text = "Ikke akkurat en DNA-sekvens" ] * $: AGGCGTTGGGAACGTT X ^ [ACGT] * $: Ikke akkurat en DNA-sekvens
I noen tilfeller er det flere levedyktige alternativer. Matchende HTTP-nettadresser kan være preget av et protokollskjema, som enten er http: //
eller https: //
. Rørkarakteren |
lar deg velge mellom alternativer. Her er en regex som vil sortere dem ut: (Http) | (https).: // \ w + \ \ w 2
. Det oversetter til en streng som starter med http: //
eller https: //
etterfulgt av minst ett ord karakter etterfulgt av en prikk etterfulgt av minst to ord tegn.
func main () mønster: = '(http) | (https): // \ w + \. \ w 2 match (mønster, "http://tutsplus.com") : //tutsplus.com ") match (mønster," htt: //tutsplus.com ") Utgang: √ (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
I denne delen av opplæringen dekket vi mye bakken og lærte mye om vanlige uttrykk, med praktiske eksempler ved hjelp av Golang regexp biblioteket. Vi fokuserte på ren matchning og hvordan vi uttrykte våre intensjoner ved å bruke vanlige uttrykk.
I del to vil vi fokusere på å bruke vanlige uttrykk for å arbeide med tekst, inkludert fuzzy finne, erstatninger, gruppering og håndtering av nye linjer.