Als er iets is in Linux wat nogal intimiderend over kan komen, dan zijn het reguliere expressies. Reguliere expressies zijn zoekpatronen, die het je op de command line makkelijker maken om bestanden te vinden waar bepaalde tekstpatronen in voorkomen.

Sander van Vugt

Er zijn echter zoveel eigenaardigheden, dat het risico bestaat dat je na korte tijd door de bomen het bos niet meer ziet. In dit artikel schepen we duidelijkheid! Laten we eens beginnen met een willekeurig voorbeeld:

grep a* a*

In deze opdracht wordt de generic regular expression parser, ofwel grep gebruikt om te zoeken naar een tekenreeks in een reeks bestanden. Het eerste argument van het commando geeft aan wat de tekenreeks is, het tweede argument geeft aan om welke bestanden het gaat.

Als de shell een opdracht zoals deze tegenkomt, treedt er een mechanisme in werking dat bekend staat als “command line parsing”. Dit betekent dat de shell alle speciale tekens interpreteert, en vervolgens verder gaat met de opdracht. Nu komt er in deze opdracht tweemaal een * voor, waarvan de eerste keer een reguliere expressie is, en de tweede keer een shell wildcard. Verwarrend? Je kan het een stuk duidelijker maken door reguliere expressies altijd tussen enkele aanhalingstekens te zetten. De opdracht komt er dan als volgt uit te zien:

grep 'a*' a*

In deze opdracht geven de enkele aanhalingstekens de shell de instructie om de * niet te interpreteren. Maar wat betekent nu die *? In een reguliere expressie betekent de * dat het voorgaande teken voor mag komen, en ook meerdere keren, maar dat het niet voor hoeft te komen en dat maakt het eerlijk gezegd op deze manier gebruikt een vrij zinloze reguliere expressie, omdat hij een match zou geven op bestanden met daarin de tekenreeks a, aaaaa, maar ook de tekenreeks waarin helemaal geen a voorkomt! Probeer maar eens het volgende:

echo bbbb > testfile
grep 'a*' testfile

Dat dit voorbeeld niet zo heel erg gelukkig is, betekent overigens niet dat de * reguliere expressie op zich zinloos is. Je kan hem bijvoorbeeld handig gebruiken om te zoeken naar directories, en een match te geven als in die directories al dan niet bestanden voorkomen. Dit gebeurt bijvoorbeeld in semanage fcontext -a -t ‘/dir(/.*)?’ – een commando dat een reguliere expressie bevat die je aan het eind van dit artikel volledig zult begrijpen.

Verschillende soorten

Wat het werken met reguliere expressies nu écht ingewikkeld maakt, is dat het niet om een gestandaardiseerd iets gaat. Er zijn in Linux-omgevingen verschillende opdrachten die iets nodig hebben om tekstpatronen op een effectieve manier te adresseren, en die commando’s hebben reguliere expressies nodig. Het probleem echter is dat de makers van de verschillende commando’s allemaal zelf hun eigen variant op het thema reguliere expressies gemaakt hebben. Zo heb je bijvoorbeeld grep, sed, awk, perl, python en nog een aardig aantal commando’s met hun eigen reguliere expressies, maar een reguliere expressie die je in perl wél kan gebruiken, kan het op die manier gewoon niet doen in grep. En dat zorgt ervoor dat wanneer je enthousiast op zoek gaat naar reguliere expressies, de kans bestaat dat Google je een reguliere expressie geeft die nou net niet in jouw commando werkt. Als gevolg moet je niet alleen weten welke reguliere expressies je gebruikt, maar ook in welke context een bepaalde reguliere expressie wel of niet gebruikt kan worden!

De meest algemene tool voor het werken met reguliere expressies is de Linux tool grep. In grep is er een oplossing voor dit probleem. De utility heeft zelf zijn eigen set reguliere expressies, en kan daarnaast werken met extended regular expressies door gebruikt te maken van de optie -e of de opdracht egrep. En dat kan ervoor zorgen dat een reguliere expressie die het in de normale grep niet doet, het in egrep wel gewoon doet! Als je reguliere expressie het niet doet, is het dus altijd de moeite waard om eens te kijken of hij het misschien als extended reguliere expressie wel zou doen.

Elementen in een reguliere expressie

