# Άγκυρες και διεκδικήσεις Οι άγκυρες και οι διεκδικήσεις ταιριάζουν με *θέσεις* αντί για χαρακτήρες. Ελέγχουν πού μέσα στη συμβολοσειρά βρίσκεστε, όχι τι υπάρχει εκεί. Μια απλή κλάση χαρακτήρων καταναλώνει έναν χαρακτήρα· μια άγκυρα δεν καταναλώνει κανέναν. Γι” αυτόν τον λόγο ονομάζονται *διεκδικήσεις μηδενικού πλάτους* — έχουν πλάτος μηδέν, αλλά διεκδικούν ότι πρέπει να ισχύει μια ιδιότητα. ## Αρχή και τέλος συμβολοσειράς Το `^` ταιριάζει στην αρχή της συμβολοσειράς. Το `$` ταιριάζει στο τέλος, ή ακριβώς πριν από μια τελική νέα γραμμή. ```perl "housekeeper" =~ /keeper/; # matches — 'keeper' appears somewhere "housekeeper" =~ /^keeper/; # does not match — not at start "housekeeper" =~ /keeper$/; # matches — at end "housekeeper\n" =~ /keeper$/; # matches — before the trailing newline ``` Χρησιμοποιημένα μαζί, τα `^…$` αναγκάζουν το μοτίβο να καλύψει ολόκληρη τη συμβολοσειρά: ```perl "bert" =~ /^bert$/; # matches "bertram" =~ /^bert$/; # does not match "dilbert" =~ /^bert$/; # does not match "" =~ /^$/; # matches — empty string ``` Για μια κυριολεκτική συμβολοσειρά, το `$s eq "bert"` είναι ταχύτερο και σαφέστερο. Το `^…$` δικαιολογεί τη χρήση του μόνο όταν το μεσαίο τμήμα χρησιμοποιεί πραγματικά χαρακτηριστικά regex. ## Απόλυτες έναντι σχετικών ως προς γραμμή αγκύρων Υπό τον τροποποιητή `/m`, τα `^` και `$` αγκυρώνουν σε κάθε όριο γραμμής μέσα στη συμβολοσειρά, όχι μόνο στα εξωτερικά άκρα: ```perl my $x = "There once was a girl\nWho programmed in Perl\n"; $x =~ /^Who/; # does not match — 'Who' is not at string start $x =~ /^Who/m; # matches — 'Who' is at start of second line ``` Για τα απόλυτα άκρα *ανεξαρτήτως του `/m`*, χρησιμοποιήστε τις ειδικές άγκυρες: - `\A` — απόλυτη αρχή συμβολοσειράς. - `\z` — απόλυτο τέλος συμβολοσειράς. - `\Z` — τέλος συμβολοσειράς, ή ακριβώς πριν από μια τελική νέα γραμμή. Παρόμοιο με το `$` αλλά ανεπηρέαστο από το `/m`. ```perl $x =~ /^Who/m; # matches, as above $x =~ /\AWho/m; # does not match — \A is string start, always $x =~ /girl$/m; # matches, end of first line $x =~ /girl\Z/m; # does not match — 'girl' is not at string end $x =~ /Perl\Z/m; # matches — end or before final newline $x =~ /Perl\z/m; # does not match — \z is strict end ``` Πρακτικός κανόνας: `\A` και `\z` όταν εννοείτε ολόκληρη τη συμβολοσειρά· `^` και `$` όταν εννοείτε γραμμές (συνήθως με `/m`). Τα `\A` και `\z` είναι επίσης ταχύτερα: η μηχανή ξέρει ότι περιορίζουν τη θέση, και παραλείπει τις επανειλημμένες προσπάθειες bump-along που επιτρέπει το `^`. Οι τέσσερις λειτουργίες νέας γραμμής μπορούν να συνοψιστούν σε έναν πίνακα. Επιλέξτε εκείνη που ταιριάζει με αυτό που πρέπει να σημαίνουν τα `^`, `$`, και `.` για την είσοδό σας: | Λειτουργία | Το `^` ταιριάζει σε | Το `$` ταιριάζει σε | Ταιριάζει το `.` με `\n`; | |--------------|-------------------------|------------------------------------------------|-----------------------------| | προεπιλογή | αρχή συμβολοσειράς μόνο | τέλος συμβολοσειράς (και πριν την τελική `\n`) | όχι | | `/m` | αρχή κάθε γραμμής | τέλος κάθε γραμμής | όχι | | `/s` | αρχή συμβολοσειράς μόνο | τέλος συμβολοσειράς (και πριν την τελική `\n`) | ναι | | `/sm` | αρχή κάθε γραμμής | τέλος κάθε γραμμής | ναι | Τα `\A`, `\Z`, `\z` είναι ανεπηρέαστα από οποιονδήποτε από τους δύο τροποποιητές. ## Όρια λέξεων Το `\b` ταιριάζει με μια θέση ανάμεσα σε έναν χαρακτήρα λέξης (`\w`) και έναν χαρακτήρα μη λέξης (`\W`), ή ανάμεσα σε οποιονδήποτε από τους δύο και την άκρη της συμβολοσειράς. Δεν καταναλώνει κανέναν χαρακτήρα. ```perl my $x = "Housecat catenates house and cat"; $x =~ /cat/; # matches 'cat' inside 'Housecat' $x =~ /\bcat/; # matches 'cat' inside 'catenates' $x =~ /cat\b/; # matches 'cat' inside 'Housecat' $x =~ /\bcat\b/; # matches the standalone 'cat' at end ``` Το `\B` είναι η άρνηση — μια θέση που *δεν* είναι όριο λέξης. Το `\b` εκτός `[…]` είναι όριο λέξης. Το `\b` *μέσα* στα `[…]` είναι ο χαρακτήρας *backspace*, `\x08`. Αυτή η διπλή σημασία είναι η συχνότερη παγίδα στη σύνταξη regex. Πάντοτε διαβάζετε το περιβάλλον του `\b` προτού διαβάσετε τι κάνει. ### Το `\b` και στοιχεία που ξεκινούν με `\W` — η παγίδα του `$3.75` Το `\b` απαιτεί μια μετάβαση ανάμεσα σε χαρακτήρες λέξης και μη λέξης. Αν και οι δύο πλευρές της θέσης είναι χαρακτήρες μη λέξης, δεν υπάρχει όριο. Αυτό πιάνει όσους χτίζουν μοτίβα μέσω παρεμβολής: ```perl my $item = '$3.75'; my $regex = qr/\b\Q$item\E\b/; "is $3.75 plus tax" =~ /$regex/; # does NOT match ``` Γιατί; Αφού επεξεργαστεί το `\Q$item\E`, το μοτίβο είναι `\b\$3\.75\b`. Η μηχανή αναζητά μια θέση αμέσως *πριν* το `$` όπου η μία πλευρά της θέσης είναι χαρακτήρας λέξης. Ο χαρακτήρας πριν το `$` στην είσοδο είναι ένα διάστημα (μη λέξη), και το ίδιο το `$` είναι επίσης μη λέξη. Δεν υπάρχει μετάβαση λέξης/μη λέξης· το `\b` αποτυγχάνει. Για να αγκυρώσετε την αρχή του `$item` ανεξαρτήτως του πρώτου του χαρακτήρα: ```perl my $regex = qr/(?:^|\s)\Q$item\E(?:\s|$)/; ``` Χρησιμοποιήστε άγκυρες άκρων συμβολοσειράς ή λευκού χαρακτήρα όταν το παρεμβαλλόμενο περιεχόμενο μπορεί να ξεκινά με χαρακτήρα μη λέξης. Το `\b` είναι για περιβάλλοντα όπου *ξέρετε* ότι το περιβάλλον κείμενο μοιάζει με λέξη. ### Παραλλαγές ορίου λέξης Unicode Το απλό `\b` ακολουθεί τον ορισμό `\w`/`\W`, ο οποίος υπό Unicode καλύπτει γράμματα, ψηφία, σημάδια, και συνδετική στίξη. Το πραγματικό κείμενο — φυσική γλώσσα με αποστρόφους, παύλες, αριθμούς με κόμματα — απαιτεί περισσότερη απόχρωση. Η Perl παρέχει τέσσερις σημασιολογικές παραλλαγές ορίου: | Όριο | Σημασία | |-----------|-------------------------------------------------------------| | `\b{wb}` | Όριο λέξης Unicode — χειρίζεται `don't`, `state-of-the-art` | | `\b{sb}` | όριο πρότασης | | `\b{lb}` | αλλαγή γραμμής (κατάλληλο για αναδίπλωση γραμμών) | | `\b{gcb}` | όριο συμπλέγματος γραφημάτων (ίδιο αποτέλεσμα με το `\X`) | ```perl "don't" =~ /.+?\b{wb}/x; # matches whole word — apostrophe is inside "don't" =~ /.+?\b/x; # stops at the apostrophe — plain \b splits ``` Για επεξεργασία φυσικής γλώσσας, τα `\b{wb}` και `\b{sb}` είναι σχεδόν πάντα αυτό που θέλετε. Το απλό `\b` είναι για περιβάλλοντα αναγνωριστικών ASCII. ## `\G` — η θέση της τελευταίας αντιστοιχίας Το `\G` αγκυρώνει στη θέση όπου τελείωσε η προηγούμενη επιτυχημένη αντιστοιχία `/g` στην ίδια συμβολοσειρά. Αυτό είναι που κάνει αξιόπιστη την τμηματοποίηση με `/g` σε βρόχους `while`. ```perl my $s = "12abc34"; while ($s =~ /\G(\d+|[a-z]+)/gc) { print "got '$1'\n"; } # prints: '12', 'abc', '34' ``` Χωρίς `\G`, το ίδιο μοτίβο θα ξανασάρωνε από όπου ταίριαξε πρώτα, παραλείποντας χαρακτήρες που δεν χωρούσαν — χάνοντας σιωπηλά δεδομένα. Ο τροποποιητής `/gc` διατηρεί τη θέση σε αποτυχία· καλύπτεται στο κεφάλαιο [modifiers](modifiers.md). ### Γιατί έχει σημασία το `\G`: η επιλογή συγχρονισμού ή παράλειψης Το επεξεργασμένο παράδειγμα του Friedl. Βρείτε κάθε πεντάψηφιο ZIP code που ξεκινά με `44` σε μια κολλημένη συμβολοσειρά: ```perl my $s = "06192054410-44272-13901-44106-22134"; while ($s =~ /44(\d{3})/g) { print "got 44$1\n"; } ``` Χωρίς `\G`, το bump-along της μηχανής βρίσκει ευχαρίστως τα `44272` και `44106` σωστά *καθώς επίσης και* το `44272` από διαφορετική θέση εκκίνησης οποτεδήποτε απορρίπτεται μια προηγούμενη αντιστοιχία. Η έξοδος είναι «η σωστή απάντηση συν επιπλέον λάθος». Με `\G` σε κάθε επανάληψη, η μηχανή δεν μπορεί να κάνει bump-along σε αποτυχία — πρέπει να συνεχίσει *ακριβώς* εκεί που τελείωσε η τελευταία αντιστοιχία. Μια αποτυχία τερματίζει τον βρόχο: ```perl while ($s =~ /\G\D*(44\d{3})/gc) { print "got $1\n"; } ``` Το `\G` ουσιαστικά απενεργοποιεί το bump-along, κάτι που απαιτεί η συγχρονισμένη τμηματοποίηση. Ο γενικός κανόνας: **αν η ορθότητα του βρόχου σας εξαρτάται από το ότι κάθε αντιστοιχία είναι γειτονική με την τελευταία, χρειάζεστε το `\G`**. Το `\G` διαβάζει την τιμή που επιστρέφει η `pos($string)`. Η ρητή τοποθέτηση του `pos` επανεκκινεί την άγκυρα. Στην πρώτη επανάληψη ενός βρόχου `/g` (ή σε οποιοδήποτε μοτίβο δεν έχει ακόμη αντιστοιχιστεί με αυτή τη συμβολοσειρά), το `\G` ισοδυναμεί με `\A`. ## Πρόσθια διεκδίκηση Η πρόσθια διεκδίκηση ελέγχει τι ακολουθεί χωρίς να το καταναλώνει. Η θετική πρόσθια διεκδίκηση είναι `(?=…)`: ```perl my $x = "I catch the housecat 'Tom-cat' with catnip"; $x =~ /cat(?=\s)/; # matches 'cat' in 'housecat' — space follows $x =~ /cat(?!\s)/; # matches 'cat' in 'catch' — no space follows ``` Το `(?!…)` είναι αρνητική πρόσθια διεκδίκηση: αντιστοιχεί μόνο αν το εσωτερικό μοτίβο *δεν* ισχύει. Μηδενικού πλάτους, όπως ακριβώς τα `^` και `$`. ```perl "foobar" =~ /foo(?!bar)/; # does not match "foobaz" =~ /foo(?!bar)/; # matches ``` ### Επεξεργασμένο παράδειγμα πρόσθιας διεκδίκησης: ψηφία που δεν ακολουθούνται από τελεία Μια ερώτηση που ακούγεται φυσική αλλά είναι πιο δύσκολη από όσο φαίνεται: «ταίριασε ακολουθίες ψηφίων που δεν ακολουθούνται από τελεία.» Η πρώτη απόπειρα: `\d+(?!\.)`. Εφαρμόστε τη στο `OH 44272`: ```perl "OH 44272" =~ /\d+(?!\.)/; # MATCHES, matches '44272' ``` Αυτό φαίνεται σωστό, αλλά για λάθος λόγο. Το άπληστο `\d+` άρπαξε πρώτα ολόκληρη την ακολουθία ψηφίων `44272`· ο επόμενος χαρακτήρας είναι το τέλος της συμβολοσειράς (όχι `.`), οπότε το `(?!\.)` πέτυχε αμέσως — η πρόσθια διεκδίκηση τυχαία ίσχυσε χωρίς να χρειαστεί ποτέ η μηχανή να υποχωρήσει. Η *διαισθητική* ανάγνωση «η ακολουθία ψηφίων τελειώνει με κάτι άλλο πέρα από τελεία» ικανοποιείται για την περίπτωση τελικών ψηφίων, αλλά το επόμενο παράδειγμα δείχνει τι συμβαίνει όταν δεν ικανοποιείται. Τώρα εφαρμόστε τη στο `4423.45`: ```perl "4423.45" =~ /\d+(?!\.)/; # matches '442', not '4423' ``` Η πρώτη απόπειρα της μηχανής: το `\d+` ταιριάζει με `4423`. Το `(?!\.)` ελέγχει τον επόμενο χαρακτήρα: είναι `.`, η αρνητική πρόσθια διεκδίκηση αποτυγχάνει. Η μηχανή οπισθοχωρεί: το `\d+` ταιριάζει με `442`, ο επόμενος χαρακτήρας είναι `3`, όχι `.`, επιτυχία. Το μοτίβο ταιριάζει με `442` — *όχι* με ολόκληρη την ακολουθία ψηφίων πριν την τελεία. Ο άπληστος ποσοδείκτης ήταν διατεθειμένος να υποχωρήσει για να πετύχει η πρόσθια διεκδίκηση. Η διόρθωση: απαιτήστε από την πρόσθια διεκδίκηση να απορρίπτει επίσης άλλα ψηφία, ώστε η ακολουθία να μην μπορεί να υποχωρήσει σε «ψηφίο ακολουθούμενο από ψηφίο». ```perl "4423.45" =~ /\d+(?![\d.])/; # matches '45' — correct ``` Το `\d+(?![\d.])` διαβάζεται ως «ταίριασε ακολουθία ψηφίων που δεν ακολουθείται ούτε από άλλο ψηφίο ούτε από τελεία». Ο άπληστος ποσοδείκτης ακόμη επεκτείνεται μεγιστοτικά, αλλά η πρόσθια διεκδίκηση τώρα αρνείται να πετύχει στη μέση μιας ακολουθίας ψηφίων, οπότε οι μόνες θέσεις που ταιριάζουν είναι τα πραγματικά τέλη ακολουθιών ψηφίων. Το μάθημα γενικεύεται: **μια πρόσθια διεκδίκηση που αποκλείει μόνο έναν χαρακτήρα μπορεί να ηττηθεί από έναν άπληστο ποσοδείκτη που υποχωρεί**. Πάντα συμπεριλαμβάνετε τους χαρακτήρες του *ατόμου* με το οποίο ταιριάζει ο ποσοδείκτης στην άρνηση της πρόσθιας διεκδίκησης. Μια σχετική περίπτωση: αρχική αρνητική πρόσθια διεκδίκηση και το bump-along. ```perl "cattle" =~ /(?!cat)\w+/; # MATCHES, captures 'cattle' ``` Η πρόσθια διεκδίκηση `(?!cat)` απορρίπτει τη θέση 0 (όπου θα ταίριαζε το `cat`). Η μηχανή κάνει bump-along: η θέση 1 ξεκινά με `attle`, όπου το `(?!cat)` πετυχαίνει, και το `\w+` ταιριάζει με `attle`. Για να επιβάλετε «η πρώτη λέξη που δεν αρχίζει με `cat`», αγκυρώστε την πρόσθια διεκδίκηση σε όριο λέξης: ```perl "cattle" =~ /\b(?!cat)\w+/; # does not match ``` Τώρα η πρόσθια διεκδίκηση εφαρμόζεται σε κάθε όριο λέξης, που στο `cattle` είναι μόνο η θέση 0. ## Οπίσθια διεκδίκηση Η οπίσθια διεκδίκηση ελέγχει τι προηγήθηκε. Η θετική οπίσθια διεκδίκηση είναι `(?<=…)`: ```perl "cats and dogs" =~ /(?<=and )dogs/; # matches 'dogs' after 'and ' ``` Η αρνητική οπίσθια διεκδίκηση είναι `(?…)` | ναι | ναι | ΟΧΙ | ΟΧΙ | ΟΧΙ | Στην ουσία, μόνο η PCRE2 (και άλλες μηχανές προερχόμενες από την Perl εκτός αυτού του πίνακα — Java, Python, .NET) υποστηρίζει διεκδικήσεις περιβάλλοντος. Τα εργαλεία POSIX και η RE2 / Go απλώς δεν τις διαθέτουν. Μοτίβα που βασίζονται σε διεκδικήσεις περιβάλλοντος δεν μεταφέρονται μέσω αυτού του χάσματος. ## Σύνοψη | Άγκυρα | Ταιριάζει με | |-----------|----------------------------------------------------------------| | `^` | αρχή συμβολοσειράς, ή αρχή γραμμής υπό `/m` | | `$` | τέλος συμβολοσειράς (πριν την τελική `\n`), ή γραμμής υπό `/m` | | `\A` | απόλυτη αρχή συμβολοσειράς | | `\z` | απόλυτο τέλος συμβολοσειράς | | `\Z` | τέλος συμβολοσειράς ή πριν την τελική `\n`, αγνοεί το `/m` | | `\b` | όριο λέξης | | `\B` | μη όριο λέξης | | `\b{wb}` | όριο λέξης Unicode | | `\b{sb}` | όριο πρότασης | | `\b{lb}` | αλλαγή γραμμής | | `\b{gcb}` | όριο συμπλέγματος γραφημάτων | | `\G` | τέλος προηγούμενης αντιστοιχίας `/g` (ή `\A` αν δεν υπάρχει) | | `(?=…)` | θετική πρόσθια διεκδίκηση | | `(?!…)` | αρνητική πρόσθια διεκδίκηση | | `(?<=…)` | θετική οπίσθια διεκδίκηση | | `(?…)`, ειδικά ρήματα ελέγχου οπισθοχώρησης. - Το κεφάλαιο [cross-engine](cross-engine.md) — πλήρης πίνακας συμβατότητας αγκύρων και διεκδικήσεων περιβάλλοντος. - [`split`](../../p5/core/perlfunc/split.md) — διαχωρισμός σε θέσεις μηδενικού πλάτους. - [`pos`](../../p5/core/perlfunc/pos.md) — ανάγνωση ή ορισμός της θέσης όπου αγκυρώνει το `\G`.