[LinuxFocus-icon]
Home  |  Map  |  Index  |  Zoek

Nieuws | Archieven | Links | Over LF
convert to palmConvert to GutenPalm
or to PalmDoc

[Guido Socher]
door Guido Socher

Over de auteur:

Hij houdt van Linux omdat het een vrij systeem is en het ook leuk is om samen te werken met Linuxers van over de hele wereld. Hij besteedt z'n vrije tijd met zijn vriendin, luistert naar BBC World Service radio, stuurt zijn fiets door het platteland en speelt graag met Linux.


Inhoud:

 

Reguliere Expressies

[Illustratie]

Kort:

Reguliere expressies (regular expressions) worden gebruikt voor ingewikkelde, context-gevoelige zoekacties en dito veranderingen aan tekst. Je kan ze gebruiken in vele tekstverwerkers, in bepaalde programma's (parsers) en in programmeertalen.



 

Introductie

Reguliere expressies kom je tegen in veel uitgebreide tekstverwerkers zoals vi en emacs. Ook zijn ze te gebruiken in grep/egrep en in talen zoals awk, perl en sed.

Ze worden onder meer gebruikt voor context-gevoelige zoekopdrachten. Een reguliere expressie is een formele beschrijving van de vorm waaraan een bepaald stuk tekst moet voldoen.

Toen ik een paar jaar geleden iemand aan het werk zag met reguliere expressies, was ik gefascineerd. Het aanpassen en opzoeken van bepaalde stukken tekst, wat normaal uren zou vergen, werd gedaan in slechts enkele seconden. En inderdaad, ik begreep geen snars van de uitdrukkingen op het scherm. Het zag eruit als rare combinaties van punten, streepjes, sterretjes en nog van dat fraais. Toch wilde ik leren hoe die dingen werken, en al vlug merkte ik dat ze vrij eenvoudig te gebruiken zijn. Ze volgen namelijk eenvoudige syntax regels.

Reguliere expressies zijn algemeen bekend in de Unix wereld, maar toch is er niet zo iets als de 'standaard' regular expression taal. Het gaat eerder om verschillende dialecten. Het programma grep bijvoorbeeld bestaat in twee soorten: grep en egrep. Ze gebruiken beiden reguliere expressies, maar met verschillende mogelijkheden. De programmeertaal Perl heeft waarschijnlijk de meest complete set reguliere expressies. Gelukkig volgen al deze verschillende vormen van regex's wel dezelfde principes. Eens je het basis-idee onder de knie hebt, leer je gemakkelijk de verschillende dialecten aan.

Dit artikel maakt je vertrouwd met de basis; hoe het nu precies werkt met een bepaald programma kan je dan opzoeken in de manual pages.  

Een eenvoudig voorbeeld

Stel, je hebt een lijst met telefoonnumers van een bedrijf die er zo uit ziet:

Nr.    Naam  ID
     ...
3412    Jef 123
3834  Marie 333
1248  Kathy 634
1423   Toon 567
2567 Pieter 435
3567     An 535
1548  Jerry 534
     ...

Er staan 500 mensen in de lijst. De gegevens staan in een gewoon tekstbestand. Mensen die een telefoonnummer hebben dat begint met een 1, werken in gebouw 1. Wie werkt er nu in gebouw 1?

Dit lossen we op met reguliere expressies:

grep '^1' telefoonlijst.txt
of
egrep '^1' telefoonlijst.txt
of
perl -ne 'print if (/^1/)' telefoonlijst.txt

In gewone woorden gezegd, elke lijn begint met een 1. De "^" staat voor het begin van een lijn. Het zorgt ervoor dat de hele expressie enkel 'past', of geldig is, als het eerste karakter van de lijn een 1 is.  

De syntax regels

Patronen uit 1 karakter

De bouwstenen van reuliere expressies zijn de uitdrukkingen die bestaan uit 1 karakter. Die uitdrukking slaat dan op juist 1 karakter. Een voorbeeld van zo'n uitdrukking is de "1" in het voorbeeld hierboven. Het geldt enkel voor 1 karakter in de tekst.

