Reguliere Expressies

(regular expressions)

Bijna ieder programma dat werkt met teksten heeft een zoek- functie. Meestal voldoet zo'n zoekfunctie uitstekend: je zoekt bijv. naar de tekst "resultaten 1999" en de functie vindt alle locaties in je bestand waar deze tekst voorkomt. Maar dan komt het moment dat je naar iets ingewikkelders wilt zoeken: je wilt bijvoorbeeld alle regels met de "resultaten" vinden van 1993 tot en met 1999 en beslist niet die van 1990 tot en met 1992. Dat is waar reguliere expressies naar voren komen.

De reguliere expressie is een methode om, op een zo efficient mogelijke manier, een reeks van tekens te beschrijven. Het kan bijvoorbeeld gebruikt worden om met tools als 'grep' in teksten te zoeken of met een tool als 'sed' te zoeken en te vervangen. Om dit uit te leggen neem ik het voorbeeld van de "resultaten" van 1993 tot en met 1999. Als reguliere expressie zou dit worden:
resultaten 199[3-9]
Simpel toch? Deze expressie vindt alle stukken tekst beginnende met 'resultaten 199' gevolgt door het cijfer 3, 4, 5, 6, 7, 8 of 9. Met grep zou dan het commando er alsvolgt uit komen te zien:
grep -e "resultaten 199[3-9]" tekstbestand.txt
(-e geeft hierbij aan dat de volgende parameter een reguliere expressie is)
In dit voorbeeld geeft [3-9] aan dat alle tekens in de reeks '3' tot en met '9' achter "resultaten 199" mogen staan. Met '[' en ']' geef je dus een reeks van mogelijkheden aan.
Een ander voorbeeld: Je wilt alle regels vinden in een bestand waar het woord 'appel' in voorkomt. Het probleem daarbij is dat 'appel' ook aan het begin van een zin kan staan en dan begint met een hoofdletter. De expressie in dat geval is:
[aA]ppel
of met grep:
grep -e "[aA]ppel" tekstbestand.txt
Zoals ik al vertelde, kun je ook reeksen opgeven. Niet alleen getallen, maar ook reeksen letters en andere tekens.
Bijvoorbeeld:
[a-z]staat voor alle kleine letters 'a' tot en met 'z'
Ook meerdere reeksen in 1x kan:
[a-zA-Z]staat voor alle letters uit het alfabet, zowel hoofd- als kleine letters.
Je kunt ook aangeven dat je een letter of een reeks letters juist NIET wilt hebben. Daarvoor gebruik je het '^'-teken.
[^a-z]heeft als effect dat alle tekens die NIET in de reeks a-z zitten, gematched worden.

Als je nu in één keer 'kippehok' en 'kippenhok' wilt zoeken (dus ongeacht of het met of zonder 'N' geschreven is), dan kan dat als volgt:
grep -e "kippen*hok" bestand.txt
Dat sterretje betekent dat de 'n' in het gezochte woord nul of meer keer kan voorkomen. Dus inderdaad, ook kippennnnnnnhok zal 'matchen'.

