Regular expressions in perl: verschil tussen versies
(12 tussenliggende versies door dezelfde gebruiker niet weergegeven) | |||
Regel 144: | Regel 144: | ||
===Character classes | ===Character classes=== | ||
Zie ook [https://perldoc.perl.org/perlre#Character-Classes-and-other-Special-Escapes] | Zie ook [https://perldoc.perl.org/perlre#Character-Classes-and-other-Special-Escapes] | ||
Vergelijkbaar qua notatie, maar iets anders qua betekenis zijn de character classes. Daar kunnen verchillende characters voldoen. | Vergelijkbaar qua notatie, maar iets anders qua betekenis zijn de character classes. Daar kunnen verchillende characters voldoen. | ||
. Match een willekeurig character (dat kan ook '.' zijn). Wil je dat er echt een punt staat, dan geef je dat aan met \ | . Match een willekeurig character (dat kan dus ook een echte '.' zijn). Wil je dat er echt alleen dat er een punt staat, dan geef je dat aan met '''<nowiki>\.</nowiki>''' | ||
\s Match a whitespace character | \s Match a whitespace character (spatie, tab, newline) | ||
\d Match a decimal digit character | \d Match a decimal digit character | ||
\w Match a "word" character (alphanumeric plus "_", plus other connector punctuation chars plus Unicode marks) | \w Match a "word" character (alphanumeric plus "_", plus other connector punctuation chars plus Unicode marks) | ||
===Quantifiers=== | ===Quantifiers=== | ||
Regel 172: | Regel 170: | ||
$dier =~ s/a/x/ ; # $dier is "schxap" ; alleen de eerste a wordt vervangen | $dier =~ s/a/x/ ; # $dier is "schxap" ; alleen de eerste a wordt vervangen | ||
$dier =~ s/aa/x ; # $dier is "schxp" ; 2 characters worden door 1 character vervangen | $dier =~ s/aa/x/ ; # $dier is "schxp" ; 2 characters worden door 1 character vervangen | ||
$dier =~ s/a+/x; # $dier is "schxp" ; maar zou ook "schaaaaaaaap" vervangen door "schxp" | $dier =~ s/a+/x/; # $dier is "schxp" ; maar zou ook "schaaaaaaaap" vervangen door "schxp" | ||
$dier =~ s/s\w+p/x/ ; # dat matcht, de hele string matcht, dus $dier is nu 'x' | $dier =~ s/s\w+p/x/ ; # dat matcht, de hele string matcht, dus $dier is nu 'x' | ||
Regex Examples | Regex Examples | ||
/Ax*A/ matcht met AA, AxA, AxxA, AxxxA, ….. | /Ax*A/ matcht met AA, AxA, AxxA, AxxxA, ….. | ||
/Ax+A/ matcht met AxA, AxxA, AxxxA, ….. | /Ax+A/ matcht met AxA, AxxA, AxxxA, ….. dus niet met AA | ||
/Ax?A/ matcht met AA, AxA | /Ax?A/ matcht met AA, AxA dus niet met AxxA | ||
/Ax{1, 3}A/ matcht met AxA, AxxA, AxxxA | /Ax{1, 3}A/ matcht met AxA, AxxA, AxxxA dus niet met AA, AxxxxA | ||
/Ax{2, }A/ matcht met AxxA, AxxxA, ….. | /Ax{2, }A/ matcht met AxxA, AxxxA, ….. dus niet met AxA | ||
/Ax{4}A/ matcht met AxxxxA | /Ax{4}A/ matcht met AxxxxA dus niet met AA, AxA, AxxA, AxxxA, AxxxxxA | ||
===Character classes=== | ===Character classes=== | ||
Regel 189: | Regel 187: | ||
Wat binnen square brackets staat duidt een character class aan. Elke letter die aan die klasse voldoet geeft een match. | Wat binnen square brackets staat duidt een character class aan. Elke letter die aan die klasse voldoet geeft een match. | ||
[abc] betekent: op deze | [abc] betekent: op deze plaats moet een letter a, b '''of''' c volgen | ||
Als de character class '''begint''' met een dakje, dan mogen deze characters juist niet voorkomen. | |||
[^abc] is dus: 'alles mag hier staan, behalve a, b of c' | |||
Je ziet de betekenis van het dakje afhankelijk van waar het geplaatst is (vooraan in de string, of vooraan in de character class) | |||
De klasse kan ook een range bevatten, de laagste en hoogste characters gescheiden door een hyphen (streepje). [a-c] is dus equivalent aan [abc] | De klasse kan ook een range bevatten, de laagste en hoogste characters gescheiden door een hyphen (streepje). [a-c] is dus equivalent aan [abc] | ||
Het wordt bijvoorbeeld gebruikt om aan te geven dat elke kleine letter matcht | Het wordt bijvoorbeeld gebruikt om aan te geven dat elke kleine letter matcht | ||
if ($dier =~ /[a-z]/) | if ($dier =~ /[a-z]/) {..} | ||
Mag het ook een grote letter zijn dan wordt het | Mag het ook een grote letter zijn dan wordt het | ||
if ($dier = /[a- | if ($dier =~ /[a-zA-Z]) {..} | ||
====Voorbeeld==== | |||
Voorbeeld hoe check je of een variabele een geldig getal bevat (Engelse notatie, dus met een digitale punt) en niets meer dan dat. | |||
Poging 1: elk cijfer voldoet, maar het mogen er meerdere zijn. Let op de plus quantifier, er mogen dus meerdere cijfers achter elkaar volgen. | |||
if ($getal =~ /[0-9]+/) {..} | |||
Poging 2: stel een decimale punt mag ook. (Let op de <b><nowiki>\.</nowiki></b> staat voor letterlijk een punt oftewel ASCII character \x20) | |||
if ($getal =~ /[0-9\.]+/) {..} | |||
Poging 1 | Poging 3: stel de opdracht luidt: er mag maar 1 getal staan en niets meer dan dat. | ||
if ($getal = /[0-9]+/) | <br>De string moet dan dus met dit patroon beginnen, en met dit patroon eindigen | ||
if ($getal =~ /^[0-9\.]+$/) {..} | |||
Toch is dit nog niet goed want nu mag de punt meerdere keren voorkomen. Beter is dan | |||
if ($getal = /[0-9\.] | if ($getal =~ /$[0-9]+\.?[0-9]*$/) {..} | ||
Let op de quantifiers: | |||
*De eerste is een plus, er moet minstens 1 cijfer zijn | |||
*De decimale punt zelf hoeft niet, aangegeven met een vraagteken. | |||
*Na de decimale punt hoeven geen cijfers meer te volgen, maar het mag wel. | |||
Als er geen decimale punt is dan zijn is de tweede class gewoon een prolongatie van de eerste. | |||
Als de strenge regel luidt 'als een decimale punt aanwezig moet er minstens nog 1 cijfer volgen', dan wordt het | |||
if ($getal =~ /^[0-9]+(\.[0-9]+)?$/) {..} | |||
if ($getal = / | |||
Overigens kan je [0-9] ook vervangen door '''<nowiki>\d</nowiki>''' (decimal) | |||
In OBK scripts staat bijvoorbeeld | |||
if ($diameter =~ /^[\d\.]+$/) # if numeric | |||
Dit is dus niet waterdicht, diameter '1.2.3' zou ook matchen. | |||
==Advies== | ==Advies== |
Huidige versie van 27 jan 2024 om 14:18
Hier een intro over regular expressions in perl. Een eerste begin, later volgt meer.
Regular expressions (vaak afgekort tot regex) zijn patronen voor tekstherkenning. Tekst in de ruimste zin van het woord. Dat kan ook een string met alleen cijfers of speciale tekens zijn. Ook talen als python en javascript doen veel met regex. De syntax is daar vaak afgeleid van de perl syntax.
ChatGPT: De eerste programmeertaal die reguliere expressies (regex) gebruikte, was Ken Thompson's implementatie van het zoekprogramma QED (Quick Editor) rond 1967. Thompson, die ook een van de ontwikkelaars van UNIX was, integreerde reguliere expressies in QED op basis van eerdere concepten. [..] Perl staat bijzonder bekend om zijn uitgebreide ondersteuning voor reguliere expressies.
Basics
Twee veel voorkomende toepassingen:
- om te checken of een patroon in een variabele voorkomt. Anders gezegd, is er een match? Resultaat is 1 (true) of 0 (false).
- om een (deel van) een string door een andere string te vervangen, oftewel substitute. De string waar je de regexp op loslaat kan dus veranderen, maar alleen als het patroon precies matcht, precies aanwezig is.
match
Voorbeelden van matching:
Stel je wilt een variable testen als volgt.
if ($dier =~ m/aap/) { doe iets } else { doe iets anders }
{ doe iets } hierna aan te duiden als { .. } wat geen perl is
De ‘m’ voor de eerste slash geeft aan dat je alleen wilt matchen maar niets wilt veranderen. Anders zou er de ‘s’ van substitute staan.
Vaak wordt de ‘m’ weggelaten, die is default.
if ($dier =~ m/aap/) { .. }
Dit is dus hetzelfde:
if ($dier =~ /aap/) { .. }
Het patroon staat normaal tussen forward slashes, maar je kan andere tekens gebruiken (als het patroon zelf veel slashes bevat is dat soms leesbaarder., maar bij hoge uitzondering).
if ($dier =~ /aap/) { .. } if ($dier =~ #aap#) { .. }
is dus hetzelfde. Maar is hier alleen verwarrend, want het patroon ‘aap’ bevat helemaal geen slashes. In 99% van de gevallen is de forward slash de norm.
Ook de omgekeerde test kan (of de substring niet voorkomt) kan.
if ($dier !~ /aap/) { .. }
Als de string niet 'aap' bevat doe dan iets.
=~ en !~ zijn dus elkaars tegengestelde.
substitute
Bij een substitute staat er eerst een patroon, dan aansluitend wat er voor in de plaats moet komen. In totaal dus 3 slashes.
$dier =~ s/aap/chimp/ ;
Det zal matchen als $dier ‘aap’ bevat maar ook als dier ‘schaap’ bevat. Niet als dier ‘Aap’ bevat.
Dus voorbeelden van een variable en wat er na de regexp in staat.
$dier = 'aap' ; $dier =~ s/aap/chimp/ ;
$dier bevat nu 'chimp'
$dier = 'Aap' ; $dier =~ s/aap/chimp/ ;
$dier bevat nog steeds 'Aap'
$dier = 'schaap' ; $dier =~ s/aap/chimp/ ;
$dier bevat nu 'schchimp'
Het luistert dus nauw.
Wil je dat de regexp ook matcht met 'Aap' dan eindig je de regex (aansluitend) met een extra 'i' achter de laatste slash. Die modifier staat voor case insensitive.
$dier = 'Aap' ; $dier =~ s/aap/chimp/i ;
$dier bevat nu 'chimp'
Er zijn speciale tekens om aan te geven dat de match aan het begin van de string moet zijn: tilde, of aan het einde: dollar. Let op: die tekens betekenen alleen dit als ze aan het begin (tilde) of eind staan (dollar), anders betekenen ze wat anders.
$dier = 'schaap' ; $dier =~ s/^aap/chimp/ ;
$dier bevat nog steeds 'schaap'
$dier = 'schaapherder' ; $dier =~ s/aap$/chimp/ ;
$dier bevat nog steeds 'schaapherder', want de te vinden substring staat niet aan het einde van de string
$dier = 'aap ' ; $dier =~ s/aap$/chimp/ ;
$dier bevat nog steeds 'aap ', want de string eindigt op een spatie
modifiers i, g en x
De /i voor case insensitive kwam al langs.
Een andere modifier die in OBK scripts vaak voorkomt is /g. Dit staat voor global, oftewel substitute zo vaak als een substring voorkomt.
Een enkel keer staat er /x In zo'n geval kunnen er spaties in de regex staan om de regex in stukjes te breken voor de leesbaarheid. Wil je echt op de aanwezigheid van een spatie testen, dan moet je dat anders gecodeerd aangeven.
Meerdere modifiers kunnen gecombineerd worden, de volgorde is niet belangrijk.
$dier = 'aap aap' ; $dier =~ s/aap$/chimp/ ;
$dier bevat 'chimp app'
$dier = 'aap aap' ; $dier =~ s/aap/chimp/g ;
$dier bevat 'chimp chimp'
Het aantal modifiers dat perl kent is heel erg groot. Maar in OBK scripts zijn 'i' en 'g' de belangrijkste.
Voor een complete referentie zie dit overzicht.
Hoe definieer je een patroon
Escape sequences
Zie ook [1]
Vele mogelijkheden, maar in OBK vooral: tabs en new-line characters.
De backslash is hier dus om aan te geven dat er een symbool volgt dat je niet letterlijk wilt matchen, substituten (of afdrukken in een print).
Voorbeeld
print "\n" ; # hier wordt geen backslash geprint, en geen kleine letter n, maar een new-line character (wat een ander character is in Linux dan in Windows) \t tab (HT, TAB) \n newline (LF, NL)
Character classes
Zie ook [2]
Vergelijkbaar qua notatie, maar iets anders qua betekenis zijn de character classes. Daar kunnen verchillende characters voldoen.
. Match een willekeurig character (dat kan dus ook een echte '.' zijn). Wil je dat er echt alleen dat er een punt staat, dan geef je dat aan met \. \s Match a whitespace character (spatie, tab, newline) \d Match a decimal digit character \w Match a "word" character (alphanumeric plus "_", plus other connector punctuation chars plus Unicode marks)
Quantifiers
Zie ook [3]
Geef aan hoe vaak een patroon mag of moet voorkomen. Een patroon kan een enkele karakter zijn of een hele string aan tekens.
* Match 0 or more times (m.a.w. het patroon kan er zijn maar het hoeft niet) + Match 1 or more times (m.a.w. het patroon moet er minimaal 1 keer zijn, maar het mag ook meerdere keren zijn) ? Match 1 or 0 times {n} Match exactly n times {n,} Match at least n times {,n} Match at most n times {n,m} Match at least n but not more than m times
$dier = "schaap" ;
$dier =~ s/a/x/ ; # $dier is "schxap" ; alleen de eerste a wordt vervangen $dier =~ s/aa/x/ ; # $dier is "schxp" ; 2 characters worden door 1 character vervangen $dier =~ s/a+/x/; # $dier is "schxp" ; maar zou ook "schaaaaaaaap" vervangen door "schxp"
$dier =~ s/s\w+p/x/ ; # dat matcht, de hele string matcht, dus $dier is nu 'x'
Regex Examples /Ax*A/ matcht met AA, AxA, AxxA, AxxxA, ….. /Ax+A/ matcht met AxA, AxxA, AxxxA, ….. dus niet met AA /Ax?A/ matcht met AA, AxA dus niet met AxxA /Ax{1, 3}A/ matcht met AxA, AxxA, AxxxA dus niet met AA, AxxxxA /Ax{2, }A/ matcht met AxxA, AxxxA, ….. dus niet met AxA /Ax{4}A/ matcht met AxxxxA dus niet met AA, AxA, AxxA, AxxxA, AxxxxxA
Character classes
Wat binnen square brackets staat duidt een character class aan. Elke letter die aan die klasse voldoet geeft een match.
[abc] betekent: op deze plaats moet een letter a, b of c volgen
Als de character class begint met een dakje, dan mogen deze characters juist niet voorkomen.
[^abc] is dus: 'alles mag hier staan, behalve a, b of c'
Je ziet de betekenis van het dakje afhankelijk van waar het geplaatst is (vooraan in de string, of vooraan in de character class)
De klasse kan ook een range bevatten, de laagste en hoogste characters gescheiden door een hyphen (streepje). [a-c] is dus equivalent aan [abc]
Het wordt bijvoorbeeld gebruikt om aan te geven dat elke kleine letter matcht
if ($dier =~ /[a-z]/) {..}
Mag het ook een grote letter zijn dan wordt het
if ($dier =~ /[a-zA-Z]) {..}
Voorbeeld
Voorbeeld hoe check je of een variabele een geldig getal bevat (Engelse notatie, dus met een digitale punt) en niets meer dan dat.
Poging 1: elk cijfer voldoet, maar het mogen er meerdere zijn. Let op de plus quantifier, er mogen dus meerdere cijfers achter elkaar volgen.
if ($getal =~ /[0-9]+/) {..}
Poging 2: stel een decimale punt mag ook. (Let op de \. staat voor letterlijk een punt oftewel ASCII character \x20)
if ($getal =~ /[0-9\.]+/) {..}
Poging 3: stel de opdracht luidt: er mag maar 1 getal staan en niets meer dan dat.
De string moet dan dus met dit patroon beginnen, en met dit patroon eindigen
if ($getal =~ /^[0-9\.]+$/) {..}
Toch is dit nog niet goed want nu mag de punt meerdere keren voorkomen. Beter is dan
if ($getal =~ /$[0-9]+\.?[0-9]*$/) {..}
Let op de quantifiers:
- De eerste is een plus, er moet minstens 1 cijfer zijn
- De decimale punt zelf hoeft niet, aangegeven met een vraagteken.
- Na de decimale punt hoeven geen cijfers meer te volgen, maar het mag wel.
Als er geen decimale punt is dan zijn is de tweede class gewoon een prolongatie van de eerste.
Als de strenge regel luidt 'als een decimale punt aanwezig moet er minstens nog 1 cijfer volgen', dan wordt het
if ($getal =~ /^[0-9]+(\.[0-9]+)?$/) {..}
Overigens kan je [0-9] ook vervangen door \d (decimal)
In OBK scripts staat bijvoorbeeld
if ($diameter =~ /^[\d\.]+$/) # if numeric
Dit is dus niet waterdicht, diameter '1.2.3' zou ook matchen.
Advies
Nog meer dan de rest van perl is het heel makkelijk om de fout in te gaan met regexs. De match of substitute doet niet wat je wil, match te vaakt of juist te weinig. Je krijgt foutboodschappen, die lastig te duiden zijn. Of alles lijkt in orde, geen foutboodscaheppen, maar het script produceert verkeerde resultaten.
Dit is voor een deel te ondervangen met een zorgvuldige werkwijze: bouw in het algemeen perl maar zeker regex code in stukjes op, en test voordurend (tussen)resultaten.
Voorbeeld:
$dier is 'schaap' ; print "A dier is $dier\n" ; $dier =~ s/aap/chimp/ ; print "B dier is $dier\n" ; exit ;
A staat hier voor 'voor uitvoering van de regex' B staat hier voor 'na uitvoering van de regex'
Dit levert op
A dier is 'schaap' B dier is 'chimp'
Het kan helpen om het script daarna meteen te stoppen (met perl 'exit'). Dan zijn dit de laatste regels output. Natuurlijk deze test code daarna wel weer verwijderen. Of in commentaar zetten, als je het vaker zal gebruiken, en snel weer wil activeren.
Als je de hele invoer wilt verwerken (want je het regex probleem doet zich maar in een paar regels voor) kan het helpen om de debug code maar voor een aantal regels aan te roepen. Anders blijft de output over het scherm scrollen.
if (($dier =~ 'aap') && ($debug_test1 ++ < 10)) # numerieke variabele begint altijd op 0, en wordt hier steeds met 1 opgehoogd { print "A dier is $dier\n" ; } $dier =~ s/aap/chimp/ ; print "B dier is $dier\n" ; }