Regular expressions

[ Introduktion | Tecken | Kvantifierare | Påstående | Grupper | Alternativ | Sekvenser | Exempel | Utilities | Sammanfattning ]


Välkommen till en introduktion i regular expressions, vilket ofta kallas regexps eller bara RE. På svenska skulle det bli "reguljära uttryck", "standardiserade uttryck" eller "vanliga uttryck", men de termerna känns inte så bra utan svengelskan är lättare att förstå ;)

Först förklaras vad regular expressions är och dess syntax. Sedan kommer några exempel med varierande komplexitet, och slutligen några verktyg som använder regular expressions.

Introduktion

Ett regular expression är ett textmönster som används för att passa in i text-strängar. Textmönstret består av en kombination av alfanumeriska tecken samt specialtecken som kallas meta-tecken. Nära släkt är faktiskt de söktecken (?,*) (wildcard expression) som ofta används vid filhantering.

Regular expression används på tre olika sätt: vanlig textsökning, sök-och-ersätt, samt vid delning. Det senare är i grunden detsamma som omvänd sökning (reverse match), dvs allting som mönstret ej passade in på.

Tecken

Innehållet i ett mönster är, enligt ovan, en kombination av alfanumeriska tecken och meta-tecken.

Ett alfanumeriskt tecken (alphanumeric character) är antingen ett tecken från alfabetet eller en siffra:
abcdefghijklmnopqrstuvwxyz
1234567890

Följande tecken är meta-tecken (metacharacter):
\ | ( ) [ ] { } ^ $ * + ? . < >

I praktiken kommer dock alla tecken som inte är ett meta-tecken att fungera som ett alfanumeriskt tecken. Dessa kallas ofta literala tecken (literal characters).

omvändare

Ett mycket speciellt tecken är bakåtvänt snedstreck \ (backslash), som gör om ett meta-tecken till ett literalt tecken, samt alfanumeriska tecken till ett slags meta-tecken eller meta-sekvens.

punkt

Ett annat speciellt tecken som ofta missförstås är punkt . (punctuation mark, or dot). Punkten kommer inte, som många tror, att passa in på en punkt i en text, utan det är istället ett meta-tecken som motsvarar vilket tecken som helst. Om du använder punkten för att hitta slutet på en mening eller en decimalpunkt i ett tal, kommer du att få fel. Som nämnt ovan skall punkten istället användas som literalt tecken med hjälp av bakåtvänt snedstreck. ( Egentligen är punkten ett specialfall av den generella kvantifieraren, se nedan.)


Se på följande exempel på ett sökmönster:
1.23
kommer inte bara passa in på numret 1.23 i en text, utan även på följande:
1x23
1 23
1-23
Om sökmönstret enbart skall passa in på decimaltalet måste det ändras till:
1\.23
Glöm det inte!


Kvantifierare

Två av de vanligaste metatecknen är:
* och +

De kallas kvantifierare (quantifier) eftersom de passar in på flera instanser av ett tecken. Kvantifieraren avser alltid tecknet som är angivet till vänster om den.


Så om du vill hitta alla ord som börjar med j kanske du vill ange:
j*
och till din överraskning får enorma mängder träffar, till och med i ord som inte innehåller bokstaven j...

Anledningen är att tecknet * passar in på noll eller flera tecken, och det var det som hände, det passade in på noll tecken!

Konsekvensen är att i regular expressions har man möjlighet att hitta det som kallas den tomma strängen, vilket helt enkelt är en sträng med noll i storlek.

Tomma strängar finns i alla texter, till exempel i ordet:
finns det tre tomma strängar. Det finns en precis innan g, en mellan g och å samt en efter å.

Det här verkar kanske konstigt, men det finns (givetvis) användning för detta i mer komplexa sökmönster.

Så med denna läxa ändrar vi sökmönstret till:
j+
och genast får vi enbart ord med j.

frågetecken

Nästa metatecken är:
?

Frågetecknet ? (question mark) betyder att mönstret skall passa in på ett tecken eller inte alls (noll eller ett).


