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
| ^begin | de 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:
- Dit is een test.
- Dit is test nummer twee.
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.