Een ander voorbeeld van uitdrukkingen met enkel-karakter patronen is:
egrep 'Kathy' telefoonlijst.txt

Dit patroon bestaat uit een aaneenschakeling van uitdrukkingen van 1 karkater (de letters K,a,t, ...)

Je kan karakters ook samenvoegen tot een groep. Zo'n groep of set wordt voorgesteld door vierkante haakjes waarin een lijst van karakters staat. De set als geheel heeft ook maar betrekking op^1 karakter in de tekst. De uitdrukking is geldig als het karakter in de tekst overeenkomt met één van de van de karakters uit de set. Een voorbeeld:

[abc]         Dit is een enkel-karakter patroon dat geldig is voor de letters a, b en c.

[ab0-9]       Een enkel-karakter patroon dat staat voor ofwel a of b, ofwel een getal van nul tot negen.

[a-zA-Z0-9\-] Dit patroon slaat op alle letters van het alfabet (kleine letters of hoofdletters),
              op alle cijfers van nul tot negen en op het minteken.

Even proberen:
egrep '^1[348]' telefoonlijst.txt

Deze opdracht zoekt naar lijnen die beginnen met 13, 14 of 18.

We zagen reeds dat de meeste reguliere expressies slechts 1 ASCII karakter evalueren, maar sommigen hebben een speciale betekenis. De vierkante haken bijvoorbeeld duiden op een set. In de set betekent het "-" teken 'tot en met' bv. [0..9] betekent nul tot en met negen. Om ervoor te zorgen dat zo'n stuurteken geen speciale betekenis meer heeft, moet je er een \backslash\ voor zetten. Het minteken in [a-zA-Z0-9\-] is daar natuurlijk een voorbeeld van. Er zijn ook dialecten waar het omgekeerd werkt: de speciale tekens worden voorafgegaan door een beckslash, je verwijdert de backslash voor de gewone betekenis.

Het punt is een speciaal teken. Het staat voor alles behalve het 'newline' karkter, dat eigenlijk de tekstversie is van op enter drukken. Voorbeeld:

grep '^.2' telefoonlijst.txt
 or
egrep '^.2' telefoonlijst.txt

Dit zoekt naar lijnen met een 2 als tweede teken van de lijn en eender wat als eerste teken.

Sets kunnen worden omgedraaid door in plaats van "[^" , "[" te typen. Hier betekent de "^" niet langer 'begin van de lijn', maar wel dat de set omgedeerd moet worden.

[0-9]    Slaat op alle cijfers: nul tot negen
[^0-9]   Alles wat GEEN cijfer is van nul tot negen
[^abc]   Alles wat niet a, b of c is
 .       Het punt geldt voor alle tekens die geen newline zijn.
         Het is dus hetzelfde als [^\n] (\n is het newline teken)

Om te zoeken naar lijnen die beginnen met iets dat NIET 1 is, kunnen we schrijven:
grep '^[^1]' telefoonlijst.txt
 of
egrep '^[^1]' telefoonlijst.txt

Ankers

Reeds in het vorige deel hebben we gezien dat "^" slaat op het begin van een lijn. Ankers of anchors zijn speciale regexp tekens die gelden voor een bepaalde positie in de tekst en niet voor een willekeurig karakter in de tekst.

^  Begin van een lijn
$  Einde van een lijn

Om in onze lijst naar mensen te zoeken die het ID nummer 567 hebben, doen we:

egrep '567$' telefoonlijst.txt

Dit zoekt naar lijnen die op het eind het nummer 567 bevatten.

Multipliers

Een vermenigvuldiger of multiplier geeft aan hoe vaak een enkel-karakter patroon moet voorkomen in de tekst.

beschrijvinggrepegrepperlvivimvileelvisemacs
0 of meer keren********
1 of meer keren\{1,\}++  \+\+\++
0 of 1 keer\???  \=\?\=?
n tot m keren\{n,m\}  {n,m}      \{n,m\} 

Noot: In de verschillende VIs stel je de 'magic' optie in om te werken zoals hierboven.

Een voorbeeld uit de lijst:

....
1248  Kathy 634
....
1548  Jerry 534
....

Om te zoeken naar een regel die begint met een 1, dan enkele cijfers bevat, verdergaat met ten minste 1 spatie en vervolgens een naam bevat die begint met een K, schrijven we:

grep '^1[0-9]\{1,\} \{1,\}K' telefoonlijst.txt
  ofwel met * , meerdere keren [0-9] en spatie:
grep '^1[0-9][0-9]*  *K' telefoonlijst.txt
  ofwel
egrep '^1[0-9]+ +K' telefoonlijst.txt
  of
perl -ne 'print if (/^1[0-9]+ +K/)' telefoonlijst.txt

De multiplier vermeedert het aantal keren dat de zoekterm moet voorkomen Dus "23*4" betekent dus NIET "een 2, een 3, dan eender wat en dan een vier 4" (Dat doe je met "23.*4"). Het betekent "een twee, dan x aantal keer een drie en dan een vier"

Merk op dat multipliers erg dominant zijn in een reguliere expressie. Dit betekent dat de eerste multiplier blijft gelden zolang de bijhorende reguliere expressie gevonden wordt. Ze worden dus zo ver mogelijk naar rechts 'uitgerokken'.

De expressie ^1.*4
komt overeen met de gehele lijn

1548  Jerry 534
van het begin tot aan de laatste 4.
Het resultaat is dus NIET enkel de 154.

Voor grep maakt dit niet zo veel uit, maar het is wel belangrijk voor tekstbewerkingen en - vervangingen.

Geheugen in een regexp: haakjes

Als je haakjes zet rond een expressie heeft dit geen invloed op de manier waarop de expressie wordt geëvalueerd. Het zorgt er gewoon voor, dat het resultaat van die uitdrukking onthouden wordt zodat je er later in de expressie nog kan naar verwijzen.

Je verwijst naar het onthouden deel via variabelen. De inhoud van de eerste haakjes is beschikbaar als variabele 1, de tweede als 2 en zo verder.

programmasyntax van de haakjessyntax van de variabele
grep\(\)\1
egrep()\1
perl()\1 of ${1}
vi,vim,vile,elvis\(\)\1
emacs\(\)\1

Voorbeeld:

De expressie [a-z][a-z] geeft als resultaat twee letters.

Nu kunnen we die variabelen gebruiken om te zoeken naar dingen zoals 'abba' of 'otto':

egrep '([a-z])([a-z])\2\1'

De variabele \1 bevatte de letter a
en de \2 bevatte de b.

Deze expressie zou ook gelden voor de naam anna, maar niet voor yxyx.

Haakjes gebruiken om tekst in het geheugen te plaatsen wordt niet zo vaak gebruikt om te zoeken naar namen als abba of otto, maar wel om tekst te bewerken.  

Reguliere Expressies gebruiken voor tekstverwerking.

Om tekst te bewerken met reguliere expressies zul je een editor als vi of emacs moeten gebruiken, of je moet gebruik maken van bv. perl.

In emacs kan je M-x query-replace-regexp typen ofwel het query-replace-regexp commando op een functietoets zetten. Een andere mogelijkheid is gebruik te maken van het commando replace-regexp. De zoekopdracht query-replace-regexp is interactief, de andere is dat niet.

In vi heb je de vervangingsopdracht (substitution command) :%s/ / /gc. Het percentteken verwijst naar de ex-range (doelgebied) 'whole file', dus deze opdracht zal het hele bestand doorzoeken. Je kunt het vervangen door een andere range, waarmee je dan enkel een bepaald deel van het bestand doorzoekt. Bijvoorbeeld, je typt in vim shift-v, je selecteert een stuk tekst en je doet vervolgens de vervangingsopdracht enkel in dat gebied. Ik zeg verder niet veel meer over vim omdat het eigenlijk zijn eigen handboek zou moeten vormen. De 'gc' is de interactieve versie. De niet-interactieve opdracht is s/ / /g

'Interactief' betekent dat je iedere vervanging moet bevestigen.

In perl doe je dit zo

perl -pe 's/ / /g' 