Till exempel sökmönstret:
jul?
kommer att passa in på någon av dessa rader:
ju
jul

generell kvantifierare

Dessa tre metatecken är helt enkelt specialiserade metoder av den mer generella kvantifieraren:
{n,m}
där n och m betyder den minsta repektive den största storleken för kvantifieraren.


Till exempel:
{1,5}
betyder att mönstret skall passa in på en eller upp till och med fem tecken.

Man kan strunta i m för att inte begränsa storleken uppåt:
{1,}
vilket kommer att passa in på ett eller flera tecken. Med andra ord, precis detsamma som + gör.

Man kan även strunta i kommatecknet , (comma) för att passa in på ett exakt antal tecken.


Till exempel:
{5}
passar in på exakt fem tecken, varken mer eller mindre.


Påstående

Nästa typ av metatecken är påståenden (assertions), som kommer att passa in om ett visst påstående är sant.

Det första teckenparet för påståenden är:
^ och $
som kommer att passa in på början av en rad respektive i slutet på en rad.

Observera att i vissa implementeringar av regular expressions är det tillåtet att ändra betydelsen till att istället passa in på början av en text respektive i slutet på en text.
Dessa påståenden passar alltid in på en tom sträng, eller med andra ord på en viss position.


Till exempel följande sökmönster:
^Jag
skulle passa in på alla rader som börjar med ordet Jag.

Nästa teckenpar för påstående är:
< och >
som passar in i början på ett ord repektive i slutet på ett ord.


De är mycket användbara för att hitta ett exakt ord, till exempel:
jul
passar in på alla följande ord:
jul
julgran
julklapp
jultomte

Efter nedanstående förändring i sökmönstret:
<jul>
passar det enbart in på ordet jul i texten.

Det sista man bör säga är att alla literala tecken egentligen är påståenden. Den enda skillnaden är att literala tecken har en storlek. Därför används termen påstående enbart för de som har storleken noll.


Grupper (delmönster)

En sak som du kanske har lagt märke till vid genomgången av kvantifierare är att i exemplen avsåg de endast tecknet till vänster om sig. Vi skall diskutera hur denna begränsade användning kan utökas.

Kvantifierare kan även användas på meta-tecken. (Att använda dem på påståenden vore dumt eftersom längden på dessa är noll, och använda dem på flera gör ingen skillnad.) Men att använda dem på grupper och sekvenser (se nedan) är mycket användbart.

Man kan skapa grupper, eller delmönster (subexpressions) som de kan kallas, genom att använda parentes-teckenparet :
( och )

där startparentes ( (start parenthesis) påbörjar delmönstret och slutparentes ) (end parenthesis) avslutar det. Det går bra att använda delmönster inuti delmönster.


Man kan kombinera delmönster med kvantifierare och påstående på följande vis:
( ?ho)+
vilket passar in på följande rader:
ho
ho ho
hohoho


Alternativ

Nästa typ av metatecken är alternativ (alternation) som gör det möjligt att passa ett av flera ord. Metatecknet för alternativ är stuprör (pipe):
|


Ett enkelt exempel på alternativ:
Bill|Linus|Steve|Larry
vilket antigen passar in på Bill, Linus, Steve eller Larry.

Om man kombinerar alternativ med delmönster och kvantifierare kan man få:
jul(gran|klapp|tomte)?
vilket passar in på:
jul
julgran
julklapp
jultomte

Med andra ord kan man bygga ett mönster som kan passa, fastän inte allt passar in:
(Santa (Claus|Klaus))|(Tomten)

Som du kan se passar antingen vänster eller höger delmönster in, och inte båda. Detta kan ibland vara användbart vid komplexa sökningar.


Sekvenser

Till slut finns det sekvenser (sequences), vilka definierar sekvenser av tecken som kan passa. Användbart när du inte söker ett visst ord utan snarare någonting som liknar ett ord.

Teckenparet för sekvenser är:
[ och ]

