6
Tags:
diverse
Skrevet af
Bruger #345
@ 26.08.2008
Introduktion og formål
Artiklens formål er at give jer en, forhåbentligt, blid men lærerig introduktion til regular expressions (regex). Da der findes forskellige dialekter af regex, som har mindre eller større variationer, har jeg valgt at denne artikels fokus skal være på Perls regex som nok er den mest udbredte og kendte dialekt. Da det ikke skal være helt kedeligt har jeg valgt at vi skal kigge på hvordan en email validering i regex kan tage sig ud. Og husk, det her er ment som en introduktion, så koncepterne er ret simple, men de virker. Jeg viser til sidst hvordan det bruges i .NET (sproget bliver C#, men er ellers ens for alle .NET sprog), PHP, Perl, Python, Ruby og JavaScript. Det regex udtryk vi skal kigge på er det her:
^\w[\w.-]*@(\w[\w-]*\.)+[a-z]{2,4}$
Inden vi går igang ser jeg mig nødsaget til at have en disclaimer:
Jeg godt er klar over denne validering ikke stemmer helt overens med RFC (2)882, men med hvad der generelt bliver accepteret som en gyldig email adresse. Normalt er emailudbydere langt mere striks med hvad de accepterer som en gyldig email end førnævnte RFC specificerer. Og så er jeg godt klar over at f.eks. Perl i forvejen har libraries der kan gøre validere en email (og endda efter RFC specifikationer), men formålet med denne artikel er ikke at lave en helt korrekt email validering, men at lære regex. Desuden har jeg testet samtlige eksempler i bunden, så de skulle meget gerne virke. Med det sagt, lad os så komme igang!
Atomic Zero-width Assertions
Den kryptiske overskrift dækker i dette tilfælde over det første og sidste tegn i vores regex, nemlig
^ og
$. Her skal det siges at overskriften nok er lidt misvisende da det kun er .NET's dokumentation der bruger det navn. Der er ingen standard navngivning for dem, hver implementation lader til at bruge deres egne navne (Perl f.eks. kalder dem metakarakterer), så her valgte jeg at bruge .NET navnet. Nok om det lille sidespring, tilbage til artiklen. De to tegn er ens hvad angår funktionalitet, de fungerer bare i hver sin ende; ^ ser efter at man er i starten af tekst eller linjen, afhængig af multiline indstillingen (jvf. punkt 1 forneden). Det samme gør sig gældende med $, den sikrer sig at man er i enten slutningen af strengen, men før det sidste linjeskift, eller til sidst i linjen, før et eventuel linjeskift.
Character class
Det næste fænomen vi støder på er
\w som kaldes en character class. \w kan være lidt tricky, da hvad den står for, afhænger af om Unicode understøttelse er slået til eller ej (jvf. punkt 2 forneden). Hvis det er ASCII understøttelse som bruges så ser \w nogenlunde sådan her ud; [a-zA-Z_0-9]. Det er den vi har brug for her og en diskussion omkring Unicode udgaven af \w ligger uden for den artikels emne, men er man interesseret kan man slå det op i f.eks. Perl's eller .NET's dokumentation. Det næste vi støder på er endnu en character class, men af en anden type, som indeholder \w plus de to tegn
. og
-. \w opfører sig ligesom før med at matche [a-zA-Z_0-9]. Normalt er . tegnet et specielt tegn i regex, som betyder "alt undtagen linjeskift", men som kan ændres via singleline indstillingen, relateret til multiline nævnt før, så den kommer til at betyde kort og godt "alt". Indeni en character class betyder den dog bare ., altså "punktum" eller "dot". Tegnet - har ingen speciel betydning udenfor en character class, men indenfor kan den angive en sekvens eller række af tegn, som vi har set i \w, f.eks. [a-z] som betyder alle tegn fra a til z, begge inklusiv. Normalt vil der ikke ske noget ved at have placeret den imellem \d og . ([\w\d-.]), men der kan opstå uventede resultater i andre tilfælde, så det er god vane at placere den til sidst. Lige inden vi afslutter vil jeg kigge hvordan man finder noget, når man ved hvad man ikke vil finde, f.eks. hvis man ikke vil have bogstaverne x, y eller z. Så kan man skrive sådan her; [^xyz]. Tegnet ^ gør her at denne character class vil finde alle tegn der ikke er x, y eller z.
Quantifiers
Efter den anden character class er der en asterisk,
*. Dette er en såkaldt quantifier der bestemmer hvor mange gange det forgående tegn, character class eller under-udtryk skal forkomme. Denne quantifier, *, siger at det forgående tegn, character class eller under-udtryk skal forkomme nul eller flere gange. Altså vil vi gerne have at den finder et bogstav, et tal, _, . eller - nul eller flere gange. Når der ikke følger en quantifier efter et tegn (eller character class eller under-udtryk. Jeg tror i er med nu, så jeg siger bare tegn fra nu af) så skal det tegn forkomme præcist én gang. Hvis vi kigget lidt frem i udtrykket finder vi plus tegnet,
+. Dette er ligeledes en quantifier, som er identisk med den forrige, undtagen på et punkt; den siger at det forgående tegn skal forkomme
en eller flere gange. I modsætning til * kræver + altså at tegnet skal forkomme mindst en gang. Quantifiers fås også i en anden udgave hvor man bruger formen {n,m}. F.eks. svarer {0,} til * og {1,} til +. Læg mærke til at jeg har undladt det sidste tal efter kommaet. Det betyder at den skal gentage det forrige tegn så mange gange som overhovedet muligt. Havde jeg unladt kommaet, havde det betydet at den skulle gentage 0 og 1 gang, respektivt. Hvis du kigger godt på udtrykket foroven kan du sikkert godt finde den sidste quantifier; {2,4}. Den kræver at tegnet før den forkommer mindst 2 gange men ikke mere end 4 gange. Den sidste quantifier jeg kort vil diskutere, som vi ikke brugt i vores udtryk, er
?. Den minder lidt om *, men hvor * kan matche så mange tegn den har lyst til, så kræver ? at der maksimum forkommer en instans af tegnet. Så det er en slags enten eller. Der findes også andre typer quantifiers som er varianter af disse, mest kendt er nok lazy quantifiers. De ligger begge udenfor artiklens rækkevidde, men jeg vil vende tilbage til dem i en senere artikel.
Almindelige tegn og escape sequences
Jeg vælger her at dække over to ting da de er nært beslægtet og godt kan diskuteres samlet. Det første er almindelige tegn. Hvis vi kigger på udtrykket kan vi se et @, som bare er et ganske almindeligt tegn, det har ingen speciel betydning i regex. Men som tidligere antydet er det ikke tilfældet med alle tegn, hvor f.eks. har . en helt speciel betydning udenfor en character class. Den er dog ikke unik i denne egenskab så her er en liste over de enkelt tegn der har en speciel egenskab og hvor den opstår:
- .: Udenfor character class
- $: Udenfor character class
- ^: Både inden- og udenfor character class
- [: Både inden- og udenfor character class
- (: Udenfor character class
- ): Udenfor character class
- |: Udenfor character class
- *: Udenfor character class
- +: Udenfor character class
- ?: Udenfor character class
- \: Både inden- og udenfor character class
Det skal dog bemærkes at ^ og [ kun er specielle i en character class under specielle omstændigheder. ^ har kun en speciel betydning hvis den er det første tegn i en character class, som vi så tidligere, hvor den ekskluderede de tegn som man skrev. Ved [ er det anderledes, da den bruges til at lave en slags under-character class, som man kan trække fra; [a-n-[b-d]]. Her laver en man en sekvens fra a til n og trækker bogstaverne fra b til d fra. Så matcher den alle bogstaver fra a til n, undtagen b, c og d. Det sidste tegn som fungerer i en character class er \ som er escape tegnet. Det bruger man til at sætte foran et tegn som man vil fjerne dens specielle betydning fra (der er dog enkelte tilfælde hvor det er omvendt). F.eks. kan man sætte den foran ., som vi har gjort i vores udtryk, for at fjerne den specielle betydning "alt undtagen linjeskift", så nu matcher den kun ".". Dette er den samme opførsel som vi fik hvis vi indkapslede den i en character class men det er ikke altid det er muligt. F.eks. kan tegnet ^ ikke stå alene i en character class, da tegnet bruges til at lave en negativ character class, så her bruges escape tegnet foran, så det bliver til \^. Nu vil den finde tegnet ^ istedet for starten på strengen eller linjen. Nu opstår problemet så, hvad nu hvis man vil finde \? Som de fleste af jer sikkert (forhåbentligt) har regnet ud, så bruger man da selvfølgelig bare endnu en \ foran, sådan her; \\. Den observante læser vil have set at ] ikke er på listen over specielle tegn. Det er fordi at ] ikke har nogen betydning uden at der først har været en [. Hvorfor er dette anderledes for )? Burde den ikke også være tilladt hvis der ikke findes en ( der passer til? Jeg tror dette bunder i at man bruger () til at gruppere under-udtryk og dette meget hurtigt kan blive svært at holde styr på, så det er blevet en fejl at have et forkert forhold af ().
Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.
Del også gerne artiklen med dine Facebook venner:
Kommentarer (3)
Dine regexes virker ikke korrekt. Jeg brugte dem på listen af gyldige og ugyldige adresser fundet på
http://en.wikipedia.org/wiki/E-mail_address og fik en del forkerte svar:
Gyldig bc@example.com
Gyldig Abc@example.com
Gyldig aBC@example.com
Gyldig abc.123@example.com
Gyldig 1234567890@example.com
Gyldig _______@example.com
Ugyldig abc+mailbox/department=shipping@example.com
Ugyldig !#$%&'*+-/=?^_`.{|}~@example.com
Ugyldig "abc@def"@example.com
Ugyldig "Fred \"quota\" Bloggs"@example.com
Ugyldig Abc.example.dk
Gyldig Abc.@example.dk
Gyldig Abc..123@example.dk
Ugyldig A@b@c@example.dk
Ugyldig ()[]\;:,<>@example.dk
Alle .com adresserne er gyldige, mens alle .dk adresserne er ugyldige.
Hvis du så tog dig tid til rent faktisk at læse min disclaimer, så gør jeg opmærksom på lige netop dette. Mine regex her er lavet til at matche hvad generelt er tilladt, ikke hvad RFC 2882 specificere. Hvis jeg skal skrive en regex der matcher alle de ovennævnte blev den også for kompliceret til en introduktions artikel.
Men igen, alt det her har jeg rent faktisk skrevet i starten af artiklen... Hvis du havde taget dig tid til at læse den.
Og nu tog jeg mig så lige tid til at læse wikipedia siden. Det er rigtigt at . til sidst og dobbelt .. er tilladt, men igen, det overkompliciferer regexen lidt med look-around assertions som jeg slet ikke har berørt endnu. Det skal nok komme i en senere artikel, og så skal jeg nok omskrive regexen så den er mere gyldig. Men helt efter RFC specs bliver den altså aldrig, da det er meget sjældent nogen der tillader dem (selv om RFC forskriver det).
Du skal være
logget ind for at skrive en kommentar.