We bekijken een paar voorbeelden. Het nummer-schema in het bedrijf is veranderd en alle nummers die vroeger begonnen met een 1, beginnen nu met 12.

We moeten dus telkens een 2 erachter schrijven; 1423 wordt bijvoorbeeld 14223.

De oude lijst:


Nr.    Naam  ID
     ...
3412    Jef 123
3834  Marie 333
1248  Kathy 634
1423   Toon 567
2567 Pieter 435
3567     An 535
1548  Jerry 534
     ...

Hoe je de lijst aanpast:

vi:    s/^\(1.\)/\12/g
emacs: ^\(1.\)   replaced by  \12
perl:  perl -pe 's/^(1.)/${1}2/g' telefoonlijst.txt
Nu ziet de telefoonlijst er zo uit:

Nr.    Naam  ID
     ...
3412    Jef 123
3834  Marie 333
12248  Kathy 634
12423   Toon 567
2567 Pieter 435
3567     An 535
12548  Jerry 534
     ...

Perl kan meer variabelen aan dan enkel de \1 tot en met \9. De \12 bijvoorbeeld verwijst naar de twaalfde variable, die in ons geval niet nodig is (en trouwens gewoon leeg is). In de plaats gebruiken we gewoon ${1}.

Nu is onze lijst wel een beetje rommelig. De namen en nummers zijn niet meer uitgelijnd. Hoe lossen we dat op? Je test gewoon of er op de vijfde plaats een spatie staat en in dat geval voeg je een extra spatie aan toe:

vi:     s/^\(....\) /\1  /g
emacs:  '^\(....\) '  replaced by  '\1  '
perl:   perl -pe 's/^(....) /${1}  /g' telefoonlijst.txt
Nu ziet de telefoonlijst er zo uit
Nr.    Naam  ID
     ...
3412     Jef 123
3834   Marie 333
12248  Kathy 634
12423   Toon 567
2567  Pieter 435
3567      An 535
12548  Jerry 534
     ...

Een collega heeft met de hand wat aan de lijst zitten prutsen en heeft per ongeluk wat enkele spaties toegevoegd aan het begin van een paar lijnen. We gaan ze nu verwijderen.

Nr.    Naam  ID
     ...
3412     Jef 123
     3834   Marie 333
12248  Kathy 634
12423   Toon 567
 2567  Pieter 435
3567      An 535
  12548  Jerry 534
...
Dit is de remedie:
vi:     s/^  *//  (We gebruiken 2 spaties omdat vim de regexp uitdrukking + niet kent)
emacs:  '^ +'  replaced by the empty string
perl:   perl -pe 's/^ +//' telefoonlijst.txt

Stel, je schrijft een programma met daarin de variabelen temp en temporary. Je wilt nu de variabele temp vervangen door de variabele met de naam counter. Als je nu gewoon alle 'temp' tekst vervangt door 'couter', dan wordt temporary 'counterorary'. Wat duidelijk niet de boedoeling is!

Reguliere expressies kunnen dit voor je regelen. Vervang gewoon temp([^o]) door counter\1. Hiermee bedoelen we dus 'temp' maar niet de letter 'o'. (Een andere oplossing zou je kunnen maken met boundaries, maar die worden in dit artikel niet behandeld.)

Ik hoop dat dit artikel je enthousiast heeft gemaakt. Nu ben je misschien klaar om te gaan neuzen in man-pages en in de documentatie van je favoriete tekstverwerker. Enkel daar kun je de precieze details van elk dialect leren.

Dit artikel is niet echt volledig. Er zijn nog meer speciale karakters, bijvoorbeeld de alteration (~ , zet kleine letters om in hoofdletters en omgekeerd) dat eigenlijk een soort van "OR" opdracht is. En dan heb je nog de boundaries die hierboven vermeld werden.

Amuseer je ermee!


Site onderhouden door het LinuxFocus editors team
© Guido Socher, FDL
LinuxFocus.org

Klik hier om een fout te melden of commentaar te geven
Vertaling info:
en -> -- Guido Socher
en -> nl Floris Lambrechts

2002-02-08, generated by lfparser version 2.17