Bij het werken met reguliere expressies kan gebruikgemaakt worden van verschillende elementen. Het eerste element duiden we aan als het atom (atoom). Een atoom specificeert welke tekst een match zou moeten geven. Atomen kunnen enkele tekens zijn, een reeks tekens, of een punt om een match te geven met elk willekeurig teken. Atomen kunnen ook klassen zijn, zoals [[:alpha:]], [[:upper:]] en [[:alnum:]], voor letters, hoofdletters en letters of cijfers.

Als tweede element kan gebruikgemaakt worden van herhalingsoperatoren (repetition operators) die aanduiden hoe vaak een teken voor moet komen. Het derde element dat je tegen kan komen geeft aan waar het volgende teken gevonden kan worden.

In tabel 1 vind je reguliere expressies die een positie aangeven:

Reguliere Expressie Betekenis
^ begin van de regel
$ eind van de regel
\< begin van een woord
\> eind van een woord
\A begin van een bestand
\Z eind van een bestand

Tabel 1: Positionele reguliere expressies.

Vervolgens zijn er de operatoren die aangeven hoe vaak een teken voor moet komen. Tabel 2 geeft een overzicht:

Reguliere Expressie Betekenis
{n} Precies n keer
{n,} Minimaal n keer
{,n} Maximaal n keer
{n,o} Tussen n en o keer
* Nul of meer keer
+ Een of meer keer
? Nul of een keer

Tabel 2: Herhalings reguliere expressies.

Een mooi voorbeeld van een reguliere expressie wordt regelmatig gebruikt bij het werken met semanage om SELinux beveilingsinstellingen te doen. De complete opdracht hierbij is semanage fcontext -a -t public_content_rw_t “/web(/.*)?”. Het gaat hierbij natuurlijk om de reguliere expressie aan het eind van de opdracht.

Om voorgaande reguliere expressie te begrijpen, beginnen we met de vraagteken aan het einde. Deze geeft aan dat het voorgaande deel nul of een keer voor mag komen. Nu is het voorgaande deel in dit geval een expressie: alles wat tussen de haken voorkomt. Om het simpel te zeggen, het vraagteken en de voorgaande expressie geven dus aan dat /web zonder iets erachter goed is, maar dat er ook nog wat achter mag staan. Dan de expressie zelf. Deze bestaat uit /.*. Deze expressie begint met een /, de / die je normaal aan het eind van een directory pad vindt. Vervolgens staat er een punt. Deze verwijst naar elk mogelijk teken. En dan een *, die aangeeft dat de voorgaande punt (een willekeurig teken) nul of meer keer voor kan komen. Concreet betekent dat dat het pad /web/ een match geeft op de reguliere expressie, maar dat /web/ gevolgd door bestandsnamen ook OK is.

Laten we tot slot van dit artikel eens kijken naar een paar voorbeelden van reguliere expressies:

Laten we eens beginnen met de reguliere expressie ‘b?t‘. Enig idee op welk van de volgende vier deze extended reguliere expressie een match geeft?

  1. bit
  2. boet
  3. bt
  4. baaaat

 

Het juist antwoord is allemaal! Er wordt in deze reguliere expressie namelijk gekeken naar het voorgaande teken, dat nul of meer één keer voor mag komen, en heeft daarom betrekking op de b, en niet op wat erna komt. Het risico bestaat dat je alleen antwoord a als goed beschouwd hebt, en dat is te begrijpen, maar dan heb je de ? niet als reguliere expressie, maar als shell wildcard geïnterpreteerd!

Laten we er nog een bekijken: ‘kip\{2\}en‘. De mogelijke antwoorden zijn:

  1. kipen
  2. kien
  3. kippen
  4. kipkipen

 

En het juist antwoord is c. In deze extended reguliere expressie wordt namelijk gebruikgemaakt van een repetition operator, die geeft dat je precies twee maal de letter p wilt zien.

Nog één dan, de reguliere expressie is ‘linda.’ en de mogelijke antwoorden zijn:

  1. lindaaa
  2. lind
  3. linda.
  4. linda

Van deze antwoorden zal alleen antwoord b geen match geven. De punt geeft namelijk aan dat het voorgaande teken (de a) één of meer keer voor moet komen.

Ik hoop dat dit artikel je enig inzicht heeft kunnen geven in het werken met reguliere expressies. Het blijft hoe dan ook lastig, maar het is een onderwerp dat je het beste onder controle krijgt door veel te oefenen!