Reguljära Uttryck
Nu tänkte jag skriva lite om något som är otroligt bra att kunna som programmerare, jag kan inte tänka mig att programmera utan kunskap om reguljära uttryck. På Engelska heter det "Regular Expressions" och förkortas till regex så det ska vi använda i resten av artikeln. Regex är inget eget språk utan en metod som finns implementerad i flera olika språk. Min favorit editor på UNIX, NEdit, har syntax highlighting för regex så det kan nästan ses som ett riktigt språk men man får inte glömma att regex behöver ett annat språk som värd och det närmaste man kan komma regex som eget språk är program som är gjorda bara för att utföra regex på strängar, som tex egrep i UNIX.
Jag ska ta upp regex som den är i Perl eftersom jag och flera andra tycker att det är den enda korrekta regex implementationen som de flesta andra språk försöker följa. Ni kan testa era regex på http://swehack.se/pub/regex.html och träna er där. Innan vi börjar måste jag säga att jag gärna vill ta upp så mycket som möjligt men det är omöjligt för mig att dokumentera hela regex i denna ena artikeln men jag vill dock samla allt jag hinner/orkar om det i en artikel och därför ska jag, som med så många andra artiklar, reservera mig för att göra både ändringar och uppdateringar.
Ska börja med att förklara exakt vad ett regex kan vara. Följande exempel är giltig regex och kan användas för att söka och matcha strängar.
/regex/; /RegEx/;
Ni märker att jag använder slash för att börja och avsluta mitt regex, detta är hur man ska göra i perl så att perl tolken vet var dina regex börjar och slutar, vissa språk kräver inte detta som tex PHP. Den första regex raden skulle leta igenom en text sträng efter strängen "regex" i gemener och matcha den medan den andra raden skulle söka efter texten "RegEx" med R och E i versaler, den hade alltså, tex, INTE matchat strängarna "regex", "Regex" eller "REGEX". Nu ska vi dock utöka våra kunskaper om regex lite genom att börja använda metatecken.
Metatecken är speciella tecken i flera olika språk och i regex så är det tecken som används för att utföra funktioner inom regex, som tex att matcha en grupp tecken. Vi ska först titta på lite enklare metatecken. Här har ni några exempel som jag sedan ska förklara.
/[abcdef]/; /[123456]/;
Den första raden hade matchat vilket tecken som helst mellan a och f, alltså alla strängar som utan mellanslag innehåller tecken mellan a till f i alfabetet. Några exempel är då ju "f", "c" eller "ebcda". Som ni ser så kvittar det vilken ordning de kommer i, alla de tecken som fanns i regex raden inom [ och ] matchas ändå. Samma sak gäller för den andra raden men för siffror mellan 1 och 6. När man sätter tecken inom [ ] så definerar man en grupp tecken men man behöver inte skriva precis alla tecken som man vill ha med utan man kan också använda - som för övrigt är ett metatecken. Då kanske ni tror att om man skriver så här /hej [a-z]/; så matchar ni all text som innehåller "hej" ett mellanslag och en sträng med gemener mellan a och z i alfabetet? Detta är INTE sant, när du använder [ och ] så matchar den bara första tecknet som är ett tecken inom den gruppen du har definerat, alltså matchas bara ett tecken. Så här är det med de flesta regex funktioner och därför finns det metatecken som gör att flera tecken ska matchas. Här följer ett exempel som kommer matcha all text där någon skriver "hej" följt av ett mellanslag och en valfri sträng med obestämd längd som innehåller gemener mellan a till z i alfabetet.
/hej [a-z]+/;
Som vanligt börjar vår regex med / och fortsätter med ett fast värde, "hej ", detta kan inte matcha något annat än hej och ett mellanslag efter. Efter det definerar vi ett tecken mellan a och z i alfabetet, precis som vanligt men nu kommer ett nytt metatecken. Ett plus tecken, "+", matchar ett eller fler av det tecken som kommer före. Alltså skulle /a+/; matcha allt från minst ett a till en oändlig mängd. Så strängar som "a", "aaaaaa" och "aa" skulle matcha men inte "a aaa a". Vi kan också använda en asterisk, det hade matchat inga eller fler av de tecknet som kom före. Så om ni vill vara säkra på att ni får med något så ska ni använda + men om det ni letar efter även kan vara inget så kan ni ha en asterisk. Jag använder av vana /.+?/; för att matcha precis vad som helst. En punkt matchar vilket tecken som helst och som sagt så säger + att den ska matcha ett eller fler tecken och det nya frågetecknet säger att den ska sluta matcha vid första träffen. Även om det då finns fler strängar som matchar i den texten ni söker så kommer den sluta leta efter första. Regex skiljer sig lite från språk till språk och bland annat så matchar en punkt inte en nyrad i PHP om ni inte lägger till ett s i slutet. Alltså för att få en punkt att matcha precis allt i PHP så skriver ni "/./s" i någon av de regex funktioner som finns tillgängliga genom PCRE paketet i PHP.
Vi har pratat lite om metatecken och en sak ni måste veta innan ni går vidare är att om man vill leta i en sträng efter metatecken, alltså inte använda deras funktionalitet utan söka efter text som innehåller tex [ och ] eller frågetecken, så måste man göra dom till undantag genom att sätta en backslash framför. Alltså om vi tex vill hitta alla frågetecken i en sträng så skulle vi göra så här /\?/; så används inte ? som ett metatecken längre utan är precis som vilket tecken som helst för att vi satte en backslash framför. I slutet av artikeln ska jag gå igenom alla metatecken i regex och förklara dom så var inte oroliga.
Vi kan självklart göra mer än att bara söka med regex, vi kan också ersätta text och här får man verkligen se styrkan i regex. När vi ersätter så sätter vi ett s framför vår regex eftersom det står för "substitute" som på svenska betyder "ersätt". Ett regex som ersätter kan vara s/hej/yo/i; som skulle söka igenom en textsträng och ersätta precis alla hej med yo. Ni ser att jag satte ett i på slutet, detta betyder att vår regex inte matchar teckenkänsligt(case Insensitive), alltså kommer strängar som "Hej", "hEj" och "HEJ" alla ersättas med yo. Sätter vi inget i på slutet så är regex som standard teckenkänsligt och för att matcha både stora och små tecken måste vi göra något sånt här s/[Hh][Ee][Jj]/yo/i; som kommer matcha alla former av hej. Ni ser i det föregående exemplet att vi definerat grupper av tecken som ska matchas, i detta fallet definerade vi samma tecken i varje grupp men varje tecken har sin versal och respektive gemen. Nu när vi börjat ersättning ska vi lära oss om något som kallas "subexpressions", de används inte bara i ersättnings regex men där spelar de en stor roll i att utnyttja den fulla potentialen hos regex.
Ett subexpression är helt enkelt ett regex inom parenteser, alltså kan vi redan nu gissa oss fram till att parenteser är ännu ett metatecken som jag senare ska ha med i min lista. Den delen av subexpressions vi är intresserade av låter oss ta delar av det vi matchar och sätta det i en sorts variabler. I perl hamnar alla subexpressions du gör automatiskt i en enkel variabel med ett tal som namn, eftersom dom börjar räkna vid 1 och inte vid 0 som i vanliga fall så är det första ni matchar med ett subexpression i variabeln $1, det andra i $2 osv. Perl låter er alltså ta ut det ni matchar med subexpressions och använda det i resten av er kod, fram till nästa gång ni använder subexpressions då era gamla variabler skrivs över med nya värden.
Nu ska vi utveckla vår regex som ersätter hej med yo lite. Ni kan tänka er att vi håller på med en ircbot som ska hälsa på folk eller svara med yo när dom hälsar på den med hej, det kan vara precis vad som helst, det är bara ett exempel. Vi vill ju att när någon säger hej så ska vi få med oss deras namn och säga hej tillbaka med deras namn i hälsningen. Då tar vi först och kollar på hur det ser ut när du tar emot ett hej i din IRC klient.
:noc[jobb]!~nocturnal@dogbert.swehack.se PRIVMSG #shbot :hej shbot
Detta är den exakta texten som skickas till din IRC klient när någon som heter noc[jobb] i kanalen #shbot säger hej. Eftersom regex läser en sträng från vänster till höger så ska vi först lista ut hur vi ska få ur namnet på personen som säger hej. Nu kommer vi få användning av subexppressions. För att matcha allt mellan : och ! så skriver vi /:.+?!/; men vi vill ju ta med oss det som är mellan : och ! in i en variabel eller i ett ersättningsregex. Så vi skriver /:(.+?)!/; och nu har vi gjort det, vi har nu i Perl lagt namnet på personen som hälsade på oss i en variabel som heter $1. Som ni ser så måste man enligt IRC protokollet skriva PRIVMSG när man ska skicka ett meddelande till en kanal, det är precis likadant om man skickar ett privat meddelande till en person men man har personens IRC-namn istället för kanalens namn efter PRIVMSG. Nu ska vi ersätta den rad vi får från servern med vår egen. Vi skriver nu som i mitt exempel nedan.
s/:(.+?)!.+? PRIVMSG (#(.+?)) :hej shbot/PRIVMSG $2 :yo $1/i;
Nu har vi utvecklat oss lite och om ni inte hängt med så kanske detta ser skrämmande ut men ni får helt enkelt tänka logiskt och minnas allt det jag sagt så kommer allt falla på plats. Först av allt måste jag säga att om allt som är efter den första slashen och före den andra inte matchar i den strängen som vi söker igenom så kommer inget av detta som vi skrivit ha någon betydelse. Nu matchar vi först :noc[jobb]! och noc[jobb] sätter vi i en variabel som heter $1 eftersom det är vårt första subexpression. Sedan skriver jag ".+? " för att matcha "~nocturnal@dogbert.swehack.se " som avslutas med ett mellanslag och är därför väldigt enkelt att matcha, även om det skulle finnas ett mellanslag mitt i så hade det fungerat eftersom PRIVMSG följer den texten och den matchar jag ju också. Efter PRIVMSG matchas kanalen som personen sa hej i, nu ser ni att jag har ett subexpression i ett annat, detta går att göra på mycket bättre sätt men jag ska låta er lista ut dom själva. Om ni undrar vilket subexpression som är först av två som är i varandra så är svaret, det som börjar först, alltså i detta fallet det yttersta. Här är ett litet exempel (detta blir $1(detta blir $2 men inkluderas i $1)).
Vi kan också matcha # som är framför kanalens namn endast om det finns där med lookbehind referenser men det ska vi inte gå in på just nu, ni kan göra en enkel liten regex som kollar efter ett # i $2 och om den inte hittar det så kan ni istället skriva PRIVMSG $1 så att meddelandet skickas privat till personen som skickade det till dig privat. Det finns mycket sånt man kan göra, vi har bara skrapat på ytan av regex men jag tycker att det räcker för tillfället, det kanske kommer en del 2. Som avslutning tänkte jag visa ännu en extremt kraftfull funktion i regex. Ni som kodar perl eller andra språk känner till if satser och hur dom fungerar, nu ska jag visa er samma sak i regex. Skriver ni (?if(then|or)) så letar ni först efter "if" och om det hittar så letar den vidare efter "then" men om den inte hittar första strängen så letar den istället efter "or". Ni behöver inte ha en tredje sträng utan det räcker med (?if(then)) så matchar den inget om det första uttrycket inte hittas. Detta kan jämföras med något som kallas lookahead och lookbehind i regex världen. Jag ska nog inte gå in närmare på det nu.
Nu ska jag göra en lista av alla metatecken och operatörer som finns i regex samt beskriva vad dom gör så att ni själva kan använda dom i er regex.
OBS Metatecken som \b har ofta en versal motsvarighet som i de flesta fall betyder motsatsen, detta gäller dock inte alla! OBS
Här följer olika metatecken som kan betyda olika saker i regex. Som ni ser så måste man ha en backslash framför alla och eftersom backslash är ett metatecken i sig så måste man skriva \\ för att matcha ett backslash tecken.
Metatecken
METATECKEN BESKRIVNING =========================== ^ eller \A Början på en rad eller sträng $ eller \Z Slutet på en rad eller sträng \< Början på ett ord \> Slutet på ett ord \b Ett ords omkrets \B Motsatsen till \b [\b] Backspace \c Kontrolltecken \d Alla tal \D Motsatsen till \d \f "Form feed" \n Ny rad \r "Carriage return" \s Mellanslag eller "whitespace" \S Motsatsen till \s \t Tab \v Vertikal tab \w Matchar alla alfanumeriska tecken, tal eller understreck \W Motsatsen till \w \x Matcha ett hexadecimalt tal \0 Matcha oktala tal
Här följer lite metatecken som används för att utföra olika funktioner med regex.
METATECKEN BESKRIVNING
===========================
. Matchar ett av alla tecken
| Eller, a|b|c, a ELLER b ELLER c
[] Matcha en eller en grupp av tecken
[^] Matcha inte en eller en grupp av tecken
- Definera en grupp, tex 0-9 eller A-Z
\ Gör ett undantag för ett metatecken
* Matchar inget eller fler av föregående tecken
*? Matchar endast en gång
+ Matchar en eller fler av föregående tecken
+? Matchar endast en gång
{MIN,MAX} Matchar ett visst antal gånger, MIN är minsta
antalet och MAX är högsta antalet matchningar som hittas
{MAX} Matchar endast MAX antal gånger
{MIN, } Matchar minst MIN antal gånger
{MIN, }? Slutar leta efter att uttrycket framför har matchat
() Definerar ett underuttryck eller ett "subexpression"
\1 Matchar första underuttrycket, \2 för andra osv
?= Se framåt
?<= Se bakåt
?! Matcha inte framåt
?!= Matcha inte bakåt
?() Krav, "if then", om den matchar regex inom () så
försöker den hitta regex som är bakom () men inte annars
?()| Krav, "if then else", samma som ovan men om den inte matchar
första uttrycket inom () så kommer den försöka matcha uttrycket
bakom |OBS Jag hade ingen referens när jag skrev färdigt artikeln så jag kommer nog lägga till fler metatecken så fort jag får tag på min bok som chefen lånat! OBS



