Introductie
Regular expressions (regex of regexp) zijn misschien wel de meest gebruikte programmeertaalfuncties. Bijna alle grote talen van Java tot Golang en nieuwkomers zoals Deno hebben reguliere expressies geïmplementeerd als een ingebouwde functie.
Zelfs buiten het conventionele programmering, komt regex vrij veel voor. Bijna alle webservers zoals Apache en Nginx, die ongeveer 90% van de markt uitmaken, vertrouwen op regex. In wezen is voor ontwikkelaars vrijwel noodzakelijk om meer te leren over regex.
Ondanks dat het veel wordt gebruikt, hebben reguliere expressies in de loop der jaren een slechte reputatie gekregen, omdat het ‘moeilijk’ te beheersen is. Als je de basisprincipes al kent, kun je ook verder leren hoe regex intern werkt. Daarnaast kun je ook betere regexes maken en de volledige kracht ervan ontketenen.
Dat gezegd hebbende, laten we in deze tutorial eerst kijken hoe de basis werkt.
Vereisten
Voordat je je al teveel laat meeslepen, is het belangrijk dat je weet wat regex eigenlijk is. Een regular expression is een tekenreeks die bepaalde patronen beschrijft die je kunt gebruiken om een tekst te matchen en te lokaliseren.
Regex is intern afhankelijk van een ‘reguliere expressiemotor’. Er zijn veel implementaties van regex-engines in de wereld, die soms niet veel verschillen in complexiteit en op andere momenten weer wel. In dit artikel leggen we de innerlijke werking van regex uit met behulp van de PCRE (Perl Compatible Regex Engine). Javascript en PHP zijn voorbeelden van prominente programmeertalen die afhankelijk zijn van een PCRE.
Om regexes gemakkelijker leesbaar te maken, worden ze normaal gesproken tussen schuine strepen geplaatst, bijv. /ABC/. Deze conventie wordt in het hele artikel gerespecteerd.
Stap 1: Regex-engines begrijpen
Regex definieert alleen bepaalde patronen voor overeenkomende teksten. Om het daadwerkelijke patroonaanpassingsproces te laten lopen, moet het patroon worden geïnterpreteerd via de engine. De engine is eigenlijk verantwoordelijk voor het matchen van de regex met de invoerreeks.
Er zijn twee hoofdklassen van regex-engines: backtracking (ook wel regex-gerichte engines genoemd) en finite-state machines (ook wel tekstgerelateerde regex-engines genoemd)
Finite-state regex engines: Deze werken door het bouwen van een finite-state machine en door de karakters erin te voeren. Ze zijn over het algemeen het snelst, maar vanwege ontwerpbeperkingen is het onmogelijk om bepaalde functies erin te implementeren. POSIX ( wordt gebruikt in UNIX-systemen) is een regex-implementatie die finite-based regex gebruikt.
Backtracking regex engines: Een backtracking regex engine loopt door de regex en probeert het volgende token in de regex te matchen met het volgende teken in de tekst. Als er een overeenkomst wordt gevonden, gaat de engine door zowel de regex als de onderwerpstring. Als een token niet overeenkomt, gaat de motor terug naar een vorige positie in de regex en wordt er een ander pad geprobeerd.
Backtracking-engines zijn de meest gebruikelijke manier om regexes te implementeren vanwege geavanceerde functies die ze toestaan (bijv. Backreferences en luie kwantificatoren). PCRE, Java, PHP, R, .NET en vele andere programmeertalen gebruiken deze smaak van regex.
Stap 2: Leren hoe regex werkt
We gaan nu dieper in op de innerlijke werking van regex-engines. Ons doel in deze stap is met name om de regels te begrijpen die bepalen hoe PRCE overeenkomt met tekst.
Om te begrijpen hoe regex werkt, stellen we ons voor dat we een machine hebben die elk type string als invoer kan gebruiken. De machine bevat vooraf bepaalde instructies die hem vertelt welke snaren hij kan matchen.
Dit zijn de regels die de innerlijke werking van onze machine regelen:
- Het leest de hele invoer en probeert de tekens één voor één te vergelijken met de instructies.
- Het leest de karakters van links naar rechts.
- Als een teken overeenkomt met het gewenste patroon, wordt dit gemarkeerd als true. De machine kan dan doorgaan naar het volgende teken, de regels II) en III) worden ook opnieuw uitgevoerd. Als een teken niet overeenkomt met het patroon, wordt dit gemarkeerd als false. Als dit gebeurt, dan kun je het huidige teken verwerpen en opnieuw beginnen met matchen vanaf II).
- Als alle instructies in de machine overeenkomen, zie je een groene licht dat aangeeft dat de regex met succes is gematcht.
Deze theoretische machine die we hebben ‘uitgevonden’ is eigenlijk hoe regex-engines zoals PCRE intern werken.
Laten we beginnen met iets eenvoudigs.
The regex /cat/ zal matchen met elke string die alleen het woord ‘cat’ bevat. Kom het wel overeen met een willekeure string ‘catsareadorable’?
Eerst moet de machine weer opnieuw opgestart worden. Dit proces is ingebouwd in de meeste programmeertalen, dus u hoeft zich er geen zorgen over te maken.
Zodra we zeker weten dat onze machine is opgestart en de regex-engine draait, voeren we de string in de machine. Het eerste teken in de tekenreeks wordt dan gelezen en vergeleken het met het ingebouwde schema. ‘C’ van de regex komt overeen met ‘c’ van de tekenreeks. De ‘a’ komt overeen met de ‘t’. Je ziet nu weer een groen licht, omdat de regex overeenkomt.
Hoe zit het met het proberen van dezelfde regex voor de string “i’m a cat”?
The machine takes in the whole input and attempts to match the letter ‘i’ to ‘c’. This fails so it ditches that effort and attempts to match the apostrophe next. This will fail to the point it reaches the word ‘cat’ at the end and eventually succeeds.
De machine neemt de hele invoer op zich en probeert de letter ‘i’ te matchen met ‘c’. Als dit mislukt, dan wordt de poging gestopt en wordt er geprobeerd om de volgende apostrof te matchen. Dit mislukt totdat het einde van het woord ‘kat’ wordt bereikt,en uiteindelijk slaagt het proces.
Kortom, literals komen altijd overeen met literals. Dat betekent dat als je ‘a’ in een string hebt, dat het altijdmet een ‘a’ in de regex zal matchen.
Om meer voorbeelden te kunnen geven moeten we eerst speciale tekens introducten.
Stap 3: Introductie van metatekens (speciale karakters)
Speciale tekens zien eruit als de normale tekens die je gewend bent, maar hebben binnen regex een andere logische betekenis. Laten we naar de betekenis van een aantal tekens kijken:
- ^ : the hat (of caret) komt overeen met het begin van de regel. Het kan ook worden gebruikt voor ontkenning.
- $ : Het dollarteken komt overeen met het einde van de regel.
- \d : komt alleen overeen met cijfers. Backslash aan het begin? Dat vertelt de regex-engine dat het geen letterlijke tekenreeks is.
Gebruik alleen de bovenstaande drie tekens en overweeg de volgende regex: ^\d\d:\d\d$
Het komt alleen overeen met tekenreeksen in het formaat:: ‘digitdigit:digitdigit,’ bijv. ‘12:45’ en ‘11:30’ en niet ‘112:20’ of ‘ 12:45’ (let op de spatie aan het begin).
Belangrijke tekens in regex omvatten:
- . : het punt-teken komt overeen met elk teken.
- \w : komt overeen met 0 of meer karakters.
- \s : komt overeen met elk witruimteteken.
- * : komt overeen met 0 of meer karakters.
- + :komt overeen met 1 of meer karakters
- \ : ontsnapt aan een teken als het gelijk is aan een metateken.
- ? : maakt het voorgaande teken optioneel.
Met deze kennis kunnen we eindelijk enkele regexes creëren. De eerste is iets dat je waarschijnlijk al duizend keer bent tegengekomen: het matchen van een willekeurig e-mailadres.
E-mails zien er meestal zo uit: ‘[email protected]’, of meer in regex-achtige termen ‘[email protected]’, die vertaald kunnen worden naar: /\w@\w\.\w/.
De meeste metatekens kunnen worden genegeerd. Terwijl ‘\w’ overeenkomt met woorden, komt‘\W’ overeen met tokens dat geen woorden zijn. Dit geldt ook voor‘\D’ (non-digits), ‘\S’ (non-space), etc.
Nu we dit ook hebben behandeld, zijn er nog twee fundamentele concepten die we moeten begrijpen, voordat we de regexschool kunnen voltooien.
Stap 4: Ranges en optionele tekens definiëren
Een eenvoudige manier om dit probleem aan te pakken, is door alle tekens op te sommen die niet binnen jouw range horen (1 tot 13) en deze te negeren. Aangezien regex reeksen ondersteunt, hoeven we niet zo ver te gaan.
Met Regex kun je een range definiëren met vierkante haken‘[‘ and ‘]’.
Een range van (kleine) letters van a tot z is /[a-z]/, een range van cijfers van 0 tot 9 is /[0-9]/ etc.
Met deze ranges gaan nieuwe deuren open.
Met de kennis van de metatekens die we tot nu toe kennen en onze vierkante haken, kunnen we eindelijk een iets complexere regex schrijven. Kijk zelf o je kunt begrijpen hoe het werkt zonder de uitleg te lezen.
/^[a-zA-Z0-9]*$/
De bovenstaande regex komt alleen overeen met alfanumerieke tekenreeksen. Waarom?
De hoed (^) komt overeen met het begin van de regex. De vierkante haken vertegenwoordigen een reeks tekens die kunnen worden geaccepteerd. In dit geval accepteren we alle kleine letters ([a-z]), hoofdletters ([A-Z)] en cijfers van 0-9.
Strings zoals ‘abcde’ en ‘teslamodel5’ worden als true gemarkeerd maar strings zoals ‘mars*colony’ of ‘muzak/’ niet,
Let ook op het gebruik van de asterisk (*). Hierdoor komt de engine overeen met een of meer van de tekens binnen de range die we hebben beschreven. Als we het zouden verwijderen, zou het patroon als true gemarkeerd worden voor slechts één teken. Overigens komt deze regex ook overeen met een lege string. Kun je raden waarom?
Als we het hoedkarakter aan de regex toevoegen:/^[^a-zA-Z0-9]*$/, dan negeren we regex. Nu worden alleen niet-alfanumerieke tekenreeksen geaccepteerd.
Nu we eindelijk begrijpen hoe we de ranges moeten definiëren, is het belangrijk om te weten dat het metateken ‘\w’ een afkorting is voor /[a-zA-Z0-9_]/.
Stap 5: Overeenkomende repetitieve tekst
Als we herhalende tekens willen matchen, gebruiken we accolades: ‘{‘ en ‘}’. Technisch gezien hebben zowel de punt- als asterisk-metatekens ook te maken met herhalingen, maar accolades geven ons meer controle.
Stel dat we strings willen matchen met het patroon ‘zzz’. Zolang er drie of meer ‘zzz’s’ zijn, moet de string overeenkomen. De relevante regex ziet er als volgt uit: / z {3} /.
Het herhalingsteken kan op drie manieren worden gebruikt:
- /{x}/ : komt exact overeen met ‘x’ herhalingen.
- /{x,}/ : komt overeen met ‘x’ aantal herhalingen of meer.
- /{x,y}/ : overeenkomsten tussen ‘x’ en ‘y’ aantal herhalingen.
De tweede manier om vierkante haken te gebruiken, is door tekens te matchen waarvan je niet zeker bent.
Als we bijvoorbeeld ‘catsareadorable’ weer als voorbeeld nemen, wat als we meer inclusief willen zijn en ‘dogsareadorable’ willen matchen?
Hier is hoe vierkante haakjes je dan te hulp schieten.
[cats]
Stap 6: Een groep vastleggen
De laatste belangrijke regex-functie die je zou moeten begrijpen, is het vastleggen van groepen. We kunnen twee dingen doen: tekens als groep matchen en informatie extraheren voor verdere verwerking.
Neem bijvoorbeeld de regex / ([0-9] +) /. Hiermee wordt de langste reeks getallen geëxtraheerd en in een variabele opgeslagen.
In Perl, bijvoorbeeld.
my $value = "Bolton's number is 801-555-1234."; if ($value =~ /([0-9]+)/ { print "Found area code: $1"; // Found area code: 801 }
En in Javascript:
const value = “Nathan's number is 801-555-1234.”; const areaCode = value.match(/([0-9]+)/ )[0]; console.log("Found area code: ", areaCode) // Found area code: 801
Om het vorige voorbeeld ‘catsareadorable’ af te maken, ons doel was om ook ‘dogsareadorable’ te matchen, je zou het volgende hiervoor kunnen schrijven:
/(cats|dogs)areadorable/
De tweede manier om groepen te gebruiken, is door herhalingen op elkaar af te stemmen.
/(abc){2}/ – komt bijvoorbeeld overeen met een tekenreeks waarin ‘abc’ minstens tweemaal wordt herhaald d.w.z ‘abcabc’ of’abc000abc’ maar niet ‘abvbbc’.
Conclusie
Dat is het einde van deze tutorial. Nu je de basis onder de knie hebt, kun je jezelf uitdagen om meer te leren over regex. Het leren van regex vergt veel tijd en oefening, verwacht dus niet dat je regex binnen een aantal keer helemaal door hebt.
Geef een reactie