Het is ook mogelijk een joker in te vullen. Dit doe je wanneer je niet weet welk teken op een bepaalde positie staat zoals bijvoorbeeld bij 'kippenhok' en 'kippe-hok': de ene keer wordt een streepje gebruikt, en de andere keer een n. Dit voorbeeld kan geschreven worden als:
kippe[n\-]hok
(merk op dat ik een '\' voor de '-' heb gezet: hiermee geef ik aan dat de '-' in dit geval niet een reeks van tekens aangeeft, maar dat het een op zich staand teken betreft)
en ook als:
kippe.hok
De punt geeft hierbij aan dat ieder teken goed is, zolang er maar "kippe" voor staat en gevolgd wordt door "hok".

Om het moeilijk te maken zijn er behalve de standaard reguliere expressies ook uitgebreide reguliere expressies ("extended regular expressions"). Die maken het onder andere mogelijk te zoeken op tekens (of reeksen) die 1 of meer keer voorkomen (in plaats van 0 of meer keer zoals bij '*'). Plaats hiertoe een '+' achter het teken. Bijvoorbeeld:
kippe.+hok
Resultaat: kippenhok matched, kippen-hok matched maar kippehok matched niet.

Aan alles is gedacht bij het ontwerp-proces van de reguliere expressies, dus ook is het mogelijk exact aan te geven hoevaak iets achter elkaar mag voorkomen. Daar wordt de notatie \{x,y\} voor gebruikt, waarbij de ',' en 'y' optioneel zijn. Een paar voorbeelden:
p\{3\}matched op 3 p's
p\{4,\}matched op 4 of meer p's
p\{5,8\}matched op 5 tot en met maximaal 8 p's achter elkaar.
Nog een voorbeeld dat een aantal expressies combineerd: het vinden van alle mogelijke ip-adressen in een document:
egrep -e "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" test.txt
(let op: ik gebruik nu 'egrep': dat is de grep die ook 'extended' reguliere expressies ondersteund)

Zoals al beschreven, kunnen de gevonden expressies overal in de tekst staan. Men kan echter ook expliciet aangeven dat de expressie aan het begin of aan het einde van een regel tekst moet staan. Met '^' geef je het begin aan en met '$' het einde van een regel aan. Het '^'-teken heeft dus twee verschillende betekenissen, afhankelijk van de context. Als je dus het volgende intikt:
grep -e "^Begin" bestand.txt
dan krijg je alle regels terug waar aan het begin van een regel het woord 'Begin' staat.
grep -e "einde\.$" bestand.txt
zoekt naar zinnen die eindigen op 'einde.'.
Deze twee zijn ook te combineren in een expressie:
grep -e "^begin.*eind$" bestand.txt
^beginde regel moet beginnen met het woord 'begin'
.*de punt geeft aan welk teken dan ook goed is, de ster geeft vervolgens aan dat het niet uitmaakt hoeveel tekens ("hoeveel punten") er staan
eind$geeft aan dat de tekst 'eind' aan het einde van de regel moet staan
Het zoeken van een lege regel doe je met de volgende expressie:
^$Het eind van de regel komt immers onmiddelijk na het begin van de regel

Als laatst bespreek ik nog het '|' (pipe) teken en het gebruik van '('+')'. Met het pipe-teken kan men twee of meer expressies samenvoegen. Dit heeft dan tot gevolg dat er gekeken wordt of tenminste één van de twee expressies matched.
Bijvoorbeeld:
egrep -e "^begin|eind$" bestand.txt
Dit matched regels met aan het begin van de regel 'begin' en/of aan het einde het woord 'eind'.
Haakje open en haakje sluiten kunnen ook erg makkelijk zijn. Je kunt daarmee aangeven dat er uit één of meer mogelijkheden gekozen moet worden ergens middenin een zin. Laat ik dit uitleggen aan de hand van wederom een voorbeeld:
^Dit is (een test|test nummer twee)\.$
Dit matched tegen de volgende twee regels: Men kan natuurlijk ook meer dan twee mogelijkheden opgeven:
^Dit is (een test|test nummer twee|test nummer drie)\.$


Welnu, dit was de 'reguliere expressies' in een notendop. Reguliere expressies kunnen behalve bij e*grep ook gebruikt worden bij bijvoorbeeld sed. Met sed kun je strings vervangen door andere strings aan de hand van een zoek-expressie. Awk is een soort van combinatie van grep en shell-scripts: je kunt aan matchende expressies acties verbinden. Dit kunnen vrij complexe programma's worden met een welhaast c-achtige structuur.
Als laatste wil ik graag multitail noemen. Dit programma stelt je in staat o.a. één of meer logfiles in één window te bekijken. In dit tool kun je met behulp van reguliere expressies bepaalde logregels onderdrukken of juist laten zien. Ook kun je stukken logregel een andere kleur geven zodat je in één oogopslag snel kunt zien wat er gaande is. Je kunt multitail downloaden via http://vanheusden.com/multitail/

Ik hoop dat ik je met dit verhaal duidelijk heb gemaakt wat een 'reguliere expressie' is en waar je het voor kunt gebruiken.
Veel succes!


Folkert van Heusden, September 2003.