Alla tecken som placeras mellan hakparenteserna tolkas som literala tecken, även metatecken. De enda undantagen är bindestreck (dash) - vilket används för uttrycka ange ett flertal tecken enkelt, samt taktecken ^ som används för att negera en sekvens.


Till viss del påminner sekvenser om alternativ; endast ett av de listade tecknen kommer att passa. Till exempel:
[a-z]
passar in en gemen bokstav i det engelska alfabetet (a till z).

En annan vanlig sekvens är:
[a-zA-Z0-9]
som passar in på både gemener och versaler i det engelska alfabetet, samt siffror.


Om man kombinerar alternativ med påstående och kvantifierare kan mer intelligenta sökningar göras:
<[a-zA-Z]+>
passar in på alla ord. Till exempel på:
jul
Linus
regular
expression
men inte på:
200
x-files
C++


Om man istället vill hitta allt som inte är ord kan man använda mönstret:
[^a-zA-Z0-9]+
som passar in på alla teckensekvenser som varken innehåller bokstäver eller siffror.

Observera att i vissa implementeringar av regular expressions finns det koder för vissa vanliga sekvenser:
\d = [0-9] (en siffra)
\D = [^0-9] (ingen siffra)
\w = [a-zA-Z0-9] (ett alfanumeriskt tecken)
\W = [^a-zA-Z0-9] (inget alfanumeriskt tecken)
\s = [ \t\n\r\f] (ett mellanrum)
\S = [^ \t\n\r\f] (inget mellanrum)


Exempel

För att förstå regular expressions ännu bättre finns följande användbara mönster.
Undersök dem och försök förstå exakt vad de gör. Experimentera gärna:

Flyttal: passar in på enkla flyttal,
t.ex "1.2" eller "-0.5":
-?[0-9]+\.[0-9]+

Hexadecimala tal: passar in på hex-siffror (i C/C++ stil):
t.ex "0xcafebabe":
0x[0-9a-fA-F]+

Protokoll validering: passar in på web-baserade protokoll,
såsom "http://", "ftp://" eller "https://":
[a-z]+://

Epost validering: detta exempel kommer bara att passa in på korrekta epost-adresser,
t.ex "[email protected]":
[a-z0-9_-]+(\.[a-z0-9_-]+)*@[a-z0-9_-]+(\.[a-z0-9_-]+)+

Epost validering #2: passar in på epost-adresser med ett namn innan,
t.ex "Bo Ek <[email protected]>":
("?[a-zA-Z]+"?[ \t]*)+\<[a-z0-9_-]+(\.[a-z0-9_-]+)*@[a-z0-9_-]+(\.[a-z0-9_-]+)+\>

C/C++ includes:
^#include[ \t]+[<"][^>"]+[">]

C++ end of line comments:
//.+$

C/C++ span line comments: har en svaghet, kan du hitta den?
/\*[^*]*\*/


Utilities

Konceptet med regular expressions kommer från Unix-världen, där det är väl använt i bland annat kommandoradssökaren grep samt editorer som sed, ed, vi och (X)Emacs. Även i gawk och perl finns stöd. Speciellt perl har en mycket avancerad implementering av regular expressions.


Sammanfattning

  \   Omvändare växlar betydelse på literala och meta-tecken
  *   Kvantifierare passar in på 0 till flera tecken
  + passar in på 1 till flera tecken
  ? passar in på 0 eller 1 tecken
  . passar in på 1 tecken
 { } {n,m} passar in på n till m tecken
  ^   Påstående passar in på början på en rad
  $ passar in på slutet på en rad
  < passar in på början på ett ord
  > passar in på slutet på ett ord
 < > passar in på ett ord
 ( )   Grupper passar in på teckenkombinationer
  |   Alternativ passar in på ett av flera alternativ
 [ ]   Sekvenser passar in på ett tecken i en kedja av tecken.
- anger sekvensen och ^ negerar sekvensen

[ Introduktion | Tecken | Kvantifierare | Påstående | Grupper | Alternativ | Sekvenser | Exempel | Utilities | Sammanfattning ]


Christer Tärning
8 november 1997