PERL TUTORIAL

ΠΑΝΕΠΙΣΤΗΜΙΟ ΜΑΚΕΔΟΝΙΑΣ
ΤΜΗΜΑ ΕΦΑΡΜΟΣΜΕΝΗΣ ΠΛΗΡΟΦΟΡΙΚΗΣ
ΕΡΓΑΣΤΗΡΙΟ ΠΑΡΑΛΛΗΛΗΣ ΚΑΤΑΝΕΜΗΜΕΝΗΣ ΕΠΕΞΕΡΓΑΣΙΑΣ

ΠΕΡΙΕΧΟΜΕΝΑ

Τι χρειάζεται να ξέρεις:
Τι χρειάζεται να έχεις:

Πώς να χρησιμοποιήσεις το διδακτικό – εγχειρίδιο
Μια μικρή εισαγωγή στην Perl
Τι είναι η Perl
Τι είναι η ενεργός(active)-Perl; Είναι οι άλλες Perl αδρανείς;
Τι μπορώ να κάνω με την Perl;
Το εγχειρίδιο. Το ταξίδι αρχίζει.
Η πρώτη σου φορά
Όταν δεν είναι …;
Υποθέτοντας ότι όλα είναι έτοιμα.
Shebang
Μεταβλητές:
Βαθμωτές τιμές
Τα $ % @ είναι καλά
Τύποι δεδομένων
Παρεμβολή μεταβλητών
Αυτόματη Αύξηση – Μείωση
Χαρακτήρας διαφυγής
Στοιχειοσειρές και αυξήσεις
Εκτύπωση
Υπορουτίνες – Μια πρώτη ματιά
Συγκρίσεις
Εντολή IF
Η αλήθεια μιας έκφρασης σύμφωνα με την Perl
Ισότητα και Perl
Όλες οι ισότητες δεν είναι ίσες : Αριθμητικά κατά Αλφαριθμητικών(string)
Σχεσιακοί τελεστές
Περισσότερα για το IF
Elsif
Εισαγωγή δεδομένων
STDIN και άλλοι τελεστές αρχείων
Chop
Ασφαλές Chopping με Chomp
Πίνακες
Λίστες- Αγέλες—Τι είναι πίνακες;
Βασικές λειτουργίες πινάκων
Στοιχεία Πινάκων
Πώς να αναφερθείς στα στοιχεία ενός πίνακα
Εναλλακτικοί τρόποι προσπέλασης πινάκων
Επανάληψη με for loop
For loops with, ο τελεστής εύρους
Foreach
Το δυσφημισμένο ‘$-’
Πρόωρος τερματισμός επανάληψης
Περισσότερος έλεγχος του πρόωρου τερμαρισμού:ετικέτες(labels)
Μεταβολή στοιχείων ενός πίνακα
Παίζοντας με πίνακες
Πίνακας συναρτήσεων χειρισμού πίνακα
Splice
Μεταβλητές Διαγραφής
Έλεγχος ύπαρξης
Βασικές Κανονικές Εκφράσεις
Εισαγωγή
Ευαισθησία κανονικών εκφράσεων
Κατηγορίες Χαρακτήρων
Ταίριασμα Ειδικών Δεικτών
Λογική αντιστροφή του regex
Επιστροφή τιμής ταιριάσματος
Ειδικοί χαρακτήρες κανονικών εκφράσεων
Η διαφορά μεταξύ + και *
Αντικατάσταση και ακόμη περισσότερες δυνατότητες των κανονικών εκφράσεων
Βασικές Αλλαγές
\w
Αντικατάσταση με αυτά που έχουν βρεθεί :
Χ
Περισσότερο Ταίριασμα
Ξανά παρενθέσεις : OR
(? : OR Efficiency)
Ταίριασμα ειδικών τιμών με…
Pre, Post και Match
RHS Εκφράσεις
/e
/ee
Ένα έτοιμο παράδειγμα : Αλλαγή Ημερομηνίας
Split και Join (Χωρίζω και Ενώνω)
Splitting – Χώρισμα
Ένα καλό FAQ
Τι χρειάζεται το Humprty Dumpty : Join - Ένωση
Ανακεφαλαίωση, αλλά με κάποιες καινούριες functions.
Randomness-Τυχαιότητα
Συνένωση
Αρχεία
Άνοιγμα
Ένα ασυγχώρητο λάθος
\\ or / in pathnames -- Επιλογή σου
 Διαβάζοντας ένα αρχείο
Γράφοντας σε ένα αρχείο
Ένα απλό γράψιμο
Προσάρτηση
@ARGV : Ορίσματα Εντολής Γραμμής
Τροποποιώντας ένα αρχείο με $^Ι
‘$ / ’ Αλλάζει ότι διαβάζει μέσα σε ‘$_’
HERE Docs
Διάβασμα Καταλόγων Αρχείων
Globbing
Readdir : Πως να διαβάσεις από καταλόγους αρχείων
Συνειρμικοί πίνακες (hash tables)
Τα Βασικά
Hash (Αναξιόπιστα ή Ανεπιθύμητα Δεδομένα) στην πράξη
Πότε πρέπει να χρησιμοποιείς hashes
Hash Hacking Functions
Προσπέλαση του hash
Περισσότερη Προσπέλαση σε Hash : Επανάληψη, Κλειδιά και Τιμές
Ταξινόμηση
Μια απλή ταξινόμηση
Αριθμητική Ταξινόμηση – Πώς πραγματικά δουλεύει η ταξινόμηση
Πολλαπλές Ταξινομημένες Λίστες
Grep και Map
Grep
Map
Γράφοντας τις δικές σου Grep και Map functions
Εξωτερικές Εντολές
Exec
System
Backticks
Πότε να χρησιμοποιείς εξωτερικές κλήσεις
Ανοίγοντας μια Διεργασία
Εκτέλεση κατά κυριολεξία-Quote execute
Όλα σε μια γραμμή
Ένα σύντομο παράδειγμα
Προσπέλαση Αρχείου
Τροποποιώντας αρχεία με Oneliner και $^Ι
Υπορουτίνες και Παράμετροι
Παράμετροι
Namespaces
Εμβέλεια Μεταβλητής
Οι μεταβλητές my
Πολλαπλές Επιστροφές Τιμών
LOCAL
Επιστροφή πινάκων
Ενότητες – (Modules)
Εισαγωγή
File::Find—Χρησιμοποιώντας μια ενότητα
ChangeNotify
Η δική σου ενότητα
Bondage and Discipline
Shebang
Use strict;
Διόρθωση λαθών – Debugging
Λογικοί Τελεστές
OR
Προτεραιότητα: Τι έρχεται πρώτο
And
Άλλοι λογικοί τελεστές

Το κείμενο του εγχειριδίου σε doc
Τα παραδείγματα του εγχειριδίου
Το περιβάλλον της Perl



Τι χρειάζεται να ξέρεις:

Πρέπει να είσαι ικανός να χειρίζεσαι ένα PC, χωρίς να απαιτείται

προγραμματιστική εμπειρία. Πρέπει να καταλαβαίνεις τα βασικά από την λειτουργία ενός PC και να γνωριζεις τι είναι τα αρχεία και τι οι κατάλογοι.

 Τι χρειάζεται να έχεις:

4 Ένα PC, το οποίο να μπορεί να τρέξει ένα λειτουργικό σύστημα Win32, όπως Windows95 ή Windows 98.

4 Χρειάζεται να διαθέτεις ένα αντίγραφο της Perl και γι’ αυτό ίσως πρέπει να είσαι συνδεδεμένος με το Internet. Εκτός και αν βρεις κάποιον άλλο τρόπο.

  

Σημείωση: Δεν χρειάζεται ένα Win32 PC, αν εγκαταστήσεις την

Perl σε ένα PC με λειτουργικό σύστημα όπως το Linux.

Δεν χρειάζεται μεταγλωτιστής. Η Perl είναι μια διερμηνευτική γλώσσα, που σημαίνει ότι διερμηνεύει και εκτελεί τον κώδικα κατευθείαν- δεν τον μεταφράζει σε ενδιάμεσο κώδικα.

Πώς να χρησιμοποιήσεις το διδακτικό εγχειρίδιο

Πρέπει να το δουλέψεις από την αρχή μέχρι το τέλος. Γενικά, η εξήγηση ακολουθεί τα παραδείγματα του κώδικα. Πριν διαβάσεις την εξήγηση, προσπάθησε να επεξεργαστείς τον κώδικα. Έλεγξε αν προχώρησες σωστά. Με αυτόν τον τρόπο θα εκμεταλλευτείς κατάλληλα όλες τις πληροφορίες αυτού του εγχειριδίου.

Μια μικρή εισαγωγή στην Perl

 Τι είναι η Perl

Η Perl είναι μια γλώσσα προγραμματισμού. Perl σημαίνει Πρακτική Γλώσσα Αναφοράς και Εξαγωγής ( Practical Report and Extraction Language ). Υπάρχει αναφορά σε ‘Perl’ και ‘ perl’. ‘Perl’ είναι η γλώσσα προγραμματισμού σαν σύνολο, ενώ ‘perl’ ονομάζεται ο πυρήνας εκτέλεσης. Δεν υπάρχει γλώσσα που ονομάζεται ‘Perl5’—αυτό σημαίνει ‘ Perl έκδοση 5’. Εκδόσεις προηγούμενες της ‘Perl 5’ είναι πολύ παλιές και δεν υποστηρίζονται.

Μερικές από τις δυνατότητες της Perl είναι :

¨ Ταχύτητα Ανάπτυξης . Επιμελείσαι ένα αρχείο κειμένου και απλά το τρέχεις. Με αυτόν τον τρόπο αναπτύσσεις προγράμματα πολύ γρήγορα. Δεν χρειάζεται ξεχωριστός μεταγλωτιστής.

¨ Ισχύς . Οι κανονικές εκφράσεις (regular expressions) της Perl είναι από τις καλύτερες που διατιθενται. Μπορείς να δουλέψεις με objects, sockets… με οτιδήποτε χρειάζεται ένα σύστημα διαχείρισης. Πρόσθεσε τον πλούτο των οντοτήτων που είναι διαθέσιμες στο CPAN και τα έχεις όλα.

¨ Ευκολία Χρήσης . Αν μπορείς να γράψεις ένα αρχείο δέσμης (batch file), μπορείς να προγραμματίσεις με Perl. Δεν χρειάζεται να μάθεις τον αντικειμενοστραφή προγραμματισμό, αλλά μπορείς να γράψεις αντικειμενοστραφή προγράμματα στην Perl. Αποφασίζεις το στυλ του προγραμματισμού και η Perl θα σε εξυπηρετήσει.

¨ Μεταφερσιμότητα . Πολλοί άνθρωποι αναπτύσσουν τα προγράμματα της Perl στο ΝΤ ή Win95 και μετά μέσω ενός πρωτοκόλλου (FTP) μεταφέρουν τα αρχεία σε ένα λειτουργικό σύστημα Unix, όπου και τρέχουν. Καμία τροποποίηση δεν απαιτείται.

¨ Εργαλεία Επιμέλειας . Δεν χρειάζεται η τελευταία έκδοση του ολοκληρωμένου περιβάλλοντος ανάπτυξης για την Perl. Μπορείς να αναπτύξεις τα προγράμματα της Perl με κάθε επεξεργαστή κειμένου. Notepad, vi, MS Word 97, ή ακόμη κατευθείαν από την κονσόλα. Φυσικά μπορείς να κάνεις τα πράγματα πιο εύκολα και να χρησιμοποιήσεις ένα από τα πολλά ‘δωρεάν λογισμικά’ για προγραμματισμό perl .

Τι είναι η Ενεργός(Active) Perl;

Η εταιρία με το όνομα ActiveState υπάρχει για να προωθεί εργαλεία της Perl σε περιβάλλον Win32. Η ActiveState ονομαζόταν ActiveWare, και πρίν ήταν κατά κάποιο τρόπο κομμάτι της Hip Communications. Win32 σημαίνει Windows 95, W98, Win NT.

Πριν την έκδοση της Perl 5.005, υπήρχε μια έκδοση της Perl για Win32 και μια άλλη για όλα τα υπόλοιπα συστήματα. Η άλλη έκδοση ήταν γνωστή σαν ‘μητρική έκδοση’(‘native version’).

Τι μπορώ να κάνω με την Perl;

 Παρατίθενται δυο παραδείγματα :

 * Το Internet

Παρατήρησες πόσα website έχουν δυναμικές σελίδες με .pl ή όμοια σαν χαρακτηριστικό (τύπου) αρχείου; Αυτή είναι η Perl. Η πιο δημοφιλής γλώσσα για τον προγραμματισμό CGI για πολλούς λόγους, οι περισσότεροι από τους οποίους προαναφέρθηκαν. Στην πραγματικότητα υπάρχουν πάρα πολλές δυναμικές σελίδες οι οποίες γράφτηκαν με Perl και που μπορεί να μην έχουν επέκταση .pl. Εάν κωδικοποιείς σε Active Server Pages μπορείς να χρησιμοποιήσεις ActiveStates PerlScript.

* Συστήματα Διαχείρισης

Αν είσαι Unix sysadmin θα γνωρίζεις για : sed, awk και shell scripts.Η Perl μπορεί να κάνει οτιδήποτε κάνουν αυτά και πολύ περισσότερα. Επιπλέον, η Perl τα κάνει πιο γρήγορα και ικανοποιητικά. Αν είσαι NT sysadmin, μαλλον δεν θα είσαι συνηθισμένος στον προγραμματισμό.

Το εγχειρίδιο. Το ταξίδι αρχίζει.

Η πρώτη σου φορά

Δημιουργείστε το πρώτο έγγραφο Perl ακολουθώντας τις παρακάτω

οδηγίες :

  1. Δημιούργσε ένα καινούριο κατάλογο για τα προγράμματα Perl, χωριστά για τα αρχεία δεδομένων και την εγκατάσταση της Perl. Για παράδειγμα, c:\scripts\, το οποίο θεωρούμε ότι χρησιμοποιείται σε αυτό το εγχειρίδιο.
  2. Άρχισε με οποιονδήποτε επεξεργαστή κειμένου, ο οποίος θα σε βοηθήσει να γράψεις στη γλώσσα Perl.Το ‘Notepad.exe’ είναι καλό.Αν δεν μπορείς να βρείς το ‘Notepad’ στο μενού εκκίνησης (startmenu), πάτησε το κουμπί εκκίνησης (start), επέλεξε ‘Run’, γράψε ‘Notepad’ και πάτησε ‘OK’.
  3. Γράψε τα παρακάτω στο Notepad:
  4. print "My first Perl script\n";
  5. Σώσε το παραπάνω στο c:\scripts\myFirst.pl. Προσοχή! Το Notepad μπορεί να σώσει αρχεία με προέκταση ‘.txt’, έτσι θα καταλήξει ερήμην σου, ως : myFirst.txt.pl. Η αλλαγή αυτή δεν επηρεάζει την Perl και έτσι συνεχίζει να εκτελεί το αρχείο. Αν λοιπόν η έκδοση του Notepad καταλήξει όπως παραπάνω, επέλεξε ‘All files’ πριν να το σώσεις ή άλλαξε το όνομα του αρχείου και ξαναφόρτωσέ το.
  6. Δεν χρειάζεται να βγείς από το Notepad-κράτησέ το ανοικτό, αφού μπορεί να γίνουν αλλαγές πολύ σύντομα.
  7. Πήγαινε στη προτροπή (command prompt). Εάν δεν ξέρεις πως να το

    αρχίσεις, πάτησε ‘Start’ και μετά ‘Run’.Εάν χρησιμοποιείς Win 9x , γράψε ‘command’ και πάτησε ‘enter’. Αν χρησιμοποιείς ΝΤ, γράψε ‘cmd’ και πάτησε ‘enter’.

  8. Πήγαινε στο αρχείο καταλόγου (directory) των εγγραφών της Perl, για παράδειγμα : cd\scripts.
  9. Εκτέλεσε το έγγραφο : perl myFirst.pl και θα δεις το αποτέλεσμα. Καλώς ήρθατε στον κόσμο της Perl.

Όταν δεν είναι …;

Έχεις γράψει : perl myFirst.pl και στην οθόνη σου δεν είδες το : My First Perl Script. Αν δεις ‘bad command or Filename’ αυτό σημαίνει ότι ή δεν έχεις εγκαταστήσει την Perl ή το perl.exe δεν είναι το μονοπάτι σου. Το πιθανότερο είναι να συμβαίνει το δεύτερο. Ξαναξεκίνησε και προσπάθησε ξανά. Εάν δεις :'Can’t open Perl script “xxxx.pl: No such file or directory” , τότε η Perl έχει σίγουρα εγκατασταθεί, αλλά ή έχεις γράψει το όνομα του εγγράφου λάθος ή το έγγραφο δεν ανήκει στο directory από όπου προσπαθείς να το τρέξεις. Για παράδειγμα ίσως έχεις σώσει το script στο : c:\windows και εσύ βρίσκεσαι στο : c:\scripts,τότε φυσικά η Perl ‘παραπονιέται ότι δεν μπορεί να βρεί το έγγραφο. Δεν χρειάζεται να τρέξεις το έγγραφο από το directory στο οποίο ανήκει, αλλά έτσι είναι ευκολότερο.

Υποθέτοντας ότι όλα είναι έτοιμα.

Αναλύουμε, τώρα, τι συμβαίνει εδώ. Πρώτον, οι γραμμές τελειώνουν με το σύμβολο του ελληνικού ερωτηματικού ‘;’. Σχεδόν όλες οι γραμμές του κώδικα της Perl πρέπει να τελειώνουν με αυτό το σύμβολο. Αυτός είναι κανόνας της Perl. Επίσης σημειώστε το: \n. Αυτός είναι ο κώδικας για να πεις στην Perl να συνεχίσει σε καινούρια γραμμή. Τι είναι η καινούρια γραμμή; Σβήσε το: \n από το πρόγραμμα και ξανατρέξτο με: print “My First Perl Script”; και όλα θα γίνουν ξεκάθαρα. Τώρα έχεις γράψει το πρώτο σου έγγραφο σε Perl.

Shebang

Σχεδόν όλα τα βιβλία για την Perl έχουν γραφτεί για το Un*X, το οποίο είναι ένα πρόβλημα για τα Win32. Αυτό οδηγεί τα scripts να είναι :

#!c:/perl/perl.exe

print "I'm a cool Perl hacker\n";

Η συνάρτηση στη γραμμή ’’Shebang” δείχνει πως να εκτελεστεί το αρχείο. Αυτό έχει ενδιαφέρον στο Unix. Στα Win32 το σύστημα ξέρει ήδη πώς να εκτελεστεί το αρχείο πριν να φορτωθεί , και γι’ αυτό η γραμμή αυτή είναι περιττή. Ωστόσο, η γραμμή αυτή δεν παραλείπεται τελείως, αφού ψάχνει για κάποιες αλλαγές που έχουν γίνει στην Perl. Ίσως, επιλέξεις να προσθέσεις τη γραμμή και έτσι τα scripts τρέχουν κατευθείαν στο UNIX χωρίς τροποποιήσεις.

Μεταβλητές:

Βαθμωτές

Αρχικά παίρνουμε τις απλές βαθμωτές μεταβλητές. Μια βαθμωτή μεταβλητή είναι μια μοναδική τιμή. Όπως ‘ $var=10’ το οποίο αντιστοιχεί στη μεταβλητή $var την τιμή 10. Όπως θα δούμε αργότερα στους πίνακες και στα hashes (αναξιόπιστα δεδομένα) η μεταβλητή @var αναφέρεται σε περισσότερες από μια τιμές. Προς το παρόν ισχύει ότι ‘Οι Βαθμωτές είναι Μοναδικές’.

Το $ % @ είναι καλά

Εάν έχεις οποιαδήποτε εμπειρία σε άλλες γλώσσες προγραμματισμού τότε ίσως εκπλαγείς από τον κώδικα ‘$var = 10’. Στις περισσότερες γλώσσες, εάν θέλεις να αναθέσεις την τιμή 10 σε μια μεταβλητή με το όνομα ‘var’ θα έγραφες var = 10. Όχι όμως στην Perl. Όλες οι μεταβλητές παραθέτονται με σύμβολα όπως: $ @ %. Ένα από τα πλεονεκτήματα των συμβόλων αυτών είναι να φτιάχνεις προγράμματα εύκολα στο διάβασμα. Τα προθέματα σημαίνουν ότι ‘μπορείς να δεις που είναι οι μεταβλητές’ με αρκετή ευκολία, καθώς και ‘τι είδος μεταβλητή είναι’. Παρακάτω θα δοκιμάσουμε κάποιες ακόμη μεταβλητές :

$string="perl";
$num1=20;
$num2=10.75;
print "The string is $string, number 1 is $num1 and number 2 is $num2\n";

Τύποι Δεδομένων

Δεν χρειάζεται να δηλώσεις τον τύπο των μεταβλητών. Σε άλλες γλώσσες πρέπει να δηλώσεις εάν η μεταβλητή είναι string, πίνακας, τι τύπου αριθμός είναι κ.λ.π. Για παράδειγμα στη Java γράφεις: int var = 10, που σημαίνει ότι η μεταβλητή var είναι ένας ακέραιος αριθμός και παίρνει την τιμή 10.

Γιατί λοιπόν στις άλλες γλώσσες προγραμματισμού πρέπει να δηλώνεις ακριβώς τις μεταβλητές που χρησιμοποιείς; Δεν θα ήταν ευκολότερο να το παραλείψεις;

Στα μικρά προγράμματα, ναι! Στα μεγάλα, όμως, projects με πολλούς προγραμματιστές που δουλεύουν στην ίδια εφαρμογή, όχι! Και αυτό γιατί δηλώνοντας ακριβώς τις μεταβλητές υπάρχει πειθαρχία στο χειρισμό των μεγάλων projects.

Η Perl δεν έχει σχεδιαστεί για γιγαντιαίες εφαρμογές τεχνολογίας λογισμικού. Είναι για σχετικά μικρά, γρήγορα προγράμματα. Για αυτούς τους λόγους δεν χρειάζεται μεγάλη αυστηρότητα στον έλεγχο των μεταβλητών.

Η ιδέα της υποχρέωσης του προγραμματιστή να δηλώνει τι είδος μεταβλητή θα δημιουργήσει ονομάζεται : typing. Καθώς η Perl δεν υποστηρίζει κανόνες στη διαδικασία του typing ονομάζεται ‘loosely typed’, σε αντίθεση με την C++ η οποία είναι ‘strongly typed’.

Παρεμβολή Μεταβλητών

Ξαναχρησιμοποιούμε τον κώδικα :

$string="perl";
$num1=20;
$num2=10.75;
print "The string is $string, number 1 is $num1 and number 2 is $num2\n";

Υπάρχει ένας τεχνικός όρος για τον τρόπο με τον οποίο οι μεταβλητές χρησιμοποιούνται μέσα σε ένα string: “variable interpolation” ή “Παρεμβολή Μεταβλητών”. Εάν δεν έχουμε το πρόθεμα $, τότε έχουμε έναν ψευδοκώδικα, όπως το παρακάτω παράδειγμα. Ψευδοκώδικας είναι ένας κώδικας που αποδεικνύει μια έννοια, δεν σχεδιάστηκε για να μπορεί να τρέχει, π.χ : print ‘The string is “.string.” and the number is “.num.”\n’;

Προσπάθησε να τρέξεις τον παρακάτω κώδικα :

$string="perl";
$num=20;
print "Doubles: The string is $string and the number is $num\n";
print 'Singles: The string is $string and the number is $num\n';

Τα διπλά εισαγωγικά επιτρέπουν την παρεμβολή μεταβλητών. Ενώ τα απλά εισαγωγικά όχι. Και τα δύο χρησιμοποιούνται, όπως θα δούμε παρακάτω, εάν θέλεις να παρεμβάλεις οτιδήποτε.

Αυτόματη Αύξηση – Μείωση

Άν θέλεις να αυξήσεις κατά 1 μια μεταβλητή, χρησιμοποιείς : $num=$num+1. Υπάρχει ένας πιο γρήγορος τρόπος για να γίνει αυτό: $num++. Αυτό ονομάζεται αυτόματη αύξηση. Αντίστοιχα, η αυτόματη μείωση είναι :$num--.

Βλέπε το παρακάτω παράδειγμα :

$num=10;
print "\$num is $num\n";

$num++;
print "\$num is $num\n";

$num--;
print "\$num is $num\n";

$num+=3;
print "\$num is $num\n";

 

Το τελευταίο παράδειγμα δείχνει ότι μπορείς να αυξήσεις ή να μειώσεις μια μεταβλητή και περισσότερο από 1.

Χαρακτήρας Διαγραφής

Υπάρχει και κάτι καινούριο στον παραπάνω κώδικα. Το ‘ \ ’. Αυτό τερματίζει τη λειτουργία της ειδικής σημασίας του προθέματος. Ο χαρακτήρας διαγραφής σημαίνει ότι τυπώνεται μόνο το σύμβολο $ αντί να αναφερθεί σε μια μεταβλητή. Στην πραγματικότητα το \ τερματίζει τη λειτουργία όλων των ειδικών χαρακτήρων της Perl, όχι μόνο του $. Επίσης, επιστρέφει κάποιους μη-ειδικούς χαρακτήρες ως ειδικούς ως εξής: n. Πρόσθεσε το \ και το απλό n θα γίνει η καινούρια γραμμή (New Line). Ο χαρακτήρας \ μπορεί επίσης να τερματίσει τον εαυτό του. Έτσι αν θέλεις να τυπώσεις ένα απλό \ προσπάθησε :

print "the MS-DOS path is c:\\scripts\\";

Επίσης το \ χρησιμοποιείται και για αναφορές. Υπάρχει ένας τεχνικός όρος για τους ‘ειδικούς χαρακτήρες’ όπως @ $ %, οι οποίοι ονομάζονται μεταχαρακτήρες. Η Perl χρησιμοποιεί πολλούς από αυτούς.

 Στοιχειοσειρές και αυξήσεις

$string="perl";
$num=20;
$mx=3;

print "The string is $string and the number is $num\n";

$num*=$mx;
$string++;
print "The string is $string and the number is $num\n";

 

Σημείωσε ότι η συντομία ‘*=’ σημαίνει ‘πολλαπλασίασε το $num με το $mx ή $num=$num*$mx’. Φυσικά η Perl υποστηρίζει τους συνηθισμένους τελεστές: + - * / ** %. Τα δυο τελευταία σημαίνουν ύψωση σε δύναμη και υπόλοιπο ακέραιας διαίρεσης. Επίσης σημείωσε τον τρόπο που μπορείς να αυξήσεις ένα αλφαριθμητικό.

Εκτύπωση

Η συνάρτηση print δέχεται μια λίστα από αντικείμενα για εκτύπωση, τα οποία χωρίζονται με κόμμα. Για παράδειγμα :

print "a doublequoted string ", $var, 'that was a variable called var', $num," and a newline \n";

 

Αν βάλεις όλα τα παραπάνω μέσα σε ένα αλφαριθμητικό μέσα σε διπλά εισαγωγικά:

print "a doublequoted string $var that was a variable called var $num and a newline \n";

 

τότε θα πάρεις το ίδιο αποτέλεσμα. Το πλεονέκτημα της χρήσης της συνάρτησης print με μια λίστα από αντικείμενα είναι ότι οι εκφράσεις υπολογίζονται πρίν να εκτυπωθούν.

Για παράδειγμα, δοκίμασε αυτό :

$var="Perl";
$num=10;
print "Two \$nums are $num * 2 and adding one to \$var makes $var++\n";
print "Two \$nums are ", $num * 2," and adding one to \$var makes ", $var++,"\n";

 

Μπορεί να εκπλαγείς από τα αποτελέσματα του τελευταίου παραδείγματος. Στην πραγματικότητα τι συμβαίνει με την μεταβλητή ‘ $var’; Έπρεπε να αυξηθεί κατά ένα βγάζοντας αποτέλεσμα ‘Perm’. Ο λόγος για τον οποίο γίνεται ‘m’ είναι επειδή αυτό είναι το επόμενο γράμμα μετά το γράμμα ‘l . Στην πραγματικότητα αυξήθηκε κατά ένα. Μετά αυξήσαμε την μεταβλητή $var++ αντι να την προαυξήσουμε.

Η διαφορά είναι ότι με την μετα-αύξηση η τιμή της μεταβλητής επιστρέφεται και μετά εκτελείται η λειτουργία της. Έτσι στο παραπάνω παράδειγμα, η τρέχουσα τιμή της $var επιστράφηκε στη συνάρτηση print και ύστερα αυξήθηκε κατά ένα. Μπορείς να το αποδείξεις αυτό προσθέτοντας τη γραμμή:

print “\$var is now $var\n”;

στο τέλος του παραπάνω παραδείγματος. Εάν θέλουμε να εκτελεστεί η λειτουργία της $var πριν να επιστραφεί η τιμή στη συνάρτηση print, τότε προαυξάνουμε την $var ως :++$var.

 

Υπορουτίνες – Μια πρώτη ματιά

Ας ρίξουμε άλλη μια ματιά στο παράδειγμα που χρησιμοποιήσαμε για να δούμε πως δουλεύει το σύστημα της αυτόματης αύξησης. Πρόσεξε πως χρησιμοποιούμε ακριβώς τον ίδιο κώδικα τέσσερις φορές. Γιατί να μην τον βάλουμε σε μια υπορουτίνα;

$num=10;		# sets $num to 10
&print_results;		# prints variable $num

$num++;
&print_results;

$num*=3;
&print_results;

$num/=3;
&print_results;

sub print_results {
print "\$num is $num\n";
}

 

Η υπορουτίνα μπορεί να μπεί οπουδήποτε στο έγγραφό σου, στην αρχή, στο τέλος, στη μέση…χωρίς τίποτα να αλλάξει. Μια υπορουτίνα είναι κομμάτι κώδικα, το οποίο μπορείς να το χρησιμοποιήσεις περισσότερες από μια φορές μέσα στο ίδιο έγγραφο. Στην Perl, μια υπορουτίνα είναι μια συνάρτηση οριζόμενη από το χρήστη. Δεν υπάρχει διαφορά. Η υπορουτίνα ορίζεται με τη λέξη sub και ύστερα το όνομα της. Όλος ο κώδικας της υπορουτίνας περικλείεται από τα σύμβολα { } για την αρχή και το τέλος αντίστοιχα. Η περιοχή μεταξύ των δύο παραπάνω αγκυλών ονομάζεται block. Οι υπορουτίνες καλούνται με το πρόθεμα & και ύστερα το όνομά τους, για παράδειγμα &print_results;

 

Σχόλια

Οτιδήποτε ακολουθεί το σύμβολο # είναι σχόλιο και γι’ αυτό αγνοείται. Σε κάθε επόμενη γραμμή των σχολίων πρέπει να βάλεις ένα καινούριο #.

Συγκρίσεις

Εντολή IF

Η πρόταση if είναι απλή. Για παράδειγμα: If the day is Sunday, then lie in bed. Μετατροπή σε Perl (μην το τρέξεις):

if ($day eq "sunday") {
	&lie_in_bed;
}

 

Γνωρίζεις ήδη ότι με το &lie_in_bed, καλείται μια υπορουτίνα. Θεωρούμε ότι η μεταβλητή $day έχει αναφερθεί νωρίτερα στο πρόγραμμα. Εάν $day δεν είναι ίσο του ‘Sunday’ τότε η υπορουτίνα &lie_in_bed δεν εκτελείται.

Δοκίμασε το παρακάτω :

$day="sunday";

if ($day eq "sunday") {
        print "Zzzzz....\n";
}

Πρόσεξε τη σύνταξη. Η πρόταση if χρειάζεται κάτι για να ελέγξει το αληθές της πρότασης. Αυτή η έκφραση πρέπει να είναι μέσα σε παρενθέσεις και το block πρέπει να περικλείεται από αγκύλες.

Η αλήθεια μιας έκφρασης σύμφωνα με την Perl

Υπάρχουν πολλές συναρτήσεις της Perl οι οποίες ελέγχουν την αλήθεια μιας έκφρασης. Μερικές είναι : if, while, unless. Έτσι είναι σημαντικό να ξέρεις ποια είναι το αληθές όπως ορίζεται από την Perl. Υπάρχουν τρείς κύριοι κανόνες :

 

  1. Κάθε αλφαριθμητικό είναι αληθές, εκτός από το κενό και το χαρακτήρα ο.
  2. Κάθε αριθμός είναι αληθής, εκτός από τον αριθμό μηδέν. Περιλαμβάνονται και οι αρνητικοί αριθμοί.
  3. Κάθε μη ορισμένη μεταβλητή είναι ψευδής. Μη ορισμένη είναι αυτή η μεταβλητή που δεν έχει τιμή, δηλαδή δεν έχει οριστεί.

Ένα παράδειγμα κώδικα για να καταλάβεις το νόημα :

&isit;                   # $test1 is at this moment undefined

$test1="hello";         # a string, not equal to "" or "0"
&isit;

$test1=0.0;             # $test1 is now a number, effectively 0
&isit;

$test1="0.0";           # $test1 is a string, but NOT effectively 0 !
&isit;

sub isit {
        if ($test1) {                           # tests $test1 for truth or not
                print "$test1 is true\n";
        } else {                                # else statement if it is not true
                print "$test1 is false\n";
        }
}

Το πρώτο τέστ απέτυχε γιατί το $test1 δεν έχει οριστεί. Αυτό σημαίνει ότι δεν έχει δημιουργηθεί αντιστοιχώντας μια τιμή σε αυτό. Έτσι απέτυχε σύμφωνα με τον κανόνα 3. Τα δύο τελευταία τέστ έχουν ενδιαφέρον. Φυσικά, το 0.0 είναι το ίδιο με το 0 στα αριθμητικά συμφραζόμενα. Αλλά δεν είναι το ίδιο με το 0 στα αλφαριθμητικά συμφραζόμενα, και σε αυτή την περίπτωση είναι αληθής. Χρησιμοποιούμε απλές μεταβλητές. Είναι χρήσιμο να ελέγχουμε το αποτέλεσμα μιας έκφρασης. Για παράδειγμα, αυτή είναι μια έκφραση : $x*2, και αυτό είναι το αποτέλεσμα : $var1 + $var2.

Ένα παράδειγμα :

$x=5;
$y=5;

if ($x - $y) {
        print '$x - $y is ',$x-$y," which is true\n";
} else {
        print '$x - $y is ',$x-$y," which is false\n";
}

Το τέστ απέτυχε γιατί ‘5-5’ είναι φυσικά μηδέν, το οποίο είναι ψευδές. Η πρόταση print φαίνεται λίγο παράξενη και αυτό γιατί εδώ έχουμε μια λίστα. Πρώτον, υπάρχει ένα αλφαριθμητικό σε απλά εισαγωγικά. Είναι έτσι γιατί δεν θέλουμε να αναπαριστά παρεμβολή μεταβλητής. Το δεύτερο είναι μια έκφραση η οποία είναι εκτιμημένη και το αποτέλεσμα θα εκτυπωθεί. Τέλος το αλφαριθμητικό σε διπλά εισαγωγικά χρησιμοποιείται γιατί θέλουμε να τυπώσουμε μια καινούρια γραμμή και χωρίς τα διπλά εισαγωγικά το \n δεν θα παρεμβληθεί. Αυτό που είναι περισσότερο χρήσιμο, από τον έλεγχο των ειδικών μεταβλητών αν είναι αληθείς, είναι ο έλεγχος ισότητας.

Για παράδειγμα :

$lucky=15;
$drawnum=15;

if ($lucky == $drawnum) {
        print "Congratulations!\n";
} else {
        print "Guess who hasn't won!\n";
}

Το σημαντικό του παραπάνω κώδικα είναι ο τελεστής της ισότητας ‘= =’

 Ισότητα και Perl

Το σύμβολο = είναι ένας τελεστής εκχώρησης, όχι ένας τελεστής σύγκρισης.

Έτσι λοιπόν :

- Το if ($x=10) είναι πάντα αληθές, διότι στο $χ έχει εκχωρηθεί η τιμή 10 επιτυχώς.

- Το if ($x= =10) συγκρίνει τις δυο τιμές, που μπορεί να μην είναι ίσες.

Μέχρι εδώ έχουμε ελέγξει αριθμούς, αλλά υπάρχουν και αλφαριθμητικά, τα οποία πρέπει να ελέγξουμε επίσης :

$name	 = 'Mark';

$goodguy = 'Tony';

if ($name == $goodguy) {
        print "Hello, Sir.\n";
} else {
        print "Begone, evil peon!\n";
}

Κάτι όμως εδώ δεν πάει καλά. Προφανώς το ‘Mark’ είναι διαφορετικό του ‘Tony’, αλλά γιατί η Perl τα θεωρεί ίσα; Αριθμητικώς είναι ίσα (αφού έχουν τέσσερις χαρακτήρες και τα δύο), αλλά πρέπει να τα ελέγξουμε σαν αλφαριθμητικά και όχι σαν νούμερα. Για να γίνει αυτό, απλά αντικαθιστώ το ‘= =’ με το ‘eq’ και όλα δουλεύουν όπως περιμέναμε.

 Όλες οι ισότητες δεν είναι ίσες : Αριθμητικά κατά Αλφαριθμητικών

Υπάρχουν δύο τύποι τελεστών σύγκρισης, τα αριθμητικά και τα αλφαριθμητικά. Έχουμε ήδη συναντήσει δύο, το ‘= =’ και το ‘eq’. Εκτέλεσε τα παρακάτω :

$foo=291;
$bar=30;

if ($foo < $bar) { 
        print "$foo is less than $bar (numeric)\n"; 
}

if ($foo lt $bar) { 
        print "$foo is less than $bar (string)\n"; 
}

Ο ‘lt’ είναι τελεστής σύγκρισης στα αλφαριθμητικά συμφραζόμενα, ενώ το ‘<’ συγκρίνει τα αριθμητικά συμφραζόμενα. Αλφαβητικά, σε αλφαριθμητικά συμφραζόμενα, το 291 προηγείται του 30. Άλλαξε τους αριθμούς κατά λίγο. Σημείωσε ότι η Perl δεν νοιάζεται αν χρησιμοποιείται ένας τελεστής σύγκρισης αλφαριθμητικών σε μια αριθμητική τιμή και αντίστροφα. Είναι ένα χαρακτηριστικό της ελαστικότητας της Perl.


Σχεσιακοί Τελεστές

 Οι υπόλοιποι τελεστές είναι :

Comparison

Numeric

String

Ίσο

==

eq

Όχι ίσο

!=

ne

Μεγαλύτερο από

>

gt

Μικρότερο από

<

lt

Μεγαλύτερο από ή ίσο με

>=

ge

Μικρότερο από ή ίσο με

<=

le


Περισσότερα για το IF

Περισσότερα για την πρόταση IF. Τρέξε τα παρακάτω :

$age=25;
$max=30;

if ($age  $max) {
        print "Too old !\n";
} else {
        print "Young person !\n";
}

Είναι εύκολο να διαπιστώσεις τι κάνει το else. Εάν η έκφραση είναι ψευδής, τότε εκτελείται ότι υπάρχει μέσα στο block του else. Η Perl επίσης μπορεί να κάνει και περισσότερους από έναν ελέγχους. Όπως :

 Elsif


$age=25;
$max=30;
$min=18;

if ($age  $max) {
        print "Too old !\n";
} elsif ($age < $min) { 
        print "Too young !\n"; 
} else { 
        print "Just right !\n"; 
}

Αν ο πρώτος έλεγχος αποτύχει, εκτελείται ο δεύτερος και συνεχίζει μέχρι να τελειώσουν οι προτάσεις elsif ή μέχρι να φτάσει σε μια πρόταση else. Η πρόταση else είναι προαιρετική και καμία elsif πρόταση δεν μπορεί να έρθει μετά από αυτήν. Υπάρχει μεγάλη διαφορά μεταξύ του προηγούμενου και του επόμενου παραδείγματος:

if ($age > $max) {
        print "Too old !\n";
} 

if ($age < $min) {
        print "Too young !\n";
}

Άν τρέξεις το τελευταίο θα σου επιστρέψει το ίδιο αποτέλεσμα. Ωστόσο είναι ένα παράδειγμα κακού προγραμματισμού. Σε αυτή την περίπτωση ελέγχουμε έναν αριθμό, αλλά υποθέστε ότι ελέγχουμε ένα αλφαριθμητικό για να δούμε εάν περιέχει το R ή το S. Είναι πιθανό το αλφαριθμητικό να περιέχει και τα δύο. Με αυτόν τον τρόπο θα περνούσες στον έλεγχο και των δυο IF προτάσεων. Χρησιμοποιώντας μια elsif πρόταση μπορείς να το αποφύγεις αυτό. Όσο η πρώτη πρόταση είναι αληθής, δεν εκτελούνται καμία από τις elsif ή else προτάσεις. Χρησιμοποιώντας την IF και Unless έχουμε το παρακάτω παράδειγμα :

print "Too old\n" if     $age > $max;
print "Too old\n" unless $age < $max;

Οι δυο παραπάνω γραμμές του κώδικα δεν κάνουν ακριβώς το ίδιο πράγμα. Θεώρησε τη μέγιστη (max) ηλικία στα 50 και βάλε την ίδια τιμή στην μεταβλητή age.

Πρέπει να είσαι προσεκτικός με την λογική σου γράφοντας αυτό τον κώδικα.

 

Εισαγωγή Δεδομένων

STDIN και άλλοι τελεστές αρχείων

Μερικές φορές πρέπει να εισαχθούν δεδομένα από τον χρήστη. Προσπάθησε αυτό :

print "Please tell me your name: ";
$name=<STDIN>;
print "Thanks for making me happy, $name !\n";

Στο σημείο αυτό μαθαίνεις καινούρια πράγματα. Πρώτον για το <STDIN>. Το STDIN είναι ένας τελεστής αρχείου, δηλαδή είναι αυτό που χρησιμοποιείς για αλληλεπίδραση με : αρχεία, κονσόλες, sockets κ.α. Μπορούμε να πούμε ότι το STDIN είναι η κύρια μέθοδος εισαγωγής δεδομένων. Στην περίπτωση αυτή το STDIN διαβάζει από την κονσόλα. Για να διαβάσεις δεδομένα από ένα αρχείο χρησιμοποίησε τα σύμβολα : < >. Σε αυτήν την περίπτωση είναι οτιδήποτε είχε εισαχθεί στο σύμβολο προτροπής (prompt). Η τιμή έχει εκχωριθεί στην μεταβλητή $name και έχει τυπωθεί. Όταν πατήσεις enter θα έχεις συμπεριλάβει έναν χαρακτήρα καινούριας γραμμής στο όνομά σου. Για να το αποφύγεις αυτό χρησιμοποίησε την chop.

Chop


print "Please tell me your name: ";
$name=<STDIN>;
chop $name
print "Thanks for making me happy, $name !\n"

Αποτυγχάνει με ένα συντακτικό λάθος. Για να το διακρίνεις δες το λάθος στον κώδικα στην αντίστοιχη γραμμή και θα δεις που είναι το συντακτικό λάθος. Η απάντηση είναι ότι λείπει το ‘;’ στο τέλος των δυο τελευταίων γραμμών. Εάν προσθέσεις το ‘;’ μόνο στην τρίτη γραμμή του κώδικα το πρόγραμμα θα δουλέψει όπως θα έπρεπε, αφού η Perl δεν απαιτεί το ‘;’ στην τελευταία πρόταση του block. Καλό είναι όμως να χρησιμοποιείται σε όλες τις προτάσεις, γιατί μπορεί να προσθέσεις αργότερα περισσότερο κώδικα.

Η συνάρτηση chop αφαιρεί τον τελευταίο χαρακτήρα από αυτά που τις έχουν δοθεί. Στην περίπτωσή μας μετακινεί την καινούρια γραμμή. Στην πραγματικότητα, αυτό γίνεται πιο σύντομα :

print "Please tell me your name: ";
chop ($name=<STDIN>);
print "Thanks for making me happy, $name !";

Οι παρενθέσεις ( ) δίνουν τη δυνατότητα στην chomp να ενεργεί στο αποτέλεσμα που βρίσκεται μέσα σ’ αυτές. Έτσι εκτελείται πρώτα το $name=<STDIN> και μετά το αποτέλεσμά του, το οποίο είναι το $name.

Safe Chopping with Chomp

Αντί να αφαιρέσεις τον τελευταίο χαρακτήρα αδιαφορώντας για το τι είναι, μπορείς να μετακινήσεις τον τελευταίο χαρακτήρα μόνο εάν είναι μια καινούρια γραμμή με την chomp:

chomp ($name=<STDIN>);

Σ’αυτή την περίπτωση η Perl βρίσκει κάποιο λάθος. Η chomp δεν αφαιρεί πάντα τον τελευταίο χαρακτήρα όταν αυτός είναι μια καινούρια γραμμή, αλλά εάν δεν είναι καθορίζεις μια ειδική μεταβλητή, που ονομάζεται $/, για κάτι διαφορετικό.

Πίνακες

Λίστες, Αγέλες-Τι είναι πίνακες;

Η Perl έχει δύο τύπους πινάκων, τον συνειρμικό πίνακα (hashes) και τον διατεταγμένο πίνακα. Και οι δύο τύποι είναι λίστες. Μια λίστα είναι μια συλλογή από μεταβλητές που αναφέρονται σαν συλλογή και όχι ως ξεχωριστά στοιχεία. Η λίστα στην Perl μοιάζει σαν μια ‘αγέλη (herd)’. Η λίστα είναι μια συλλογή από μεταβλητές, οι οποίες δεν χρειάζεται να είναι όλες του ίδιου τύπου. Μια λίστα στην Perl μπορεί να αποτελείται από τρείς βαθμωτές μεταβλητές, δύο πίνακες στοιχείων και δέκα συνειρμικούς πίνακες στοιχείων.

Βασικές λειτουργίες πινάκων

Ένας πίνακας είναι μια ταξινομημένη λίστα από βαθμωτές μεταβλητές. Η λίστα αυτή μπορεί να αναφέρεται στο σύνολο ή σε συγκεκριμένα στοιχεία της. Το παρακάτω πρόγραμμα ορίζει έναν πίνακα με το όνομα @names, και γεμίζει τον πίνακα με πέντε τιμές.

@names=("Muriel","Gavin","Susanne","Sarah","Anna");

print "The elements of \@names are @names\n";
print "The first element is $names[0] \n";
print "The third element is $names[2] \n";
print 'There are ',scalar(@names)," elements in the array\n";

Πρώτον, σημείωσε τον τρόπο που ορίζουμε τον πίνακα @names. Καθώς είναι σε μορφή λίστας, χρησιμοποιούμε παρενθέσεις. Κάθε τιμή χωρίζεται με κόμμα, το οποίο αποτελεί τον εξ’ ορισμού οριοθέτη της Perl σε λίστα. Πρόσεξε στη συνέχεια πώς τυπώνεται. Αναφερόμαστε σε αυτό σαν σύνολο, το οποίο είναι μια μορφή λίστας, και σημαίνει ότι αναφερόμαστε σε περισσότερα από ένα στοιχεία της λίστας κάθε φορά. Ο κώδικας print @names; θα δουλέψει το ίδιο καλά. Εάν θέλουμε να κάνουμε οτιδήποτε με πίνακα σαν λίστα, κάτι δηλαδή με περισσότερες από μια τιμές, τότε αναφερόμαστε στον πίνακα ως @array. Αυτό είναι σημαντικό. Το πρόθεμα @ χρησιμοποιείται όταν θέλεις να αναφερθείς σε περισσότερα από ένα στοιχεία της λίστας.

Όταν αναφέρεσαι σε περισσότερα από ένα στοιχεία, αλλά όχι σε όλα ενός πίνακα, αυτό ονομάζεται slice.

Στοιχεία Πινάκων

Αρχικά, θα ασχοληθούμε με μόνο ένα στοιχείο της λίστας, έτσι δεν μπορούμε να χρησιμοποιήσουμε το πρόθεμα @, το οποίο αναφέρεται σε πολλαπλά στοιχεία του πίνακα. Είναι μια μοναδική βαθμωτή μεταβλητή γι’ αυτό χρησιμοποιούμε το πρόθεμα $. Δεύτερον, πρέπει να επιλέξουμε ποιο στοιχείο θέλουμε. Αυτό είναι εύκολο: $array[0] για το πρώτο στοιχείο του πίνακα, $array[1] για το δεύτερο κ.λ.π. Οι δείκτες των πινάκων ξεκινάνε από το μηδέν, εκτός αν εμείς επιλέξουμε διαφορετικά.

Πώς να αναφερθείς στα στοιχεία ενός πίνακα

Προσπάθησε να καταλάβεις το παρακάτω :

$myvar="scalar variable";
@myvar=("one","element","of","an","array","called","myvar");

print $myvar;        # refers to the contents of a scalar variable called myvar
print $myvar[1];     # refers to the second element of the array myvar
print @myvar;        # refers to all the elements of array myvar

Οι δύο μεταβλητές $myvar και @myvar με κανένα τρόπο δεν ταυτίζονται.

Εναλλακτικοί τρόποι προσπέλασης πινάκων

Ο αριθμός του στοιχείου μπορεί να είναι μια μεταβλητή.

print "Enter a number :";
chomp ($x=<STDIN>);

@names=("Muriel","Gavin","Susanne","Sarah","Anna");

print "You requested element $x who is $names[$x]\n";

print "The index number of the last element is $#names \n";

Αυτό είναι χρήσιμο. Πρόσεξε την τελευταία γραμμή του παραδείγματος. Επιστρέφει τον αριθμό του δείκτη του τελευταίου στοιχείου. Φυσικά μπορείς να κάνεις : $last=scalar(@names)-1; αλλά είναι δύσκολο. Υπάρχει ένας πιο εύκολος τρόπος για να πας στο τελευταίο στοιχείο, όπως παρακάτω:

print "Enter the number of the element you wish to view :";
chomp ($x=<STDIN>);

@names=("Muriel","Gavin","Susanne","Sarah","Anna","Paul","Trish","Simon");

print "The first two elements are @names[0,1]\n";
print "The first three elements are @names[0..2]\n";
print "You requested element $x who is $names[$x-1]\n";		# starts at 0
print "The elements before and after are : @names[$x-2,$x]\n";
print "The first, second, third and fifth elements are @names[0..2,4]\n";

print "a) The last element is $names[$#names]\n";	# one way
print "b) The last element is @names[-1]\n";		# different way 

Φαίνεται περίπλοκο, αλλά δεν είναι. Μπορείς να έχεις πολλές τιμές χωρισμένες με κόμμα. Όσες εσύ θέλεις και με οποιανδήποτε σειρά. Ο τελεστής ‘..’ δίνει οτιδήποτε περιλαμβάνεται μεταξύ των τιμών. Τέλος, δες πώς τυπώνεται το τελευταίο στοιχείο. Θυμήσου ότι το : $#names μας δίνει έναν αριθμό. Απλά κλείστο μέσα σε αγκύλες [ ] και έτσι θα έχεις το τελευταίο στοιχείο.

Επειδή θέλουμε να προσπελάσουμε στοιχεία της μορφής [0,1] τα οποία είναι περισσότερες από μια μεταβλητές, δεν μπορούμε να χρησιμοποιήσουμε το βαθμωτό πρόθεμα, δηλαδή το $. Χρησιμοποιούμε το σύμβολο @, γιατί έχουμε προσπέλαση σε πίνακα με τη μορφή λίστας. Δεν έχει σημασία που δεν είναι ολόκληρος ο πίνακας. Θυμήσου ότι αυτό ισχύει και ονομάζεται slice.

Επανάληψη με for loops

Πώς διατρέχουν τα στοιχεία ενός πίνακα; Φτάχνοντας μια for loop:

@names=("Muriel","Gavin","Susanne","Sarah","Anna","Paul","Trish","Simon");

for ($x=0; $x <= $#names; $x++) {
        print "$names[$x]\n"; 
}

Δίνει στη μεταβλητή $x την τιμή 0, τρέχει την loop μια φορά, προσθέτει ένα στο $x, ελέγχει εάν αυτό είναι μικρότερο ή ίσο από το $#names και συνεχίζει μ’αυτό τον τρόπο.

Η for loop έχει τρία τμήματα :

· Αρχικοποίηση

· Έλεγχος κατάστασης

· Τροποποίηση

 

Σε αυτή την περίπτωση, η μεταβλητή $x αρχικοποιείται στην τιμή μηδέν. Αμέσως ελέγχεται αν είναι μικρότερο ή ίσο του $#names. Εάν αυτό ισχύει τότε το μπλόκ εκτελείται μια φορά. Εάν δεν ισχύει η συνθήκη, τότε το μπλοκ δεν εκτελείται καθόλου. Μόλις το μπλόκ εκτελεστεί, η έκφραση τροποποίησης εκτιμάται, δηλαδή η $x++. Μετά, ο έλεγχος κατάστασης ελέγχει αν το μπλοκ μπορεί να εκτελεστεί ή όχι.

For loops with, o τελεστής εύρους

Υπάρχει μια άλλη έκδοση:

for $x (0 .. $#names) {
        print "$names[$x]\n";
}

Δίνει απλά στην $χ την τιμή μηδέν, μετά αυξάνει την $χ κατά ένα μέχρι να γίνει ίσο με: $#names.

Foreach

Για καλύτερη εμφάνιση του κώδικα μπορούμε να χρησιμοποιήσουμε την foreach:

foreach $person (@names) {
        print "$person";
}

Σαρώνει κάθε στοιχείο του @names και το αντιστοιχεί κάθε φορά στη μεταβλητή $person. Μπορείς αν θέλεις να χρησιμοποιήσεις

for $person (@names) {
        print "$person";
}

χωρίς να υπάρξει καμία απολύτως διαφορά.

Το δυσφημισμένο ‘$_’

Το $_ είναι η προκαθορισμένη μεταβλητή για είσοδο δεδομένων και αναζήτηση ‘Default Input and Pattern Searching Variable’.

foreach (@names) {
        print "$_";
}

Εάν δεν ορίσεις μια μεταβλητή στην οποία θα βάζεις κάθε στοιχείο, χρησιμοποίησε το $_ το οποίο είναι το εξ’ ορισμού κατάλληλο γι’ αυτή τη λειτουργία και για πολλές άλλες λειτουργίες στην Perl. Συμπεριλαμβάνοντας την συνάρτηση print έχουμε:

foreach (@names) {
        print ;
}

Εδώ τυπώνεται το $_ εξ’ ορισμού. Θα συναντήσεις πολλά $_ στην Perl.

Πρώορος τερματισμός επανάληψης

Εάν θέλεις μια επανάληψη που δεν τελειώνει ποτέ, δοκίμασε μια συνθήκη που ξέρεις ότι πάντα θα είναι αληθής

while (1) {
	$x++;
        print "$x:  Did you know you can press CTRL-C to interrupt a perl program?\n";
}

Άλλος τρόπος για να τερματίσεις μια επενάληψη είναι να χρησιμοποιήσεις την foreach, όπως ήδη έχουμε δεί. Αν δεν ξέρεις όμως πότε θέλεις να βγείς από μια επανάληψη; Για παράδειγμα, υποθέτουμε ότι θέλεις να εκτυπώσεις μια λίστα ονομάτων αλλά να σταματήσεις όταν θα βρείς ένα συγκεκριμένο. Έχουμε το παρακάτω έγγραφο της Perl :

@names=('Mrs Smith','Mr Jones','Ms Samuel','Dr Jansen','Sir Philip');

foreach $person (@names) {
	print "$person\n";
	last if $person=~/Dr /;
}

Το μόνο που χρειάζεται να ξέρεις είναι ότι η έκφραση συνθήκης στην πρόταση last if γίνεται αληθής όταν το όνομα ξεκινάει από ‘Dr’. Όταν αυτό γίνει η last έχει εκτελεστεί και η επανάληψη τελειώνει.

Περισσότερος έλεγχος του πρόωρου τερματισμού : ετικέτες (labels)

Ο παρακάτω κώδικας έχει την ίδια αρχή με τον προηγούμενο:

@names  =('Mrs Smith','Mr Jones','Ms Samuel','Dr Jansen','Sir Philip');
@medics =('Dr Black','Dr Waymour','Dr Jansen','Dr Pettle');

foreach $person (@names) {
	print "$person\n";
	if ($person=~/Dr /) {
		foreach $doc (@medics) {
			print "\t$doc\n";
			last if $doc eq $person;
		}
	}
}

Εδώ φαίνεται η ένθετη επανάληψη, δηλαδή μια επανάληψη μέσα σε μια άλλη. Αυτό που θα συμβεί είναι ότι ο πίνακας @names ψάχνει για το ‘Dr’ και αφού το βρεί τότε ο πίνακας @medics ψάχνει να επιβεβαιώσει αν είναι ένας πραγματικός γιατρός και όχι οτιδήποτε άλλος καθηγητής. Η κανονική έκφραση έχει μεταβληθεί σε μια πρόταση if η οποία δουλεύει καλά καθώς επιστρέφει true ή false. Το πρόβλημα στον κώδικα είναι ότι αφού βρούμε τον γιατρό, θέλουμε ο κώδικας να σταματήσει. Αυτό όμως δεν συμβαίνει. Σταματάει μόνο η ένθετη επανάληψη που δεν θα τυπωθεί ποτέ. Ωστόσο ο κώδικας τελειώνει με το ‘Sir Philip’.Αυτό που χρειάζεται είναι ένας τρόπος να σπάσει η loop με τα φωλιασμένα. Γι’ αυτό :

@names  =('Mrs Smith','Mr Jones','Ms Samuel','Dr Jansen','Sir Philip');
@medics =('Dr Black','Dr Waymour','Dr Jansen','Dr Pettle');

LBL: foreach $person (@names) {
	print "$person\n";
	if ($person=~/Dr /) {
		foreach $doc (@medics) {
			print "\t$doc\n";
			last LBL if $doc eq $person;
		}
	}
}

Μόνο δύο αλλαγές υπάρχουν εδώ. Έχουμε ορίσει μια ετικέτα με το όνομα ‘LBL’.Αντί να βγούμε από την τρέχουσα επανάληψη, η οποία είναι δεδομένη, ορίζουμε μια ετικέτα για να βγούμε από αυτή, η οποία βρίσκεται στην εξωτερική επανάληψη. Αυτό δουλεύει με αυθαίρετο αριθμό ένθετων επαναλήψεων. Μπορείς να καλείς τις ετικέτες (labels) όποτε επιθυμείς και να τις ονομάσεις όπως θέλεις π.χ DORIS, MATILDA κ.λ.π

Μεταβολή στοιχείων ενός πίνακα

Έχουμε το @names. Θέλουμε να το μεταβάλουμε. Τρέξε το παρακάτω :

print "Enter a name :";
chomp ($x=<STDIN>);

@names=("Muriel","Gavin","Susanne","Sarah");

print "@names\n";

push (@names, $x);

print "@names\n";

Η συνάρτηση push προσθέτει μια τιμή στο τέλος ενός πίνακα, ή ακόμη και περισσότερες:

print "Enter a name :";
chop ($x=<STDIN>);

@names=("Muriel","Gavin","Susanne","Sarah");
@cities=("Brussels","Hamburg","London","Breda");

print "@names\n";

push (@names, $x, 10, @cities[2..4]);

print "@names\n";

Με βάση τη δήλωση δεν υπάρχει πέμπτο στοιχείο στον πίνακα @names όπως φαίνεται στην πρώτη εκτύπωση.

Με τη λειτουργία push όμως υπάρχει πέμπτο στοιχείο. Πρόσθεσε το παρακάτω στο τέλος του προηγούμενου παραδείγματος :

print "There are ",scalar(@names)," elements in \@names\n";

Εμφανίζονται οχτώ στοιχεία στον πίνακα @names. Ωστόσο, στην πραγματικότητα είναι εννιά, γιατί υπάρχει και το κενό στοιχείο $cities[4] του πίνακα @cities. Έτσι η Perl έχει επεκτείνει τον πίνακα @names, ενώ ο πίνακας @cities παραμένει αμετάβλητος. Για να το δείς αυτό δοκίμασε να κάνεις poping τον πίνακα.

Παίζοντας με Πίνακες


@names=("Muriel","Gavin","Susanne","Sarah");
@cities=("Brussels","Hamburg","London","Breda");

&look;

$last=pop(@names);
unshift (@cities, $last);

&look;

sub look {
        print "Names : @names\n";
        print "Cities: @cities\n";
}

Τώρα έχουμε δύο πίνακες. Η συνάρτηση pop μετακινεί το τελευταίο στοιχείο ενός πίνακα και το επιστρέφει, το οποίο σημαίνει ότι μπορείς να καταχωρήσεις την επιστρεφόμενη τιμή σε μια μεταβλητή. Η συνάρτηση unshift προσθέτει μια τιμή στην αρχή του πίνακα. Θυμήσου ότι το &subroutinename καλεί μια υπορουτίνα.

Παρακάτω παρουσιάζονται οι συναρτήσεις που χρησιμοποιούνται στους πίνακες

Πίνακας συναρτήσεων χειρισμού πίνακα

push

Προσθέτει τιμή στο τέλος του πίνακα

pop

Αφαιρεί και επιστρέφει τιμή από το τέλος του πίνακα

shift

Αφαιρεί και επιστρέφει τιμή από την αρχή του πίνακα

unshift

Προσθέτει τιμή στην αρχή του πίνακα

 

Splice


@names=("Muriel","Sarah","Susanne","Gavin");

&look;

@middle=splice (@names, 1, 2);

&look;

sub look {
        print "Names : @names\n";
        print "The Splice Girls are: @middle\n";
}

Το πρώτο όρισμα του Splice είναι ένας πίνακας. Το δεύτερο ένα offset. Το offset είναι ο αριθμός του δείκτη μιας λίστας στοιχείων για να αρχίσει το splice. Στην περίπτωσή μας είναι 1. Μετά έρχεται ο αριθμός των στοιχείων που θα αφαιρεθούν, που λογικά είναι 1 ή περισσότερα, στην περίπτωσή μας 2. Μπορείς να το βάλεις στο 0 χωρίς να υπάρξει πρόβλημα. Αυτό θα ήταν εύχρηστο καθώς το splice μπορεί να προσθέσει στοιχεία στη μέση του πίνακα. Αν δεν επιθυμείς καμία διαγραφή το 0 είναι ο κατάλληλος αριθμός για να χρησιμοποιήσεις. Έτσι :

@names=("Muriel","Gavin","Susanne","Sarah");
@cities=("Brussels","Hamburg","London","Breda");

&look;

splice (@names, 1, 0, @cities[1..3]);

&look;

sub look {
        print "Names : @names\n";
        print "Cities: @cities\n";
}

Σημείωσε πως έχει γίνει η εκχώρηση στον @middle.

Εάν αναθέσεις το αποτέλεσμα του splice σε μια βαθμωτή μεταβλητή τότε:

@names=("Muriel","Sarah","Susanne","Gavin");

&look;

$middle=splice (@names, 1, 2);

&look;

sub look {
        print "Names : @names\n";
        print "The Splice Girls are: $middle\n";
}

θεωρείται το τελευταίο στοιχείο που αφαιρέθηκε. Η συνάρτηση splice είναι επίσης ένας τρόπος για να διαγράψεις στοιχεία από ένα πίνακα.

Μεταβλητές Διαγραφής

Θέλουμε να διαγράψουμε το “Hamburg” από τον ακόλουθο πίνακα. Πώς θα το κάνουμε; Ίσως με τον ακόλουθο τρόπο :

@cities=("Brussels","Hamburg","London","Breda");

&look;

$cities[1]="";

&look;

sub look {
	print "Cities: ",scalar(@cities), ": @cities\n";
}

Σίγουρα το “Hamburg” μετακινήθηκε. Όμως το στοιχείο του πίνακα υπάρχει ακόμα. Υπάρχουν ακόμα τέσσερα στοιχεία στον @cities. Έτσι αυτό που χρειαζόμαστε είναι η συνάρτηση splice, η οποία αφαιρεί το στοιχείο.

splice (@cities, 1, 1);

Μέχρι εδώ δεν έχουμε κανένα πρόβλημα με τον πίνακα. Αλλά τι γίνεται με τις ταξινομημένες μεταβλητές, όπως τις παρακάτω :

$car ="Porsche 911";
$aircraft="G-BBNX";

&look;

$car="";

&look;

sub look {
	print "Car :$car: Aircraft:$aircraft:\n";
	print "Aircraft exists !\n" if $aircraft;
	print "Car exists !\n" if $car;
}

Φαίνεται σαν να έχουμε διαγράψει την μεταβλητή $car. Αλλά δεν έχει διαγραφεί. Έχει μετατραπεί σε ένα κενό αλφαριθμητικό “ ”. Το κενό αλφαριθμητικό εκτιμάται, σύμφωνα με τα παραπάνω, λάθος και έτσι ο έλεγχος της IF αποτυγχάνει.

Έλεγχος ύπαρξης

Αν κάτι είναι λάθος δεν σημαίνει ότι δεν υπάρχει. Η μεταβλητή σου υπάρχει ακόμα. Η Perl διαθέτει μια συνάρτηση, η οποία ελέγχει αν κάτι υπάρχει. Η ύπαρξη μιας μεταβλητής, στην ορολογία της Perl, σημαίνει ότι έχει οριστεί.

print "Car is defined !\n" if defined $car;
Εκτιμάται σαν αληθής, καθώς η μεταβλητή $car υπάρχει.

$car 	 ="Porsche 911";
$aircraft="G-BBNX";

&look;

undef $car; # this undefines $car

&look;

sub look {
	print "Car :$car: Aircraft:$aircraft:\n";
	print "Aircraft exists !\n"  if $aircraft;
	print "Car exists !\n" 	     if defined $car;
}

Η μεταβλητή $car έχει διαγραφεί.

Βασικές Κανονικές Εκφράσεις

Εισαγωγή

Μια κανονική έκφραση (regular expression) είναι : dir*.txt, η οποία εμφανίζει όλα τα αρχεία που τελειώνουν σε “.txt”.

Τα regex στην Perl μοιάζουν με :

$name=~/piper/

Το οποίο σημαίνει ότι αν το ‘piper’ είναι μέσα στην $name, τότε είναι αληθές.

Η κανονική έκφραση είναι μέσα στα ‘/ /’ και ο τελεστής ‘=~’ δηλώνει το στόχο της αναζήτησης.

Τρέξε το παρακάτω. Δοκίμασε να ψάξεις το ‘the faq’ . Στη συνέχεια το ‘my tealeaves’ και θα δεις τι θα συμβεί.

print "What do you read before joining any Perl discussion ? ";
chomp ($_=<STDIN>);

print "Your answer was : $_\n";

if ($_=~/the faq/) {
        print "Right !  Join up !\n";
} else {
        print "Begone, vile creature !\n";
}

Επομένως εδώ το $_ ψάχνει για το ‘the faq’. Ο τελεστης ‘=~’ δεν χρειάζεται. Ο κώδικας λειτουργεί εξίσου καλά και ως : if (/the faq/) {, γιατί αν δεν συγκεκριμενοποιήσεις την μεταβλητή αναζήτησης, τότε η perl ψάχνει το $_ εξ’ ορισμού. Στη συγκεκριμένη περίπτωση θα ήταν καλύτερο να χρησιμοποιήσεις το : if ($_eq”the faq”) { , καθώς ελέγχουμε για ακριβή αντιστοίχιση.

 Ευαισθησία κανονικών εκφράσεων

Τι θα γίνει εάν κάποιος εισάγει το ‘The FAQ’; Θα αποτύχει γιατί τα regex είναι ευαίσθητα. Φτιάχνω το : if (/the faq/ I) {, και με τη χρησιμοποίση του ‘/I’ μειώνεται η ευαισθησία του regex. Τώρα δουλεύει με όλες τις παραλλαγές που μπορούν να γίνουν, όπως με το ‘the Faq’ ή με το ‘the FAQ’. Τώρα μπορείς να καταλάβεις γιατί μια κανονική έκφραση είναι καλύτερη σ’αυτή την περίπτωση από έναν απλό έλεγχο που χρησιμοποιεί το ‘eq’.

Διάβασε το παρακάτω παράδειγμα το οποίο θα σε βοηθήσει να ξεκαθαρίσεις τα παραπάνω. Τα tabs και τα κενά χρησιμοποιούνται για αισθητικούς λόγους.

$_="perl for Win32";                            # sets the string to be searched

if ($_=~/perl/) { print "Found perl\n" };       # is 'perl' inside $_ ?  $_ is "perl for Win32".
if (/perl/)     { print "Found perl\n" };       # same as the regex above.  Don't need the =~ as we are testing $_
if (/PeRl/)     { print "Found PeRl\n" };       # this will fail because of case sensitivity
if (/er/)       { print "Found er\n" };         # this will work, because there is an 'er' in 'perl'
if (/n3/)       { print "Found n3\n" };         # this will work, because there is an 'n3' in 'Win32'
if (/win32/)    { print "Found win32\n" };      # this will fail because of case sensitivity
if (/win32/i)   { print "Found win32 (i)\n" };  # this will *work* because of case insensitivity (note the /i)

print "Found!\n"  if      / /;                  # another way of doing it, this time looking for a space

print "Found!!\n" unless $_!~/ /;		# both these are the same, but reversing the logic with unless and !
print "Found!!\n" unless    !/ /;		# don't do this, it will always never not confuse nobody :-)
						# the ~ stays the same, but = is changed to ! (negation)

$find=32;                                       # Create some variables to search for
$find2=" for ";                                 # some spaces in the variable too

if (/$find/)  { print "Found '$find'\n" };      # you can search for variables like numbers
if (/$find2/) { print "Found '$find2'\n" };     # and of course strings !

print "Found $find2\n" if /$find2/;           # different way to do the above

Όπως φαίνεται από το τελευταίο παράδειγμα, μπορείς να βάλεις μια μεταβλητή σε ένα regex. Πολλά έχουν γραφεί για τις κανονικές εκφράσεις. Παρακάτω παρατίθονται κάποια χρήσιμα κόλπα :

Κατηγορίες Χαρακτήρων

@names=qw(Karlson Carleon Karla Carla Karin Carina Needanotherword);

foreach (@names) {                      # sets each element of @names to $_ in turn
        if (/[KC]arl/) {                # this line will be changed a few times in the examples below
                print "Match !  $_\n";
        } else {
                print "Sorry.   $_\n";
        }
}

Στην περίπτωση αυτή ο @names αρχικοποιείται με τη χρήση κενού στη θέση του κόμματος. Το qw αναφέρεται στο ‘quote words’, το οποίο χωρίζει τις λέξεις της λίστας. Κάθε λέξη τελειώνει με ένα κενό (όπως tabs, spaces, newlines e.t.c). Τα σύμβολα [ ] περικλείουν συγκεκριμένους χαρακτήρες που πρόκειται να ταιριαστούν. Στην περίπτωσή μας το ‘Karl’ ή το ‘Carl’ μπορεί να βρίσκονται στο ίδιο στοιχείο. Μπορείς να χρησιμοποιήσεις και περισσότερους από δύο χαρακτήρες και περισσότερα από ένα σύνολα επίσης. Στο παραπάνω πρόγραμμα άλλαξε την γραμμή 4 με το :

if (/[KCZ]arl[sa]/) {

το οποίο αντιστοιχεί σε αυτά που αρχίζουν με τα K, C ή Z, μετά το ‘arl’, και μετά ή το ‘s’ ή το ‘a’. Ο συνδυασμός KCZarl δεν γίνεται. Η άρνηση είναι επίσης πιθανή. Δοκίμασε το παρακάτω :

if (/[KCZ]arl[^sa]/) {

το οποίο επιστρέφει πράγματα που αρχίζουν με τα K,C ή Z, μετά το arl και μετά οτιδήποτε εκτός από τα ‘s’ ή ‘a’. Το σύμβολο ^ πρέπει να είναι ο πρώτος χαρακτήρας, γιατι διαφορετικά δεν θα λειτουργεί σαν άρνηση. Έχοντας πει ότι τα [ ] ορίζουν συγκεκριμένους χαρακτήρες μόνο, αναφέρονται οι δυο περιπτώσεις που είναι ίδιες :

/[abcdeZ]arl/;
/[a-eZ]arl/;

Αν χρησιμοποιείς ένα ενωτικό θα πάρεις μια λίστα χαρακτήρων στην οποία θα συμπεριλαμβάνονται η αρχή και το τέλος αυτών. Αν θέλεις να αντιστοιχήσεις ένα μεταχαρακτήρα, τότε :

/[\-K]arl/;

και αντιστοιχεί Karl ή -arl. Παρόλο που το ενωτικό ‘-’ αντιπροσωπεύεται από δύο χαρακτήρες, είναι ο ένας χαρακτήρας που αντιστοιχείται.

Ταίριασμα Ειδικών Δεικτών

Εάν θέλεις να αντιστοιχήσεις στο τέλος της λέξης, σιγουρέψου ότι το $ είναι ο τελευταίος χαρακτήρας του regex. Το παρακάτω εμφανίζει όλα τα ονόματα που τελειώνουν με το χαρακτήρα ‘α’ :

if (/a$/) {

Στο παρακάτω παράδειγμα υπάρχει ένα σύμβολο στην αρχή του αλφαριθμητικού, το ^.

if (/n/i)  {
if (/^n/i) {

Το πρώτο είναι αληθές αν η λέξη περιλαμβάνει το γράμμα ‘n’ οπουδήποτε σε αυτήν. Το δεύτερο συγκεκριμενοποιεί ότι το ‘n’ πρέπει να βρίσκεται στην αρχή του αλφαριθμητικού. Χρησιμοποίησε το ^ οπουδήποτε μπορείς, γιατί κάνει το regex γρηγορότερο και ασφαλέστερο, εάν ξέρεις ποιος είναι ο πρώτος χαρακτήρας.

 

Λογική αντιστροφή του regex

Εάν θέλεις να αντιστρέψεις λογικά ολόκληρο το regex, μετέτρεψε το ‘=~’ σε ‘!=’

if ($_ !~/[KC]arl/) {

Το ίδιο δουλεύει και το :

if (!/[KC]arl/) {

Επιστροφή τιμής ταιριάσματος

Τι θα γίνει εάν θέλουμε να εμφανίσουμε κάτι από ένα string; Μέχρι τώρα κάναμε έλεγχο αληθείας χωρίς να επιστρέφονται αυτά που έχουμε βρεί. Τρέξε το παρακάτω :

$_='My email address is <Robert@NetCat.co.uk>.';

/(<robert\@netcat.co.uk>)/i;

print "Found it ! $1\n";

Πρώτον, πρόσεξε ότι όταν δηλώνεται το $_ χρησιμοποιούνται single quotes. Αν υπάρχουν double quotes χρησιμοποιείται το \@ στη θέση του @. Τα double quotes “ ” επιτρέπουν τη παρεμβολή των μεταβλητών, έτσι η Perl ψάχνει ένα πίνακα που ονομάζεται @NetCat και ο οποίος δεν υπάρχει.

Δεύτερον, πρόσεξε τις παρενθέσεις γύρω από ολόκληρο το regex. Όταν χρησιμοποιείς παρενθέσεις, το αποτέλεσμα είναι ότι το πρώτο ταίριασμα μπαίνει σε μια μεταβλητή που ονομάζεται $1. Το δεύτερο μπαίνει στην $2 κ.λ.π. Επίσης σημείωσε ότι το \@ περιέχει χαρακτήρα διαφυγής και η Perl δεν θεωρεί ότι είναι ένας πίνακας. Θυμήσου ότι το \ είτε τερματίζει έναν ειδικό χαρακτήρα είτε δίνει ένα ειδικό μήνυμα.

Ειδικοί χαρακτήρες κανονικών εκφράσεων

 Υποθέτουμε ότι δεν ξέρουμε πιο μπορεί να είναι το email address.

$_='My email address is <webslave@work.com>.';

print "Found it ! :$1:" if /(<.*>)/i;

Όταν δεις μια τέτοια πρόταση if διάβασέ την από δεξιά προς τα αριστερά. Η πρόταση print εκτελείται μόνο αν ο κώδικας στα δεξιά της έκφρασης είναι αληθής. Αυτό μπορούμε να το εξηγήσουμε. Πρώτον, αν το ταίριασμα είναι επιτυχές, μπαίνει στην $1 ότι υπάρχει από το άνοιγμα μέχρι το κλείσιμο των παρενθέσεων ( ). Μετά, ο πρώτος χαρακτήρας από αυτά που ψάχνουμε είναι ο ‘<’. Μετά είναι η τελεία ‘.’. Για τις κανονικές εκφράσεις αυτό σημαίνει ότι η τελεία ενώνει κάθε είδους χαρακτήρα. Έτσι τώρα ταιριάζουμε το < ακολουθούμενο από οποιονδήποτε χαρακτήρα. Το * σημαίνει μηδέν ή περισσότερους από τους προηγούμενους χαρακτήρες. Το regex τελειώνει με το >.

Ένα παράδειγμα το δείχνει αυτό καλύτερα.

$_='My email address is <webslave@work.com>.';

print "Found it ! :$1:" if /(<*>)/i;

Τι συμβαίνει εδώ; Το regex αρχίζει, λογικά, από την αρχή του string. Δεν σημαίνει ότι ξεκινάει από το ‘Μ’, αλλά πριν από αυτό. Δεν υπάρχει τίποτα μεταξύ της αρχής του string και του ‘Μ’. Το regex ψάχνει για ‘<*’, το οποίο είναι μηδέν ή περισσότερα ‘<’. Το πρώτο πράγμα που βρίσκει δεν είναι το ‘<’,αλλά το κενό ανάμεσα στην αρχή του string και του ‘Μ’ στο ‘My email…’.

Όσο το regex ψάχνει για ‘0 ή περισσότερα’ < μπορούμε σίγουρα να πούμε ότι υπάρχουν μηδέν < στην αρχή του string. Οπότε το ταίριασμα είναι ώς εδώ επιτυχές.

Ωστόσο, το επόμενο αντικείμενο για ταίριασμα είναι το >. Δυστυχώς το επόμενο αντικείμενο στο string είναι το ‘Μ’ από το “My email…”. Το ταίριασμα σε αυτό το σημείο αποτυγχάνει. Επομένως οι δυο χαρακτήρες που ταιριάζουν επιτυχώς σε αυτό το σημείο, είναι : < ή >. Στο σημείο αυτό αποτυγχάνει γιατί σε κανένα από αυτά τα ταιριάσματα δεν ήταν το ‘Μ’.

Το regex δεν έχει επιτυχές ταίριασμα στο <, μετά προχωράει μέσα στο string μέχρι το ταίριασμα να τελειώσει >. Οι χαρακτήρες μέσα στο string μεταξύ των < > επίσης πρέπει να ταιριάξουν το regex και σ’ αυτήν την περίπτωση δεν το κάνουν. Έγινε προσπάθεια για ταίριασμα του regex οπουδήποτε αυτό είναι πιθανό. Το σύστημα του regex συνεχίζει το ταίριασμα σε κάθε πιθανό σημείο του string, δουλεύοντας προς το τέλος. Ας κοιτάξουμε το ταίριασμα όταν φτάνει στο ‘m’ του ‘work.com’.

Εδώ πάλι έχουμε 0 < και το ταίριασμα δουλεύει όπως πριν. Μετά την επιτυχία του <* ο επόμενος χαρακτήρας που αναλύεται είναι ο >, και έτσι το ταίριασμα είναι επιτυχές. Μπορεί το ταίριασμα να έχει επιτύχει αλλά η δουλειά σου να μην έχει γίνει.

Κοίταξε το παρακάτω παράδειγμα που εξηγεί το * :

$_='My email address is <webslave@work.com>.';
print "Match 1 worked :$1:" if /(<*)/i;

$_='<My email address is <webslave@work.com>.';
print "Match 2 worked :$1:" if /(<*)/i;

$_='My email address is <webslave@work.com<<<<>.';
print "Match 3 worked :$1:" if /(<*>)/i;

Το ταίριασμα ένα είναι αληθές . Δεν επιστρέφει τίποτα , αλλά είναι αληθές γιατί υπάρχουν 0 ‘<’ στην αρχή του string.

Το ταίριασμα δύο δουλεύει. Μετά το 0 ‘<’ στην αρχή του string, υπάρχει 1’<’ και έτσι το regex μπορεί να το ταιριάξει επίσης.

Το ταίριασμα τρία δουλεύει. Μετά την αποτυχία του πρώτου <, πάει στο δεύτερο. Μετά από αυτό υπάρχουν πολλά για ταίριασμα μέχρι το τέλος. Έχουμε τον κώδικα :

$_='HTML <I>munging</I> time !.';

/<I>(.*)<\/I>/i;

print "Found it ! $1\n";

Τώρα υποθέτουμε ότι αλλάζει το $_ σε :

$_='HTML <I>munging</I> time is here <I>again</I> !.';

Τρέξτο τώρα ξανά. Είναι γνωστό σαν Greedy Matching. Όταν η Perl βρεί το αρχικό ταίριασμα, το οποίο είναι το <I>, πάει στο τέλος του string και ξεκινάει από εκεί προς τα πίσω να βρεί ένα ταίριασμα. Έτσι ταιριάζει το μακρύτερο string. Αν όμως θέλεις το ταίριασμα του μικρότερου, υπάρχει η παρακάτω λύση :

/<I>(.*?)<\/I>/i;

Πρόσθεσε το ερωτηματικό ‘?’ και η Perl κάνει το μικρότερο ταίριασμα.

Η διαφορά μεταξύ + και *

Το *, όπως είδαμε, σημαίνει ‘0 ή περισσότερα’ ταιριάσματα. Αν όμως θέλεις ‘1 ή περισσότερα’ τότε χρησιμοποιείς το +. Έχουν σημαντική διαφορά.

$_='The number is 2200 and the day is Monday';

($star)=/([0-9]*)/;

($plus)=/([0-9]+)/;

print "Star is '$star' and Plus is '$plus'\n";

Το $star δεν έχει τιμή. Το ταίριασμα ήταν επιτυχές. Κατάφερε να ταιριάξει ‘0 ή περισσότερους’ χαρακτήρες από το 0 έως το 9 από την αρχή του regex.

Το δεύτερο regex με το $plus δουλεύει λίγο καλύτερα, γιατί έχουμε ταίριασμα 1 ή περισσότερων χαρακτήρων από το 0 έως το 9. Αν βρεθεί ένα ταίριασμα από το 0 έως το 9, τότε θα αποτύχει. Μόλις βρεθεί ένα από το 0-9 το ταίριασμα συνεχίζει μέχρι ο επόμενος χαρακτήρας να είναι 0-9, και μετά σταματάει.

Υπάρχει ένας άλλος τρόπος να αφαιρέσουμε ένα email address μέσα από τα angle brackets:

$_='My email address is <robert@netcat.co.uk> !.';

/<([^>]+)/i;

print "Found it ! $1\n";

Αντικατάσταση και ακόμη περισσότερες δυνατότητες των κανονικών εκφράσεων

Βασικές Αλλαγές

Υποθέτουμε ότι θέλουμε να αντικαταστήσουμε κομμάτι του string. Για παράδειγμα το ‘us’ με το ‘them’:

$_='Us ? The bus usually waits for us, unless the driver forgets us.';

print "$_\n";

s/Us/them/;   # operates on $_, otherwise you need $foo=~s/Us/them/;

print "$_\n";

Αναζητείται στο string το ‘Us’ και όταν αυτό βρεθεί αντικαθιστάται με το ‘them’. Θα παρατηρήσεις ότι έτσι γίνεται μία μόνο αντικατάσταση. Εάν θέλεις αντικατάστασή του σε όλο το μήκος του string, τότε χρησιμοποίησε το :

s/Us/them/g;

το οποίο αποτυγχάνει, γιατί τα regexes δεν έχουν ευαισθησία. Γι΄αυτό κάνε:

s/us/them/ig;

το οποίο αποτυγχάνει, γιατί τα regexes δεν έχουν ευαισθησία. Γι΄αυτό κάνε:

s/ us[. ,]/them/g;

και θα γίνουν όλες οι αλλαγές. Οτιδήποτε έχεις μάθει για τα regexes μπορεί να χρησιμοποιηθεί με το ‘s///’, όπως οι παρενθέσεις, [ ], greedy and stingy matching και πολλά άλλα. Η διαγραφή είναι εύκολη, αρκεί να μην ορίσεις τον χαρακτήρα αντικατάστασης, όπως: s/Us//; Κάνοντας χρήση των παραπάνω γνώσεων διορθώνουμε το πρόβλημα. Πρέπει να είμαστε σίγουροι ότι ένα κενό προηγείται του ‘us’ : s/ us/them/g; Υπάρχει τώρα κάποια βελτίωση. Το πρώτο ‘Us’ δεν αλλάζει πλέον, αλλά υπάρχει πάλι κάποιο πρόβλημα : αλλάζονται και άλλες λέξεις που περιέχουν το ‘us’ όπως είναι η ‘usually’. Αυτό που ψάχνουμε είναι ένα κενό, μετά το ‘us’, μετά ένα κόμμα, περίοδο ή κενό. Ξέρουμε πώς να ορίσουμε ένα από τα νούμερα επιλογής- την κλάση χαρακτήρα (class character).

s/ us[. ,]/them/g;

Δυστυχώς το προηγούμενο βήμα δεν ήταν προς τη σωστή κατεύθυνση, αλλά ένα παράδειγμα φτωχού προγραμματισμού, γιατί μας περιορίζει. Δεν μπορείς να σκεφτείς όλους τους πιθανούς συνδυασμούς. Είναι συνήθως ευκολότερο και πιο σίγουρο να καθορίσεις τι δεν ακολουθεί το ταίριασμα. Στην περίπτωση αυτή, μπορεί να είναι οτιδήποτε εκτός από γράμμα. Το ορίζουμε από a-z και το προσθέτουμε στο regex:

s/ us[^a-z]/ them/g;

το σύμβολο ^ αρνείται οτιδήποτε υπάρχει στα [ ], και το a-z αναπαριστά κάθε αλφαβητικό γράμμα ανάμεσα στο a-z. Το κενό έχει προστεθεί στο κομμάτι αντικατάστασης.

 \w

Η χρήση του a-zA-Z είναι περισσότερο ενδιαφέρουσα, εάν δεν έχουμε χρησιμοποιήσει το /I. Καθώς το a-zA-Z είναι μια κατασκευή, η Perl παρέχει μια εύκολη στενογραφία :

s/ us[^\w]/ them/g;

Το \w σημαίνει ‘word’ – ισοδύναμο του a-zA-Z_0-9. Έτσι μπορούμε να χρησιμοποιήσουμε το ένα στη θέση του άλλου.

Για να είναι αρνητικό το \w το μετατρέπω ως :

s/ us[\W]/ them/g;

Και έτσι δεν χρειάζεται το σύμβολο ^.

Ουσιαστικά ούτε οι αγκύλες χρειάζοναι. Έτσι είναι :

s/ us\W/ them/g;

Είναι δύσκολο να ταιριάξεις το πρώτο ‘us’. Ευτυχώς υπάρχει μια εύκολη λύση. Μεταξύ των λέξεων υπάρχει κάποιο σύνορο. Μπορείς να το αντικαταστήσεις με το ‘\b’.

s/\bus\W/ them/g;

(είναι το \b ακολουθούμενο από το ‘us’, και όχι ‘bus’).

Χρειαζόμαστε μια λέξη σύνορο πριν το ‘us’. Επειδή δεν υπάρχει τίποτα στην αρχή του string έχουμε ταίριασμα. Υπάρχει ένα κενό μετά το πρώτο ‘Us’ και έτσι το ταίριασμα επιτυγχάνει. Μπορείς να παρατηρήσεις ένα επιπλέον κενό, αυτό που χρησιμοποιήσαμε πριν. Το ταίριασμα δεν περιλαμβάνει κανένα κενό πια – ταιριάζεται με τη λέξη σύνορο, η οποία βρίσκεται μόλις πριν από την αρχή της λέξης. Το κενό δεν υπολογίζεται.

Παρατήρησες ότι η τελική περίοδος και το κόμμα έχουν αντικατασταθεί;

Είναι ένα μέρος του ταιράσματος, το :

Αντικατάσταση με αυτά που έχουν βρεθεί

\w, που τα ενώνει. Δεν μπορούμε να το αποφύγουμε. Μπορούμε ωστόσο να το επαναφέρουμε :

s/\bus(\W)/them\1/g;

Ξεκινάμε συλλαμβάνοντας το ταίριασμα του \w χρησιμοποιώντας παρενθέσεις. Μετά το προσθέτουμε στο αντικαταστώμενο string. Η σύλληψη βρίσκεται στην $1, αλλά καθώς αυτή βρίσκεται μέσα σε ένα regex, αναφερόμαστε σε αυτήν με το ‘\1’.

Το τελικό πρόβλημα είναι να γράψουμε με κεφαλαία γράμματα το αντικαταστώμενο string, όταν αρμόζει.

Η λύση αυτού του προβλήματος, όπως δόθηκε από τον Paul Trafford, είναι η παρακάτω :

 

Λύση στο πρόβλημα us/them

Το πρόγραμμα δουλεύει ορίζοντας τη μεταβλητή $1 στο ‘u’ ή ‘U’, για κάθε λέξη όπου αυτό το γράμμα ακολουθείται από το ‘s’ και όχι από άλλους χαρακτήρες. Το τελευταία γράμμα ορίζεται στην μεταβλητή $2.

Για κάθε γεγονός του ταιριάσματος, η $1 έχει αντικατασταθεί από το γράμμα που προηγείται στο αλφάβητο, χρησιμοποιώντας τις λειτουργίες ‘ord’ και ‘chr’, οι οποίες επιστρέφουν την τιμή ASCII του χαρακτήρα και έτσι αυτός ο χαρακτήρας ανταποκρίνεται στο φυσικό αριθμό που δόθηκε. Μετά από αυτό το ‘them’ προστίθεται, ακολουθούμενο από το $2, ώστε να διατηρήσει την αρχική μορφή της πρότασης. Το ‘/e’ χρησιμοποιείται για εκτίμηση.

 

Σημειώσεις :

 

  1. Αυτή η λύση δεν θα αντικαταστήσει το ‘Us’ με το ‘Them’ ή το ‘them’.
  2. Εάν ο μαγικός τελεστής μείωσης ’—’ υπάρχει για τα strings, τότε η λύση μπορεί να γίνει απλούστερη, χωρίς να απαιτείται η χρήση των ‘chr’ και ‘ord’.

Ο παρακάτω κώδικας μας δίνει τη λύση του Paul:

$_='Us ? The bus usually waits for us, unless the driver forgets us.';

print "$_\n";

s/\b([Uu])s(\W)/chr(ord($1)-1).hem.$2/eg;

print "$_\n";

 

Υπάρχουν αρκετές ακόμα κατασκευές. Θα ρίξουμε μια γρήγορη ματιά στο ‘\d’, το οποίο σημαίνει οτιδήποτε είναι ψηφίο, δηλαδή 0-9. Πρώτα θα χρησιμοποιήσουμε την αρνητική μορφή, ‘\D’, η οποία σημαίνει οτιδήποτε εκτός από το 0-9.

print "Enter a number :";
chop ($input=<STDIN>);

if ($input=~/\D/) {
        print "Not a number !!!!\n";
} else {
        print 'Your answer is ',$input x 3,"\n";

}

Αυτό ελέγχει ότι δεν υπάρχουν μη αριθμητικοί χαρακτήρες στην $x. Δεν είναι τέλειο, γιατί δεν συμπεριλαμβάνει τα δεκαδικά ψηφία, αλλά είναι απλά ένα παράδειγμα.

Χ

Το Χ του παραπάνω παραδείγματος δεν είναι λάθος, αλλά είναι ένα χαρακτηριστικό γνώρισμα.

unless ($input=~/\d/) {
        print 'Your answer is ',$input x 3,"\n";
} else {
        print "Not a number !!!!\n";
}

Το παραπάνω αντιστρέφει το λογικό με την ‘unless’ πρόταση.

Περισσότερο Ταίριασμα

Υποθέτουμε ότι έχουμε :

$_='HTML <I>munging</I> time is here <I>again</I> !.';

 

Θέλουμε να βρούμε όλες τις λέξεις που είναι γραμμένες με πλάγια γράμματα. Γνωρίζουμε ότι το ‘/g’ ταιριάζει γενικά, και έτσι σίγουρα το παρακάτω θα δουλέψει :

$_='HTML <I>munging</I> time is here <I>again</I> ! What <EM>fun</EM> !';

$match=/<i>(.*?)<\/i>/ig;

print "$match\n";

Ο τελεστής του ταιριάσματος επιστρέφει ‘true’ ή ‘false’ και όχι τον αριθμό των matches. Μπορείς να ελέγξεις για το αληθές με τις συναρτήσεις If, while, unless. Ο τελεστής ‘s///’ επιστρέφει τον αριθμό των αντικαταστάσεων.

Για να επιστρέψεις αυτά που έχουν ταιριαστεί χρειάζεσαι μια λίστα :

($match) = /<i>(.*?)<\/i>/i;

 

η οποία βάζει όλα τα πρώτα ταιριάσματα μέσα στο :’$match’. Σημείωσε ότι ένα ‘=’ χρησιμοποιείται για δήλωση-αντιστοίχιση, σε αντίθεση με το ‘=~’ το οποίο χρησιμοποιείται για να εκχωρίσει το regex σε μια μεταβλητή, διαφορετική από την $_. Σε αυτή την περίπτωση οι παρενθέσεις δίνουν τα περιεχόμενα της λίστας. Υπάρχει μόνο ένα στοιχείο στη λίστα, αλλά και πάλι είναι λίστα. Ολόκληρο το ταίριασμα θα δηλωθεί σε μια λίστα, ή οτιδήποτε είναι στις παρενθέσεις. Προσπάθησε να προσθέσεις μερικές παρενθέσεις :

$_='HTML <I>munging</I> time is here <I>again</I> ! What <EMfun</EM> !';

($word1, $word2) = /<i>(.*?)<\/i>/ig;

print "Word 1 is $word1 and Word 2 is $word2\n";

Στο παραπάνω παράδειγμα πρόσεξε ότι έχει προστεθεί το /g και έτσι έχει γίνει μια γενική αντικατάσταση- αυτό σημαίνει ότι η perl συνεχίζει το ταίριασμα ακόμη και αν έχει βρεί το πρώτο ταίριασμα. Φυσικά, πρέπει να ξέρεις πόσα ταιριάσματα πρόκειται να γίνουν. Έτσι μπορείς να χρησιμοποιήσεις έναν πίνακα, ή οποιονδήποτε άλλο τύπο της λίστας :

$_='HTML <I>munging</I> time is here <I>again</I> ! What <EMfun</EM> !';

@words = /<i>(.*?)<\/i>/ig;

foreach $word (@words) {
        print "Found $word\n";
}

Ο πίνακας @words θα αυξηθεί στο κατάλληλο μέγεθος για τα ταιριάσματα. Μπορείς να αναφέρεις αυτά που θέλεις να δηλώσεις :

($word1, @words[2..3], $last) = /<i>(.*?)<\/i>/ig;

Χρειάζεσαι περισσότερες λέξεις με πλάγιους χαρακτήρες για να δουλέψεις.

Υπάρχει άλλο ένα τρίκ που αξίζει να μάθεις. Αφού ένα regex επιστρέφει true κάθε φορά που γίνεται το ταίριασμα, μπορούμε να το ελέγξουμε και να κάνουμε κάτι κάθε φορά που επιστρέφει την τιμή true. Η ιδανική συνάρτηση είναι η while που σημαίνει ‘κάνε κάτι όσο η πρόταση που ελέγχω είναι αληθής (true)’ . Σε αυτή την περίπτωση, θα εκτυπώνουμε το ταίριασμα κάθε φορά που αυτό είναι true.

$_='HTML <I>munging</I> time is here <I>again</I> ! What <EMfun</EM> !';

while (/<(.*?)>(.*?)<\/\1>/g) {
        print "Found the HTML tag $1 which has $2 inside\n";
}

Έτσι ο τελεστής while τρέχει το regex, και αν αυτό είναι true, διεκπεραιώνει τις προτάσεις μέσα στο block.

Προσπάθησε να τρέξεις το παραπάνω πρόγραμμα χωρίς το /g. Πρόσεξες ότι η loop δεν έχει τέλος; Αυτό συμβαίνει γιατί η έκφραση εκτιμάται πάντα σαν true. Με την χρησιμοποίηση του /g δίνουμε τη δυνατότητα στο ταίριασμα να συνεχίσει, μέχρι τελικώς να αποτύχει.

Τώρα γνωρίζουμε έναν εύκολο τρόπο που βρίσκει τον αριθμό των ταιριασμάτων, και είναι ο παρακάτω :

$_='HTML <I>munging</I> time is here <I>again</I> ! What <EMfun</EM> !';

$found++ while /<i>.*?<\/i>/ig;

print "Found $found matches\n";

Σε αυτή την περίπτωση δεν χρειάζονται παρενθέσεις, καθώς τίποτα εκτός από την έκφραση που θα εκτιμηθεί δεν ακολουθεί την συνάρτηση while.

Ξανα παρενθέσεις : OR

Η πραγματική χρήση τους. Δοκίμασε αυτό :

$_='One word sentences ? Eliminate. Avoid clichιs like the plague.  They are old hat.';

while (/o(rd|ne|ld)/gi) {
        print "Matched $1\n";
}

Πρόσεξε τη χρήση του τελεστή or, στην περίπτωση αυτή το ‘|’. Το regex εδώ ταιριάζει το ‘ο’ το οποίο ακολουθείται από το rd, ne ή ld. Χωρίς τις παρενθέσεις θα ήταν : /ord\ne\ld/, το οποίο όμως δεν είναι αυτό που εμείς θέλουμε.

 

(?: OR Efficiency)

print "Give me a name :";
chop($_=<STDIN>);

print "Good name\n" if /Pe(tra|ter|nny)/;

Ο παραπάνω κώδικας λειτουργεί σωστά. Το regex δεν είναι όσο ικανό θα έπρεπε. Σκέψου τι κάνει η perl με το regex που μόλις έχεις αγνοήσει.

Επειδή χρησιμοποιούνται παρενθέσεις, η perl δημιουργεί την $1 για δική σου χρήση και κατάχρηση. Μια σημαντική ποσότητα από πηγές καταχωρούνται δημιουργώντας τις $1, $2 κ.λ.π. Λίγη μόνο μνήμη χρησιμοποιείται για να τις αποθηκεύσει, περισσότερο αναμιγνύεται η προσπάθεια της CPU.

print "Give me a name :";
chop($_=<STDIN>);

print "Good name\n" if /Pe(?:tra|ter|nny)/;

print "The match is :$1:\n";

Η δεύτερη πρόταση εκτύπωσης δείχνει ότι τίποτα δεν βρέθηκε αυτή τη φορά. Υπάρχει το πλεονέκτημα της προτεραιότητας παρενθέσεων, το οποίο είναι σημαντικό στα προγράμματα που χρησιμοποιούν παρενθέσεις στα regex.

Ταίριασμα ειδικών τιμών με…

Κοίταξε το παρακάτω :

$_='I am sleepy....zzzz....DING ! Wake Up!';

if (/(z{5})/) {
        print "Matched $1\n";
} else {
        print "Match failed\n";
}

Οι αγκύλες { } ορίζουν πόσοι χαρακτήρες προτεραιότητας θα ταιριαστούν. Έτσι το Ζ{2} ταιριάζει ακριβώς 2 Ζ και έτσι συνεχίζει. Άλλαξε το Ζ{5} σε Ζ{4} και κοίταξε πως δουλεύει.

/z{3}/

3 z μόνο

/z{3,}/

Τουλάχιστο 3 z

/z{1,3}/

1 έως 3 z

/z{4,8}/

4 έως 8 z

 

Σε καθένα από τα παραπάνω πρόσθεσε ένα ερωτηματικό στην κατάληξη, το αποτέλεσμα φαίνεται στο παρακάτω πρόγραμμα. Τρέξτο μερικές φορές βάζοντας 2, 3 και 4 :

print "How many letters do you want to match ? ";
chomp($num=<STDIN>);

# we assign and print in one smooth move
print $_="The lowest form of wit is indeed sarcasm, I don't think.\n";

print "Matched \\w{$num,} : $1 \n"  if /(\w{$num,})/;

print "Matched \\w{$num,?}: $1 \n"  if /(\w{$num,}?)/;

Το πρώτο ταίριασμα είναι ‘ταιριάζει κάθε λέξη( a-zA-Z) ίση ή μεγαλύτερη του $num και το επιστρέφει’. Αν λοιπόν βάλεις 4, επιστρέφεται το lowest. Η λέξη ‘The’ δεν ταιριάζει. Το δεύτερο ταίριασμα είναι ακριβώς το ίδιο, αλλά το ? δίνει το minimal match, και έτσι επιστρέφεται μόνο το τμήμα που ταιριάστηκε. Για να καταλάβεις το παραπάνω, τροποποίησε το πρόγραμμα που ακολουθεί :

print "\nMatched \\w{$num,} :";
print "$1 " while /(\w{$num,})/g;

print "\nMatched \\w{$num,?} :";
print "$1 " while /(\w{$num,}?)/g;


Pre, Post και Match

Παρακάτω θα ασχοληθούμε με τα Prematch, Postmatch και Match :

$_='I am sleepy....snore....DING ! Wake Up!';

/snore/;	# look, no parens !

print "Postmatch: $'\n";
print "Prematch: $`\n";
print "Match: $&\n";

Η διαφορά του Match και της χρήσης παρενθέσεων είναι ότι οι παρενθέσεις μπορούν να μετακινηθούν οπουδήποτε, ενώ δεν μπορείς να αλλάξεις τα σύμβολα $ & και αυτό που επιστρέφουν. Η χρήση των τριών παραπάνω τελεστών κάνει πιο αργή την εκτέλεση του προγράμματος, σε αντίθεση με την χρησιμοποίηση παρενθέσεων, οι οποίες απλά καθυστερούν τα regex στα οποία χρησιμοποιούνται.

 

RHS Εκφράσεις

RHS σημαίνει Right Hand Side. Υποθέτουμε ότι έχουμε ένα HTML αρχείο το οποίο περιέχει

<FONT SIZE=2> <FONT SIZE=4> <FONT SIZE=6>

 Και θέλουμε να διπλασιάζει το font size, έτσι το 2 γίνεται 4, το 4 γίνεται 8 κ.λ.π. Ο παρακάτω κώδικας δεν δουλεύει :

$data="<FONT SIZE=2> <FONT SIZE=4> <FONT SIZE=6>";

print "$data\n";

$data=~s/(size=)(\d)/\1\2 * 2/ig;

print "$data\n";

Αυτό που κάνει είναι να αντιστοιχεί size=x, όπου x είναι οποιοδήποτε ψηφίο. Η πρώτη αντιστοίχιση, size=, πάει στη μεταβλητή $1, και το δεύτερο, όποιο ψηφίο και αν είναι, πηγαίνει στην $2. Το δεύτερο τμήμα του regex τυπώνει τα $1 και $2 και προσπαθεί να πολλαπλασιάσει το $2 με το 2. Θυμήσου ότι το /I σημαίνει την μη ευαισθησία του matching.

Πρέπει να εκτιμήσουμε το RHS του regex σαν μια έκφραση, όχι απλά να εκτυπώσουμε αυτά που λέει, αλλά να τα εκτιμήσουμε. Αυτό σημαίνει ότι δεν πρέπει να του συμπεριφερθούμε απλά σαν string. Η Perl μπορεί να κάνει αυτό :

$data=~s/(size=)(\d)/$1.($2 * 2)/eig;

Το LHS είναι το ίδιο με το προηγούμενο.

 

/e

Προσθέτουμε το /e και η Perl εκτιμάει το RHS σαν έκφραση. Έτσι πρέπει να αλλάξουμε το /i σε $1 κ.λ.π. Οι παρενθέσεις υπάρχουν για να σιγουρέψουν ότι $2*2 εκτιμάται, μετά ενώνεται με το $1.

 /ee

Είναι πιθανό να έχουμε περισσότερα από ένα /e.

Για παράδειγμα :

$data='The function is <5funcA>';

$funcA='*2+4';

print "$data\n";

$data=~s/<(\d)(\w+)>/($1+2).${$2}/;	# first time
# $data=~s/<(\d)(\w+)>/($1+2).${$2}/e;	# second time
# $data=~s/<(\d)(\w+)>/($1+2).${$2}/ee;	# third time

print "$data\n";

Για να το εκτιμήσεις σωστά πρέπει να το τρέξεις τρείς φορές και κάθε φορά να σχολιάζεις και μια διαφορετική γραμμή. Μόνο μια γραμμή του regex πρέπει να είναι ασχολίαστη όταν το πρόγραμμα τρέχει. Την πρώτη φορά η Perl ψάχνει το string για κάθε μεταβλητή, βρίσκει τις $1 και $2 και τις αντικαταστεί. Την δεύτερη φορά εκτιμάται το $1+2. Το $1 έχει την τιμή 5, pl, plus 2= =7. Το άλλο τμήμα της αντικατάστασης, ${$2} εκτελείται μέχρι η μεταβλητή που ονομάζεται $2 να τοποθετηθεί στο string.

Την τρίτη φορά η Perl κάνει ένα δεύτερο πέρασμα στο string ψάχνοντας να κάνει κάτι. Ύστερα από το πρώτο πέρασμα και πριν από το δεύτερο το string μοιάζει με το : 7*2+4. Η Perl το εκτιμάει και τυπώνει το αποτέλεσμα.

Ένα έτοιμο παράδειγμα : Αλλαγή Ημερομηνίας

Έχουμε μια λίστα από ημερομηνίες οι οποίες έχουν την αμερικάνικη μορφή : μήνας, ημέρα, χρόνος σε αντίθεση με την λογική αντίληψη του υπόλοιπου κόσμου που θέλει τις ημερομηνίες στη μορφή : ημέρα, μήνας, χρόνος. Χρειαζόμαστε λοιπόν ένα regex για να μεταθέσουμε την ημέρα και το μήνα. Οι ημερομηνίες είναι :

@dates=(
'01/22/95',
'05/15/87',
'8-13-96',
'5.27.78',
'6/16/1993'
);

Η διαδικασία μπορεί να χωριστεί σε βήματα, όπως :

 

  1. Συνδύασε το πρώτο ψηφίο, ή τα δύο ψηφία. Κράτησε αυτό το αποτέλεσμα.
  2. Συνδύασε τον οριοθέτη, που όπως φαίνεται είναι ένας από τους ‘ / - . ’.
  3. Συνδύασε τα επόμενα δύο ψηφία, και κράτησε αυτό το αποτέλεσμα.
  4. Ξαναφτιάξε το string, αλλά αυτή τη φορά αντιμεταθέτοντας την ημέρα και το μήνα.

Μπορεί αυτά να μην είναι όλα τα βήματα, αλλά σίγουρα είναι αρκετά για αρχή. Το να σχεδιάζεις το regex είναι σημαντικό. Έτσι :

@dates=(
'01/22/95',
'5/15/87',
'8-13-96',
'5.27.78',
'6/16/1993'
);

foreach (@dates) {
	print;
	s#(\d\d)/(\d\d)#$2/$1#;
	print " $_\n";
}

Το παραπάνω δεν θα δουλέψει για τις ημερομηνίες που οριοθετούνται με τα ‘ - .’, και η τελευταία ημερομηνία δεν θα δουλέψει επίσης. Το πρώτο πρόβλημα είναι αρκετά εύκολο. Απλά συνδυάζουμε το ‘/’. Το δεύτερο δεύτερο πρόβλημα προκύπτει γιατί συνδυάζουμε δύο ψηφία. Έτσι λοιπόν, στην 5/15/87 συνδυάζεται το 15 και 87, όχι το 5 και 15. Η ημερομηνία 6/16/1993 συνδυάζει το 16 και 19 του 1993.

Μπορούμε να διορθώσουμε και τις δύο περιπτώσεις.

Πρώτον, θα συνδυάσουμε είτε 1 είτε 2 ψηφία. Υπάρχουν αρκετοί τρόποι για να γίνει αυτό, όπως το ‘ \d{1,2}’ , το οποίο σημαίνει είτε 1 είτε 2 από τους προηγούμενους χαρακτήρες, ή ίσως πιο εύκολα

‘ \d\d?’ το οποίο σημαίνει ταίριαξε ένα \d και το άλλο ψηφίο είναι προαιρετικό, μετά το ερωτηματικό ‘?’. Αν χρησιμοποιήσουμε ‘\d+’ τότε θα συνδύαζε 1998883 που δεν υφίσταται σαν ημερομηνία, τουλάχιστον με τα δικά μας δεδομένα.

Δεύτερον,θα χρησιμοποιήσουμε ένα χαρακτήρα κλάσης για όλους τους πιθανούς οριοθέτες των ημερομηνιών. Ακολουθεί η loop με αυτές τις τροποποιήσεις :

foreach (@dates) {
	print;
	s#(\d\d?)[/-.](\d\d?)#$2/$1#;
	print " $_\n";
}

Η οποία αποτυγχάνει. Έλεγξε προσεκτικά την πρόταση λάθους. Η λέξη κλειδί είναι ‘εύρος’. Τι είναι το εύρος; Το εύρος είναι μεταξύ ‘/’ και ‘.’, αφού ο ‘-’ είναι ο τελεστής εύρους σε ένα χαρακτήρα κλάσης. Δηλαδή είναι ένας ειδικός χαρακτήρας ή ένας μεταχαρακτήρας. Για να αρνηθούμε την ειδική σημασία του μεταχαρακτήρα πρέπει να χρησιμοποιήσουμε ένα backslash.

Το ‘.’ είναι ένας μεταχαρακτήρας; Είναι, αλλά όχι σε ένα χαρακτήρα κλάσης και έτσι δεν χρειάζεται να διακοπεί.

foreach (@dates) {
	print;
	s#(\d\d?)[/\-.](\d\d?)#$2/$1#;
	print " $_\n";
}

Ωστόσο, πάντα αντικαταστούμε τον οριοθέτη με το ‘/’. Ακολουθεί ένας εύκολος τρόπος για να διορθωθεί :

foreach (@dates) {
	print;
	s#(\d\d?)([/\-.])(\d\d?)#$3$2$1#;
	print " $_\n";
}

Σε περίπτωση που αναρωτιέσαι, η τελεία ‘.’ δεν ενεργεί ως ‘1 of anything’(1 από οτιδήποτε) μέσα σε ένα χαρακτήρα κλάσης. Εάν γινόταν, τότε θα νικούσε το αντικείμενο του χαρακτήρα κλάσης. Έτσι δεν χρειάζεται να διακοπεί. Υπάρχει μια παραπέρα εξέλιξη που μπορείς να κάνεις σε αυτό το regex :

$m='/.-';

foreach (@dates) {
	print;
	s#(\d\d?)([$m])(\d\d?)#$3$2$1#;
	print " $_\n";
}

Παρατήρησες τη διαφορά μεταξύ του τι αντιστοιχίσαμε στην $m και τι αντιστοιχίσαμε πρίν ;

/ \ -.

$m=’/.-’;

Η διαφορά είναι ότι το ‘-‘ δεν διακόπτεται πλέον. Γιατί όχι; Η Perl γνωρίζει ότι το ‘-’ είναι ένας τελεστής εύρους. Γι’ αυτό εδώ πρέπει να υπάρχει ένας χαρακτήρας αμέσως δεξιά και αριστερά από αυτόν για να δουλέψει, για παράδειγμα e-f. Όταν αντιστοιχούμε ένα string στην $m, ο range είναι ο τελευταίος χαρακτήρας και έτσι δεν υπάρχουν άλλοι χαρακτήρες στα δεξιά του. Έτσι η Perl δεν το ερμηνεύει σαν έναν τελεστή range . Δοκίμασε αυτό : $m=’/-.’; και πρόσεξε ότι αποτυγχάνει.

Κάτι άλλο που προκαλεί ανησυχία είναι το να συνδυάζονται αυτά που δεν θέλεις.

Δοκίμασε :

@dates=(
'01/22/95',
'5/15/87',
'8-13-96',
'5.27.78',
'/16/1993',
'8/1/993',
);

$m='/.-';

foreach (@dates) {
	print;
	s#(\d\d?)([$m])(\d\d?)#$3$2$1# or print "Invalid date! ";
	print " $_\n";
}

Οι δύο άκυρες ημερομηνίες στο τέλος αγνοούνται. Αν θέλεις να ελέγξεις την εγκυρότητα από κάθε πιθανή ημερομηνία από την αρχή του σύγχρονου ημερολογίου τότε καλύτερα να το κάνεις με μια βάση δεδομένων παρά με ένα regex, αλλά μπορούμε να κάνουμε κάποιο βασικό έλεγχο. Το σημαντικό είναι ότι γνωρίζουμε τους περιορισμούς από αυτό που κάνουμε.

Δύο πράγματα είναι σίγουρα. Ότι υπάρχουν τρία set ψηφίων που χωρίζονται από έναν επιλεγμένο οριοθέτη, και ότι το τελευταίο set ψηφίων είτε είναι δύο ψηφία, π.χ 99, 98, 87, είτε είναι τέσσερα ψηφία, π.χ 1199, 1998, 1987. Πώς μπορούμε να το κάνουμε αυτό; Επέκτεινε το ταίριασμα. Μετά το ταίριασμα του δεύτερου ψηφίου πρέπει να ταιριάξουμε πάλι τον οριοθέτη, μετά είτε 2 ψηφία είτε 4 ψηφία.

$m='/.-';

foreach (@dates) {
	print;
	s#(\d\d?)([$m])(\d\d?)[$m](\d\d|\d{4})#$3$2$1$2# or print "Invalid date! ";
	print " $_\n";
}

Το παραπάνω στην πραγματικότητα δεν δουλεύει. Το πρόβλημα είναι ότι το 993 αγνοείται. Αυτό γιατί το ‘\d\d’ θα ταιριάξει μπροστά από το 993. Έτσι δεν έχουμε διορθώσει το χρόνο πίσω στο τελικό αποτέλεσμα. Το ταίριασμα του οριοθέτη είναι επίσης ελαττωματικό. Μπορούμε να ταιριάξουμε το ‘/’ σαν τον πρώτο οριοθέτη και το ‘-‘ σαν τον δεύτερο. Έτσι υπάρχουν τρία προβλήματα να διορθωθούν :

foreach (@dates) {
	print;
	s#(\d\d?)([$m])(\d\d?)\2(\d\d|\d{4})$#$3$2$1$2$4# or print "Invalid!";
	print " $_\n";
}

Τώρα μοιάζει με σοβαρό regex.

Αλλαγές :

  1. Ξαναχρησιμοποιούμε το δεύτερο ταίριασμα, που είναι ο οριοθέτης, στο regex. Αυτό είναι το \2. Αυτό σιγουρεύει ότι ο δεύτερος οριοθέτης είναι ο ίδιος με τον πρώτο, έτσι το 5/7-98 απορρίπτεται.
  2. Το $ στο τέλος σημαίνει το τέλος του string. Δεν επιτρέπεται τίποτα μετά από αυτό. Τώρα λοιπόν έχει να βρεί είτε 2 είτε 4 ψηφία στο τέλος του string, ή αποτυγχάνει.
  3. Προσθέτουμε το ταίριασμα του χρόνου ($4) στο καινούριο τμήμα του regex.

Το regex μπορεί να είναι τόσο περίπλοκο όσο χρειάζεται. Ο παραπάνω κώδικας μπορεί να βελτιωθεί και άλλο. Μπορούμε να απορρίψουμε όλα τα χρόνια που δεν αρχίζουν με 19 ή 20 αν είναι χρόνια με 4 ψηφία. Ένα άλλο πρόβλημα του κώδικα μέχρι εδώ είναι ότι θα απορρίψει το ‘a date like 02\24\99 which is valid’ απλά επειδή υπάρχουν χαρακτήρες μετά το χρόνο. Τα παραπάνω δύο μπορούν να διορθωθούν :

@dates=(
'01/22/95',
'5/15/87',
'8-13-96',
'5.27.78',
'/16/1993',
'8/1/993',
'3/29/1854',
'! 4/23/1972 !',
);

$m='/.-';

foreach (@dates) {
	print;
	s#(\d\d?)([$m])(\d\d?)\2(\d\d|(?:19|20)\d{2})(?:$|\D)#$3$2$1$2$4# or print "Invalid!";
	print " $_\n";
}

Τώρα έχουμε ένα φωλιασμένο OR και το εσωτερικό OR δεν γίνεται εύκολα αντιληπτό.

Αυτό που είναι σημαντικό είναι να φτιάξεις το regex προσεκτικά, και να καταλάβεις τι μπορεί και τι δεν μπορεί να κάνει.

 

Split και Join (Χωρίζω και Ενώνω)

Splitting – Χώρισμα

Αρχίζουμε με την split που είναι απλούστερη.

$_='Piper:PA-28:Archer:OO-ROB:Antwerp';

@details=split /:/, $_;

foreach (@details) {
        print "$_\n";
}

Δίνονται δυο ορίσματα. Το πρώτο είναι ένα regex που συγκεκριμενοποιεί που θα κάνει το split. Το επόμενο είναι τί θα κάνει split. Το $_ μπορεί να παραληφθεί αφού ως συνήθως είναι το εξ’ ορισμού εάν τίποτα δεν συγκεκριμενοποιείται. Η δήλωση μπορεί είτε να είναι μια βαθμωτή μεταβλητή είτε μια λίστα, όπως ένας πίνακας. Εάν είναι μια βαθμωτή μεταβλητή παίρνεις τον αριθμό των στοιχείων που το split έχει χωρίσει. Εάν η δήλωση είναι ένας πίνακας, τότε όπως μπορείς να δεις στο παραπάνω παράδειγμα ο πίνακας έχει δημιουργηθεί με τα αναφερόμενα στοιχεία στη σειρά. Μπορείς επίσης να εκχωρήσεις σε βαθμωτές, για παράδειγμα :

$_='Piper:PA-28:Archer:OO-ROB:Antwerp';

($maker,$model,$name,$reg,$location) = split /:/, $_;
(@aircraft[0..1],$aname,@regdetails) = split /:/, $_;

$number=split /:/ ;             # not bothering with the $_ at the end, as it is the default

print "Using the first 'split'\n";
print "$reg is a $maker $model $name based in $location\n";
print "There are $number details available on this aircraft\n\n";

print "Using the second 'split'\n";
print "You can find $regdetails[0], an $aircraft[1], $regdetails[1]\n";

Αυτό δείχνει ότι η λίστα μπορεί να είναι μια λίστα από βαθμωτές μεταβλητές και ότι μπορείς να δείς εύκολα πόσα στοιχεία η έκφραση μπορεί να χωρίσει.

Το παραπάνω παράδειγμα προσθέτει μια τρίτη παράμετρο στο split, η οποία είναι πόσα στοιχεία θέλεις να επιστραφούν. Αν δεν θέλεις το επιπλέον υλικό στο τέλος κάντο ‘pop’.

$_='Piper:PA-28:Archer:OO-ROB:Antwerp';

@details=split /:/, $_, 3;

foreach (@details) {
        print "$_\n";
}

Στο παρακάτω παράδειγμα κάνουμε split στο whitespace. Whitespace, στην ορολογία της perl είναι ένα κενό, tab, καινούρια γραμμή, formfeed, ή carriage return. Αντί να γράψεις \t\n\f\r για καθένα από τα παραπάνω, μπορείς απλά να χρησιμοποιήσεις το \s ή την αρνητική έκδοση \S που σημαίνει οτιδήποτε εκτός από whitespace. Στην παρακάτω λίστα έχω χρησιμοποιήσει spaces, doublespaces, ένα tab και μια νέα γραμμή. Επίσης σημείωσε το ‘t’, το οποίο σημαίνει ένας ή περισσότεροι από τον προηγούμενο χαρακτήρα, και έτσι θα κάνει split σε κάθε συνδιασμό του whitespace. Η συνάρτηση split δεν επιστρέφει τον οριοθέτη, και έτσι σε αυτή την περίπτωση δεν θα επιστραφεί το whitespace.

$_='Piper       PA-28  Archer           OO-ROB
Antwerp';

@details=split /\s+/, $_;

foreach (@details) {
        print "$_\n";
}

@chars=split //, $details[0];

foreach $char (@chars) {
        print "$char !\n";
}


Ένα καλό FAQ

Η ερώτηση που ακολουθεί έχει αναφερθεί τουλάχιστον τρείς φορές στη λίστα μηνυμάτων των Perl-Win32 users. Μπορείς να την απαντήσεις;

"My data is delimited by |, for example:
name|age|sex|height|
Why doesn't
@array=split /|/, $line;
work ?"

Εάν δεν ξέρεις ήδη την απάντηση, αναφέρονται κάποια απλά βήματα. Πρώτον, δημιούργησε ένα δειγματικό πρόγραμμα και τρέξτο :

$line='name|age|sex|height';

@array=split /|/,$line;

foreach (@array) { print "$_\n" }

Το αποτέλεσμα είναι να χωρίζει (split) κάθε χαρακτήρα. Το ‘|’ επιστρέφεται. Καθώς είναι ο οριοθέτης το ‘|’ πρέπει να αγνοείται και όχι να επιστρέφεται. Το ‘|’ είναι ένας μεταχαρακτήρας, που σημαίνει or όταν είναι μέσα σε ένα regex. Έτσι, σαν αποτέλεσμα, το regex ‘/ \ /’ σημαίνει ‘nothing or nothing’ . Το split εκτελείται στο ‘nothings’ και υπάρχουν ‘nothings’ μεταξύ του κάθε χαρακτήρα. Η λύση είναι εύκολη ‘/ \ | /’ .

$line='name|age|sex|height';

@array=split /\|/,$line;

foreach (@array) { print "$_\n" }

Τι χρειάζεται το Humprty Dumpty : Join - Ένωση

$w1="Mission critical ?";
$w2="Internet ready modems !";
$w3="J(insert your cool phrase here)";	# anything prefixed by 'J' is now cool ;-)
$w4="y2k compatible.";
$w5="We know the Web.";
$w6="...the leading product in an emerging market.";

$cool=join ' ', $w1,$w2,$w3,$w4,$w5,$w6;

print $cool;

Η join παίρνει ένα τελεστή ‘glue’, που δεν είναι μια κανονική έκφραση. Ωστόσο μπορεί να είναι μια βαθμωτή μεταβλητή. Στην περίπτωση αυτή είναι ένα κενό. Μετά παίρνει μια λίστα, που μπορεί να είναι ή μια λίστα από βαθμωτές μεταβλητές, ή ένας πίνακας ή οτιδήποτε, όσο είναι μια λίστα. Μπορείς να δείς πιο είναι το αποτέλεσμα και να το εκχωρήσεις σε ένα πίνακα.

Το παρακάτω παράδειγμα προσθέτει ένα πίνακα μέσα στη λίστα και δείχνει τη χρήση μιας μεταβλητής σαν οριοθέτη.

$w1="Mission critical ?";
$w2="Internet ready modems !";
$w3="J(insert your cool phrase here)"; 	# anything prefixed by 'J' is now cool ;-)
$w4="y2k approved, tested and safe !";
$w5="We know the Web.";
$w6="...the leading product in an emerging market.";
@morecool=("networkable","compatible");

$sep=" ";

$cool=join $sep, $w1,$w2,$w3,@morecool,$w4,$w5,$w6;

print $cool;


Ανακεφαλαίωση, αλλά με κάποιες καινούριες functions.

Τυχαιότητα


@cool=(
"networkable directory services",
"legacy systems compatible",
"Mission critical, Business Ready",
"Internet ready modems !",
"J(insert your cool phrase here)",
"y2k approved, tested and safe !",
"We know the Web. Yeah.",
"...the leading product in an emerging market."
);

srand;

print "How many phrases would you like (max ",scalar(@cool),") ?";
while (1) {
        chop ($input=<STDIN>);
        if ($input <= scalar(@cool) and $input > 0) {
                last;
        }
        print 'Sorry, invalid input, try again :';
}

for (1..$input) {
        $index=int(rand $#cool);
        print "$cool[$index] ";
        splice @cool, $index, 1;
}

Μερικά σημεία για εξήγηση. Πρώτον, while (1) {. Θέλουμε μια διαρκή επανάληψη, και αυτός είναι ένας τρόπος να το κάνουμε. Το 1 είναι πάντοτε true, έτσι εκτελείται συνεχώς. Μπορούμε άμεσα να ελέγξουμε το ‘$input’, αλλά δεν θα έπρεπε να δειχτεί το ‘last’. Οι ατελείωτες επαναλήψεις δεν είναι χρήσιμες. Πρέπει να τις σπάσουμε σε κάποιο σημείο. Αυτό έγινε με την συνάρτηση ‘last’. Όταν ‘$input’ είναι μεταξύ του 1 και τον αριθμό των στοιχείων στο @cool τότε μπορούμε να βγούμε.

Ο τελεστής srand αρχικοποιεί την γεννήτρια τυχαίου αριθμού. Λειτουργεί σωστά για μας, αλλά οι CGI προγραμματιστές πρέπει να σκεφτούν κάτι διαφορετικό γιατί τα προγράμματά τους είναι πολύ συχνά εκτελέσιμα.

Η ‘rand’ παράγει ένα τυχαίο αριθμό μεταξύ του 0 και 1, ή του 0 και του αριθμού που δίνεται. Σ’ αυτή την περίπτωση, τον αριθμό των στοιχείων του @cool-1, έτσι από 0 έως 7. Δεν υπάρχει νόημα να παραχθούν νούμερα μεταξύ 1 και 8, γιατί ο πίνακας στοιχείων τρέχει από 0 έως 7.

Η συνάρτηση ‘int’ σιγουρεύει ότι είναι ένας ακέραιος, δηλαδή δεν υπάρχουν bits μετά τη δεκαδική τελεία.

Η συνάρτηση ‘splice’ αφαιρεί το στοιχείο που τυπώθηκε από τον πίνακα, ώστε να μην εμφανιστεί πάλι.

Συνένωση

Υπάρχει και άλλος τελεστής συνένωσης, αυτή τη φορά η τελεία ‘.’. Συνενώνει

μεταβλητές :

$x="Hello";
$y=" World";
$z="\n";

print "$x\n";           # print $x and a newline

$prt=$x.$y.$z;          # make a new var $prt out of $x, $y and $z

print $prt;

$x.=$y." again ".$z;    # add stuff to $x

print $x;


Αρχεία

Άνοιγμα

Η Perl είναι πολύ καλή στο χειρισμό αρχείων. Δημιούργησε, στο δικό σου perl scripts directory c:\scripts, ένα αρχείο που ονομάζεται stuff.txt. Πληκτρολόγησε τα παρακάτω σε αυτό :

The Main Perl Newsgroup:comp.lang.perl.misc
The Perl FAQ:http://www.perl.com/faq/
Where to download perl:http://www.activestate.com/

Τώρα, για να ανοίξεις και να κάνεις πράγματα με αυτό το αρχείο. Πρώτον, πρέπει να ανοίξουμε το αρχείο και να το εκχωρήσουμε σε ένα filehandle (χειρισμό αρχείου). Όλες οι λειτουργίες θα γίνουν στο αρχείο μέσω του χειρισμού αρχείου. Προηγουμένως, χρησιμοποιήσαμε το <STDIN> σαν ένα λογικό αρχείο - διαβάζουμε από αυτό.

 

 

$stuff="c:\scripts\stuff.txt";

open STUFF, $stuff;

while (<STUFF>) {
        print "Line number $. is : $_";
}

Αυτό που κάνει το script είναι λάθος. Αυτό που θα έπρεπε να κάνει είναι να ανοίξει το αρχείο που ορίζεται στο $stuff, να το εκχωρήσει στο filehandle ‘STUFF’ και μετά, όσο υπάρχουν ακόμη γραμμές αριστερά στο αρχείο, να τυπώσει τον αριθμό γραμμής ‘$.’ και την τρέχουσα γραμμή.

 

Ένα ασυγχώρητο λάθος

Αυτό που είναι ασυγχώρητο είναι να μην ελέγχεις τον κώδικα λάθους.

Μια καλύτερη έκδοση :

open STUFF, $stuff or die "Cannot open $stuff for read :$!";

Εάν η λειτουργία ‘open’ αποτύχει, το ‘or’ σημαίνει ότι ο κώδικας στο RHS (right hand side) εκτιμάται. Να ελέγχεις πάντοτε τον επιστρεφόμενο κώδικα.

\\ or / in pathnames -- Επιλογή σου

Το πρόβλημα είναι τώρα φανερό. Τα backslashes, που είναι χαρακτήρες τερματισμού, δεν ενδείκνυνται. Υπάρχουν δύο τρόποι για να το διορθώσεις :

¨ Να τερματίσεις τα backslashes, όπως :

$stuff= “c:\\scripts\\stuff.txt”;

¨ Να μετατρέψεις τα backslashes σε forward slashes :

$stuff= “c:/scripts/stuff.txt”;

Τα forward slashes είναι η προτινόμενη επιλογή, ακόμη και στα Win32, γιατί μπορείς μετά να φέρεις το script κατευθείαν στο Unix. Αν επιθυμείς να χρησιμοποιείς Perl για να ξεκινήσεις άλλες εφαρμογές τότε πρέπει να χρησιμοποιείς την ‘\\’ μέθοδο, αλλά αυτή η μεταβλητή θα χρησιμοποιηθεί μόνο σε Perl πρόγραμμα, όχι σαν παράμετρος να ξεκινήσει ένα άλλο πρόγραμμα.

 

Διαβάζοντας ένα αρχείο

$stuff="c:/scripts/stuff.txt";

open STUFF, $stuff or die "Cannot open $stuff for read :$!";

while (<STUFF>) {
        print "Line $. is : $_";
}

Το αρχείο έχει ανοιχθεί για ανάγνωση. Μπορείς να προσθέσεις και να γράψεις επίσης. Δεν χρειάζεται να χρησιμοποιείς μια μεταβλητή, τότε όμως είναι πιο εύκολο να αλλάξεις και να προσθέσεις στο τμήμα or die, και πιο εύκολο να το αλλάξεις αργότερα. Το :

open STUFF, "c:/scripts/stuff.txt" or die "Cannot

είναι καλό, αλλά χρειάζεται πολλή δουλειά αν θέλεις να αλλάξεις οτιδήποτε.

Ο τελεστής εισαγωγής γραμμής (είναι το < >) διαβάζει από την αρχή του αρχείου μέχρι να βρεθεί η πρώτη νέα γραμμή. Τα δεδομένα ανάγνωσης καταχωρούνται στην $_, την οποία μπορείς να κάνεις ότι θέλεις. Στο επόμενο βήμα της επανάληψης τα δεδομένα διαβάζονται από το σημείο που η προηγούμενη ανάγνωση σταμάτησε, μέχρι την επόμενη νέα γραμμή. Και έτσι συνεχίζει έως ότου δεν υπάρχουν άλλα δεδομένα. Όταν αυτό συμβεί η κατάσταση είναι false και η επανάληψη τερματίζει. Αυτή είναι η συμπεριφορά παράλειψης, αλλά μπορούμε να την αλλάξουμε.

Στην Perl μπορείς να ανοίξεις και να τρέξεις ένα αρχείο 200Mb χωρίς να πρέπει να το φορτώσεις ολόκληρο το αρχείο στη μνήμη. Αν πραγματικά θέλεις να φορτώσεις ολόκληρο το αρχείο των 200Mb σε μια μεταβλητή, η Perl στο επιτρέπει αφού δεν έχει όρια.

Η ειδική μεταβλητή ‘$.’ είναι ο τρέχων αριθμός γραμμής, αρχίζοντας από το 1.

Όπως συνήθως, υπάρχει ένας γρηγορότερος τρόπος να κάνεις το προηγούμενο

πρόγραμμα :

$STUFF="c:/scripts/stuff.txt";

open STUFF or die "Cannot open $STUFF for read :$!";

while (<STUFF>) {
        print "Line $. is : $_";
}

Αν επιθυμείς περισσότερη συντομία, δοκίμασε αυτό :

$STUFF="c:/scripts/stuff.txt";

open STUFF or die "Cannot open $STUFF for read :$!";

print "Line $. is : $_" while (<STUFF>);
        


Γράφοντας σε ένα αρχείο

Ένα απλό γράψιμο

$out="c:/scripts/out.txt";

open OUT, "$out" or die "Cannot open $out for write :$!";

for $i (1..10) {
        print OUT "$i : The time is now : ",scalar(localtime),"\n";
}

Πρόσεξε την πρόσθεση του ‘ > ’ στο όνομα αρχείου. Το ανοίγει για γράψιμο. Τυπώνεις στο λογικό αρχείο OUT, το οποίο είναι μια είσοδος για το φυσικό αρχείο. Το όνομα του λογικού αρχείου δεν είναι απαραίτητο να είναι με κεφαλαία γράμματα, αλλά έτσι είναι καλύτερα. Όλες οι συναρτήσεις της Perl είναι σε πεζά στοιχεία και η Perl είναι ευαίσθητη. Έτσι λοιπόν αν επιλέξεις κεφαλαία γράμματα, εγγυώνται να μην συγγρουστούν με τις λέξεις της τρέχουσας ή μέλλουσας συνάρτησης.

Πρέπει να γνωρίζεις ότι το γράψιμο σε ένα αρχείο σημαίνει ότι γράφει από την αρχή. Δεν προσαρτά δεδομένα. Ωστόσο, μπορείς να προσαρτήσεις με την παρακάτω μέθοδο:

 

Προσάρτηση

$out="c:/scripts/out.txt";

&printfile;

open OUT, "$out" or die "Cannot open $out for append :$!";

print OUT 'The time is now : ',scalar(localtime),"\n";

close OUT;

&printfile;

sub printfile {
        open IN, $out or die "Cannot open $out for read :$!";
        while (<IN>) {
                print;
        }
        close IN;
}

Αυτό το script δείχνει πάλι τις υπορουτίνες και πως να προσαρτήσεις σε ένα αρχείο, δηλαδή το γράψιμο πρόσθετων δεδομένων στο τέλος. Εισάγεται επίσης η συνάρτηση close, η οποία κλείνει ένα λογικό αρχείο. Δεν είναι ανάγκη να κλείσεις ένα αρχείο . Άφησέ το απλά ανοιχτό μέχρι να τελειώσει το script ή η επόμενη εντολή ανοίγματος στο ίδιο όνομα να το κλείσει για σένα.

@ARGV : Ορίσματα Εντολής Γραμμής

Η Perl έχει έναν ειδικό πίνακα που ονομάζεται @ARGV. Είναι η λίστα ονομάτων που περνιούνται με το όνομα του script στη γραμμή εντολής. Τρέξε το επόμενο script της perl, όπως :

perl myscript.pl hello world how are you


foreach (@ARGV) {
        print "$_\n";
}

Άλλος ένας χρήσιμος τρόπος για να βάλεις παραμέτρους σε ένα πρόγραμμα, χωρίς την εισαγωγή του χρήστη αυτή τη φορά. Τρέξε το παρακάτω έγγραφο της perl, όπως :

perl myscript.pl stuff.txt out.txt

while (<>) {
        print;
}

Είναι πράγματι πολύ σύντομο. Αν δεν ορίσεις κάτι μέσα στα ‘< >’, οτιδήποτε είναι μέσα στον @ARGV χρησιμοποιείται στη θέση του. Μόλις τελειώσει με το πρώτο αρχείο θα συνεχίσει με το δεύτερο, κ.λ.π. Πρέπει να μετακινήσεις τα στοιχεία που δεν είναι του αρχείου από τον @ARGV πριν να τον χρησιμοποιήσεις. Μπορεί να γίνει ακόμη πιο σύντομο :

perl myscript.pl stuff.txt out.txt

print while <>;

Διάβασέ το από τα δεξιά προς τα αριστερά. Μπορεί να συντομεύσει ακόμα περισσότερο.

perl myscript.pl stuff.txt out.txt

print <>;

Πολλά πράγματα στην Perl, συμπεριλαμβάνονται και τα λογικά αρχεία, μπορεί να εκτιμηθούν σε λίστα ή σε βαθμωτή μεταβλητή. Το αποτέλεσμα που επιστρέφεται εξαρτάται από τα συμφραζόμενα. Αν ένα αρχείο εκτιμάται σε βαθμωτό, επιστρέφει την πρώτη γραμμή από οποιοδήποτε αρχείο διαβάζει. Αν εκτιμηθεί σε λίστα, επιστρέφει μια λίστα, τα στοιχεία της οποίας είναι οι γραμμές των αρχείων που διαβάζονται. Η συνάρτηση print είναι ένας τελεστής λίστας και επομένως εκτιμάει οτιδήποτε δίνεται σε μορφή λίστας. Καθώς το αρχείο εκτιμάται σαν λίστα, δίνεται μια λίστα. Τα συντομότερα scripts δεν είναι συνήθως τα ευκολότερα για κατανόηση και ούτε καν πάντα τα γρηγορότερα.

 

Τροποποιώντας ένα αρχείο με $^Ι

 

Μερικές από τις πιο συνηθισμένες διαδικασίες στην Perl είναι το άνοιγμα ενός αρχείου, να γίνουν κάποιες αλλαγές και να το γράψεις πίσω στο αυθεντικό όνομα αρχείου.

Τα βήματα είναι :

  1. Κάνε ένα backup αντίγραφο του αρχείου.
  2. Άνοιξε το αρχείο για ανάγνωση.
  3. Άνοιξε ένα καινούριο προσωρινό αρχείο για γράψιμο.
  4. Πήγαινε στο αρχείο ανάγνωσης, και γράψε αυτό και όλες τις αλλαγές στο προσωρινό αρχείο.
  5. Όταν τελειώσει, κλείσε και τα δύο αρχεία.
  6. Διέγραψε το αυθεντικό αρχείο.
  7. Μετονόμασε το προσωρινό αρχείο στο αυθεντικό όνομα αρχείου.

Σιγουρέψου ότι έχεις δεδομένα στο c:\scripts\out.txt και μετά τρέξε το παρακάτω:

@ARGV="c:/scripts/out.txt";

$^I=".bk";              # let the magic begin

while (<>) {
        tr/A-Z/a-z/;    # another new function sneaked in
        print;          # this goes to the temp filehandle, ARGVOUT, 
			# not STDOUT as usual, so don't mess with it !
}

Πρώτον, φορτώνουμε το @ARGV με το όνομα του αρχείου. Δεν έχει σημασία πως φορτώνεται το @ARGV. Θα μπορούσαμε να κάνουμε ‘shift’ τον κώδικα από τη γραμμή εντολής.

Η ‘$^I’ είναι μια ειδική μεταβλητή. Το όνομα της είναι : Inplace edit variable. Όταν έχει τιμή τα αποτελέσματά της είναι :

  1. Το όνομα του αρχείου που είναι in-placed edited παίρνεται από το πρώτο στοιχείο του @ARGV. Σ’ αυτή την περίπτωση είναι c:/scripts/out.txt. Το αρχείο μετονομάστηκε με το ήδη υπάρχον όνομά του συν την τιμή της $^I, π.χ out.txt.bk.
  2. Το αρχείο διαβάζει όπως συνήθως από τον diamond operator < >, βάζοντας κάθε φορά μια γραμμή στην $_.
  3. Ένα καινούριο αρχείο έχει ανοιχθεί που ονομάζεται ARGVOUT. Το γνήσιο out.txt έχει μετονομαστεί.
  4. Η print τυπώνει αυτόματα το ARGVOUT, και όχι το STDOUT, όπως θα έκανε συνήθως.

Στο τέλος της λειτουργίας έχεις συντάξει το αρχείο και έχεις κάνει backup. Αν δεν θέλεις backup δήλωσε στην $^I το κενό string, αλλά μην παραπονεθείς αν χάσεις δεδομένα.

Ρίξε μια ματιά στο out.txt και πρόσεξε πως όλα τα κεφαλαία γράμματα έχουν μετατραπεί σε πεζά. Είναι ο τελεστής ‘tr’, που είναι πιο ικανός από το regex για να αλλάξεις μοναδικούς χαρακτήρες. Πρέπει επίσης να έχεις ένα αρχείο out.txt.bk. Τέλος, πρόσεξε πως δημιουργήθηκε ο @ARGV. Δεν είναι ανάγκη να τον δημιουργήσεις από τα ορίσματα γραμμής εντολής, μπορεί να δουλευτεί όπως ένας κανονικός πίνακας.

 

‘$ / ’ Αλλάζει ότι διαβάζει μέσα σε ‘$_’

Τι θα γινόταν αν το αρχείο εισαγωγής δεν έμοιαζε με :

Beer

Wine

Pizza

Catfood

που χωρίζονται με μια νέα γραμμή κάθε φορά, αλλά με :

shorts
t-shirt
blouse

pizza
beer
wine
catfood

Viz
Private Eye
The Independent
Byte

toothpaste
soap
towel

που χωρίζονται με δυο νέες γραμμές, όχι μία. Σώσε το παραπάνω σαν shop.txt και ακολούθησε τα παραδείγματα. Αν θέλεις κάθε σύνολο αντικειμένων σαν στοιχεία σε ένα πίνακα θα πρέπει να κάνεις το παρακάτω :

$SHOP="shop.txt";
$x=0;

open SHOP or die "Can't open $SHOP for read: $!\n";

while (<SHOP>) {
        if (/^\n/) {            # does line begin with newline ?
                $x++;           # if so, increment $x.  Rest of if statement not executed.
        } else {
                $list[$x].=$_;  # glue $_ on the end of whatever is in $list[$x], using a .
        }               
}

foreach (@list) {
        print "Items are:\n$_\n\n";
}

Το οποίο δουλεύει, αλλά υπάρχει ένας πολύ πιο εύκολος τρόπος για να γίνει:

$SHOP="shop.txt";
$/="\n\n";

open SHOP or die "Can't open $SHOP for read: $!\n";

while (<SHOP>) {
        push (@list, $_);
}

foreach (@list) {
        print "Items are:\n$_\n\n";
}

Η μεταβλητή $/ είναι μια ειδική μεταβλητή. Είναι η ‘Default Input Record Separator’. Θυμάσαι τη λειτουργία των angle brackets που διαβάζουν ένα αρχείο από την αρχή μέχρι την επόμενη νέα γραμμή; Αυτό που πραγματικά κάνουν τα angle brackets είναι να διαβάζουν μέχρι οποιοδήποτε $/ να οριστεί. Ορίζεται σε μια νέα γραμμή εξ’ ορισμού.

Έτσι αν το ορίσουμε σε δυο νέες γραμμές, όπως παραπάνω, διαβάζει μέχρι να βρεί δυο συνεχόμενες νέες γραμμές, και μετά βάζει τα δεδομένα στην $_. Έτσι το πρόγραμμα γίνεται περισσότερο σύντομο και γρήγορο. Μπορείς να ορίσεις την $/ για οτιδήποτε και όχι μόνο για νέα γραμμή. Αν για παράδειγμα θέλεις να κομματιάσεις αυτή τη λίστα :

Tea:Beer:Wine:Pizza:Catfood:Coffee:Chicken:Salmon:Icecream

 

Μπορείς απλά να αφήσεις το $/ σαν νέα γραμμή και να το καταχωρήσεις στη μνήμη κατευθείαν.

Πάμε πίσω στο τελευταίο παράδειγμα. Είναι χρήσιμο να ξέρεις πως να διαβάζεις μόνο μια γραμμή (μέχρι το $/) κάθε φορά :

$SHOP="shop.txt";
$/="\n\n";

open SHOP or die "Can't open $SHOP for read: $!\n";

$clothes=<SHOP>;        # everything up until the first occurrence of $/ into $clothes

$food=<SHOP>;   # everything from first occurrence of $/ to the second into $food

print "We need...\n",$clothes,"...and\n",$food;

 

Υπάρχει ένας γρηγορότερος τρόπος για να πετύχουμε το σκοπό του αυθεντικού προγράμματος :

$SHOP="shop.txt";
$/="\n\n";

open SHOP or die "Can't open $SHOP for read: $!\n";

@list=<SHOP>;   # dumps *all* of $SHOP into @list, not just one line.

foreach (@list) {
        print "Items are:\n$_\n\n";
}

Και δεν χρειάζεται να τα πάρεις όλα :

@list[0..2]=<SHOP> 

Δεν αναφερθήκαμε για λίγο σε μορφή λίστας. Πότε ο τελεστής εισαγωγής γραμμής < > επιστρέφει μια μοναδική τιμή ή μια λίστα εξαρτάται από τη μορφή που χρησιμοποιείς. Όταν είναι @xxxxx τότε είναι μια λίστα. Αν είναι $xxxxx τότε είναι μια βαθμωτή μεταβλητή. Μπορείς να το μετατρέψεις σε μορφή λίστας χρησιμοποιώντας παρενθέσεις. Οι δυο γραμμές παρακάτω παρέχονται έτσι ώστε να τις κολλήσεις στο παραπάνω πρόγραμμα. Δείχνουν πως οι παρενθέσεις δίνουν μορφή λίστας. Θυμήσου να αντικαταστήσεις την foreach με κάτι που να τυπώνει τις μεταβλητές.

($first, $second) = <SHOP>;
$first,  $second  = <SHOP>;


HERE Docs

 

Το πρόβλημα :

print "This is a long line of text which might be too long to fit on just one line\n";
print "and I was right, it was too long to fit on one line.  In fact, it looks like it\n";
print "might very well take up to FOUR, yes FOUR lines to print.  That's four print\n";
print "statements, which takes up even more room.  But wait! I'm wrong!  It will take\n";
print "FIVE lines to print this statement!  Or is that six lines? I'm not sure....\n";

 

Η λύση :

$var='variable interpolated';

print <<PRT;
This is a long line of text which might be too long to fit on just one line
and I was right, it was too long to fit on one line.  In fact, it looks like
it might very well take up to FOUR, yes FOUR lines to print.  

That's four print statements, which takes up even more room.  But wait! I'm 
wrong!  It will take FIVE lines to print this statement!  Or maybe six lines? 
I'm not sure....but anyway, just to prove this can be $var.
PRT

Αυτό ονομάζεται ‘here’ έγγραφο και δεν χρειάζεται να χρησιμοποιείς PRT. Μπορείς να χρησιμοποιείς οτιδήποτε θέλεις με κάποια δικαιολογία. Δεν χρειάζεται να χρησιμοποιείς here docs για να τυπώσεις σε αρχεία, απλά οπουδήποτε θα τοποθετούσες περισσότερες από μια προτάσεις print.

Διάβασμα Καταλόγων Αρχείων

Globbing

Γι’ αυτή την άσκηση, προτείνω να δημιουργήσετε άλλο κατάλογο αρχείων, όπου έχετε τουλάχιστον δύο αρχεία κειμένου και δύο ή περισσότερα δυαδικά αρχεία. Αντέγραψε ένα ζεύγος από .d11 αρχεία από το δικό σου WINDIR directory αν χρειάζεται, αυτό θα είναι για τα δυαδικά, και σώσε ένα ζεύγος από τυχαία αρχεία κειμένου. Το μέγεθος δεν παίζει ρόλο σ’ αυτή την περίπτωση. Μετά τρέξε το παρακάτω, δίνοντας το directory σαν όρισμα γραμμής εντολής :

$dir=shift;	# shifts @ARGV, the command line arguments after the script name

chdir $dir or die "Can't chdir to $dir:$!\n" if $dir;

while (<*>) {
	print "Found a file: $_\n" if -T;
}

Η συνάρτηση chdir αλλάζει το τρέχον directory της Perl. Πρέπει να ελέγξεις αν δουλεύει ή όχι. Στην περίπτωση αυτή ελέγχουμε και αλλάζουμε το directory μόνο αν η $dir είναι αληθής.

Το ‘<*>’ διαβάζει όλα τα αρχεία από ένα δοσμένο directory και τυπώνει αν περάσει τον έλεγχο αρχείου ‘-Τ’, που επιστρέφει true αν το αρχείο δεν είναι δυαδικό, π.χ αρχείο κειμένου. Μπορείς να γίνεις πιο συγκεκριμένος, όπως :

$dir =shift;
$type='txt';

chdir $dir or die "Can't chdir to $dir:$!\n" if $dir;

while (<*.$type>) {
	print "Found a file: $_\n";
}

Αλλά υπάρχει ένας καλύτερος τρόπος για να διαβάσεις από τα directories. Η παραπάνω μέθοδος είναι αργή και μη ελαστική.

 

Readdir : Πως να διαβάσεις από καταλόγους αρχείων

Άλλη έκδοση του προηγούμενου παραδείγματος :

$dir= shift || '.';

opendir DIR, $dir or die "Can't open directory $dir: $!\n";

while ($file= readdir DIR) {
	print "Found a file: $file\n";
}

Η πρώτη διαφορά είναι η πρώτη γραμμή, που λέει αν η shift είναι false, τότε $dir=., το οποίο είναι φυσικά το τρέχον directory. Μετά, το directory ανοίγει και έχουμε την ευκαιρία να βρούμε το λάθος. Έχει οριστεί ένα λογικό αρχείο. Η συνάρτηση readdir διαβάζει κάθε αρχείο μέσα στην $file. Δεν υπάρχει κατασκευή while (<DIR>) {.

Μπορούμε επίσης να εφαρμόσουμε τον έλεγχο αρχείου κειμένου. Τρέξε το παρακάτω, μια φορά χωρίς να εισάγεις ένα directory και τη δεύτερη φορά εισάγοντας ένα directory path άλλο από αυτό που το έγγραφο βρίσκεται :

$dir= shift || '.';

opendir DIR, $dir or die "Can't open directory $dir: $!\n";

while ($file= readdir DIR) {
	print "Found a file: $file\n" if -T $file ;
}

Πρώτον, επειδή το όνομα αρχείου δεν είναι τώρα στο $_ πρέπει να εφαρμόσουμε τον έλεγχο –Τ σε αυτό με -Τ $file. Γιατί δεν δουλεύει την δεύτερη φορά; Κοίταξε τον κώδικα προσεκτικά. Ελέγχεις το $file. Αν η Perl δεν παίρνει ένα πλήρες ορισμένο pathname, θεωρεί ότι είσαι ακόμη στο directory από το οποίο το script τρέχει, ή σε αυτό της τελευταίας επιτυχούς chdir. Όχι απαραίτητα σε αυτό από το οποίο κάνεις readdir. Για να το διορθώσεις :


print "Found a file: $dir/$file\n" if -T "$dir/$file" ;

όπου τώρα έχουμε συγκεκριμενοποιήσει το μονοπάτι, και στο printout και στον ίδιο τον έλεγχο του αρχείου. Τα “ ” χρησιμοποιούνται γιατί διαφορετικά η Perl προσπαθεί να χωρίσει το $file από το $dir. Δοκίμασε να τρέξεις το παρακάτω σε ένα directotry με μόνο λίγα αρχεία σε αυτό :

$dir= shift || '.';

opendir DIR, $dir or die "Can't open directory $dir: $!\n";

while ($file= readdir DIR) {
	print "Found a file: '$file'\n";
}

Έχουν βρεθεί δυο αρχεία που έχουν ενδιαφέροντα ονόματα, ονομάζονται ‘.’ και ‘..’ . Αυτά τα δυο αρχεία είναι το τρέχον και το αρχικό directory. Τρέξε την εντολή dir στο DOS:

while ($file= readdir DIR) {
	next if $file=~/^\./;
	print "Found a file: '$file'\n";
}

Μπορείς να χρησιμοποιήσεις βαθμωτή μορφή για να βάλεις τα πάντα σε μια λίστα περιγραφής :

$dir= shift || '.';

opendir DIR, $dir or die "Can't open directory $dir: $!\n";

@files=readdir(DIR);

print "@files";

Καθώς αυτό περιλαμβάνει τα αρχεία ‘.’, είναι καλύτερα να σιγουρέψουμε ότι δεν περιλαμβάνονται :

@files=grep !/^\./, readdir(DIR);

Δεν έχουμε συναντήσει ακόμη το ‘-Τ’. Αυτό ψάχνει μια λίστα και αν επιστρέψει true, αφήνει τη μεταβλητή να περάσει. Σ’ αυτή την περίπτωση, αν δεν αρχίζει με ‘.’ τότε είναι true και έτσι πηγαίνει μέσα στο @files. Υπάρχουν άλλες εντολές που σχετίζονται με το διάβασμα των directories. Μια άλλη εντολή είναι η closedir, η οποία κλείνει ένα directory.

 

Συνειρμικοί Πίνακες (Hash Tables)

Τα Βασικά

Πρώτον, μια γρήγορη ανακεφαλαίωση στους πίνακες. Πίνακας είναι μια ταξινομημένη λίστα από βαθμωτές μεταβλητές, που τις σαρώνεις με τον αριθμό του δείκτη τους αρχίχοντας από το 0. Τα στοιχεία στους πίνακες πάντα μένουν στην ίδια σειρά. Τα hashes είναι μια λίστα από βαθμωτές μεταβλητές, αλλά αντι να αναγνωρίζονται από τον αριθμό του δείκτη τους, τις σαρώνεις χρησιμοποιώντας ένα κλειδί. Οι πίνακες παρακάτω δείχνουν την διαφορά :

@myarray

 

%myhash

Index No.

Value

Key

Value

0

The Netherlands

NL

The Netherlands

1

Belgium

BE

Belgium

2

Germany

DE

Germany

3

Monaco

MC

Monaco

4

Spain

ES

Spain

Έτσι αν θέλουμε το ‘Belgium’ από το @myarray και επίσης από το %myhash, θα είναι :

print "$myarray[1]";
print "$myhash{'BE'}";

Πρόσεξε ότι το πρόθεμα $ χρησιμοποιείται, γιατί είναι μια βαθμωτή μεταβλητή. Παρά το γεγονός ότι είναι μέρος της λίστας, είναι επίσης μια βαθμωτή μεταβλητή. Η σύνταξη του hash απλά χρησιμοποιεί { } στη θέση των square brackets.

Πότε χρησιμοποιούμε τα hashes; Όταν θέλουμε να βρούμε κάτι με μια λέξη κλειδί. Υποθέτουμε ότι θέλουμε να δημιουργήσουμε ένα πρόγραμμα το οποίο επιστρέφει το όνομα της χώρας, όταν δίνεται ένας κωδικός της χώρας. Θα εισάγουμε ES και το πρόγραμμα θα επιστρέψει Spain. Μπορείς να το κάνεις αυτό με πίνακες. Ωστόσο δεν θα ήταν αρκετά εύχρηστο.

Μια πιθανή προσέγγιση :

  1. δημιούργησε @country και δώσε τιμές όπως ‘ES, Spain’
  2. επανέλαβε σε ολόκληρο τον πίνακα, και
  3. κάνε split κάθε στοιχείο του πίνακα, και έλεγξε το πρώτο αποτέλεσμα να δεις αν ταιριάζει η εισαγωγή
  4. αν ναι, επέστρεψε τον δείκτη
		@countries=('NL,The Netherlands','BE,Belgium',
		'DE,Germany','MC,Monaco','ES,Spain');

	  print "Enter the country code:";
	  chop ($find=<STDIN>);

	  foreach (@countries) {
        ($code,$name)=split /,/;
        if ($find=~/$code/i) {
                print "$name has the code $code\n";
        }
						 }

Είναι πολύπλοκο και αργό. Μπορούμε επίσης να αποθηκεύσουμε μια αναφορά σε άλλο πίνακα σε κάθε στοιχείο του @countries, αλλά δεν είναι πρακτικό. Οποιονδήποτε τρόπο επιλέξουμε, πάλι χρειάζεται να ψάξουμε όλον τον πίνακα. Αν ο πίνακας @countries είναι πολύ μεγάλος; Παρατήρησε πόσο ευκολότερο είναι το hash:

Hash (Αναξιόπιστα ή Ανεπιθύμητα Δεδομένα) στην πράξη

%countries=('NL','The Netherlands','BE','Belgium','DE','Germany','MC','Monaco','ES','Spain');

print "Enter the country code:";
chop ($find=<STDIN>);

$find=~tr/a-z/A-Z/;
print "$countries{$find} has the code $find\n";

Ολα όσα πρέπει να κάνουμε είναι να σιγουρέψουμε οτιδήποτε είναι σε κεφαλαία γράμματα με το ‘tr’. Πρόσεξε τον τρόπο που ορίζεται το %countries – ακριβώς το ίδιο όπως σε πίνακα, μόνο που οι τιμές μπαίνουν μέσα σε hash σε ζεύγη κλειδί/τιμή.

  

Πότε πρέπει να χρησιμοποιείς hashes

 Λοιπόν γιατί χρησιμοποιούμε πίνακες; Ένας καλός λόγος είναι επειδή όταν δημιουργείται ένας πίνακας, οι μεταβλητές του μένουν στην ίδια σειρά που τις δημιούργησες. Με το hash, η perl αναταξινομεί τα στοιχεία για γρήγορη πρόσβαση. Πρόσθεσε ‘print %countries;’ στο τέλος αυτού του προγράμματος παραπάνω και τρέξτο. Δεν υπάρχει καμιά ακολουθία. Αν γράφεις κώδικα που αποθηκεύει μια λίστα από μεταβλητές και τον θέλεις πίσω στη σειρά που τον βρήκες, τότε μην χρησιμοποιείς hash.

Τέλος, πρέπει να ξέρεις ότι κάθε κλειδί σε ένα hash πρέπει να είναι μοναδικό. Έχεις πρόσβαση στο hash μέσω των κλειδιών και γι’ αυτό δεν μπορείς να έχεις π.χ δύο κλειδιά με το όνομα ‘NL’ ή κάτι άλλο. Αν όμως ορίσεις ένα κλειδί δύο φορές, τότε η δεύτερη τιμή υπερτερεί της πρώτης. Αυτό είναι ένα χρήσιμο χαρακτηριστικό. Οι τιμές σε ένα hash μπορεί να είναι διπλές, ποτέ όμως τα κλειδιά.

Αν θέλεις να δηλώσεις σε ένα hash, φυσικά δεν υπάρχουν οι : push, pop, splice κ.λ.π. Στη θέση τους:

Hash Hacking Functions

Δήλωση

$countries{PT}='Portugal';

Διαγραφή

delete $countries{NL};

Προσπέλαση του hash.

Υποθέτουμε ότι κρατάς το ίδιο ‘%countries’ hash, όπως παραπάνω. Εδώ είναι κάποιοι χρήσιμοι τρόποι για να το διατρέξεις :

Όλα τα κλειδιά

print keys %countries;

Όλες τις τιμές

print values %countries;

 

 

Ένα κομμάτι του hash

print @countries{'NL','BE'};

Πόσα στοιχεία;

print scalar(keys %countries);

Ισχύει το κλειδί;

print "It's there !\n" if exists $countries{'NL'};

Η τελευταία είναι αρκετά χρήσιμη αν και δεν γίνεται μια προσπέλαση.

Περισσότερη Προσπέλαση σε Hash : Επανάληψη, Κλειδιά και Τιμές

Παρατήρησες ίσως ότι τα keys (κλειδιά) και values (τιμές) επιστρέφουν μια λίστα. Μπορούμε να κάνουμε περισσότερες επαναλήψεις σάρωσης σε μια λίστα χρησιμοποιώντας την foreach.

foreach (keys %countries) {
        print "The key $_ contains $countries{$_}\n";
}

Ένας άλλος τρόπος για το παραπάνω είναι :

while (($code,$name)=each %countries) {
        print "The key $code contains $name\n";
}

Η συνάρτηση each επιστρέφει ένα ζεύγος του hash κλειδί/τιμή και είναι γρηγορότερη. Στο παράδειγμα αυτό τα δηλώσαμε σε μια λίστα (πρόσεξες τις παρενθέσεις;). Τελικά δεν υπάρχουν άλλα ζεύγη, και επιστρέφει false στο while loop και σταματάει. Τα δύο παραπάνω μπορούν να συνενωθούν σε μια μοναδική γραμμή :

print "The key $code contains $name\n" while ($code,$name)=each %countries;

print "The key $_ contains $countries{$_}\n" foreach keys %countries;

Ταξινόμηση

Μια απλή ταξινόμηση

foreach (sort keys %countries) {
        print "The key $_ contains $countries{$_}\n";
}

Πρόσεξε τη διαφορά. Η sort βρίσκεται εδώ. Αν θέλεις την οπισθοδρομημένη ταξινομημένη λίστα τότε :

foreach (reverse sort keys %countries) {
        print "The key $_ contains $countries{$_}\n";
}

Αυτό δουλεύει επειδή :

4 τα κλειδιά επιστρέφουν λίστα

4 η ταξινόμηση αναμένει μια λίστα - παίρνει ένα από τα κλειδιά και το ταξινομεί

4 το αντίστροφο επίσης αναμένει μια λίστα, και έτσι παίρνει ένα και το επιστρέφει

4 μετά όλη η λίστα γίνεται πάλι foreach

 

Ακολουθεί ένα γρήγορο παράδειγμα για να γίνει ξεκάθαρο το νόημα του reverse:

print "Enter string to be reversed: ";
$input=<STDIN>;

@letters=split //,$input;	# splits on the 'nothings' in between each character of $input

print join ":", @letters;	# joins all elements of @letters with \n, prints it
print reverse   @letters;	# prints all of @letters, but sdrawkcab )-:

 

Οι χειριστές λίστας της Perl μπορούν να τροφοδοτούν άμεσα ο ένας τον άλλο, σώζοντας πολλές γραμμές κώδικα, αλλά επίσης μειώνοντας την ικανότητα ανάγνωσης για αυτούς που δεν είναι γνώστες της Perl.

print "Enter string to be reversed: ";
print join ":",reverse split //,$_=<STDIN>;

 

Αριθμητική Ταξινόμηση – Πώς πραγματικά δουλεύει η ταξινόμηση

Έχεις ένα hash με αριθμούς διεθνούς πρόσβασης όπως το παρακάτω :

%countries=('976','Mongolia','52','Mexico','212','Morocco','64','New Zealand','33','France');

foreach (sort keys %countries) {
        print "The key $_ contains $countries{$_}\n";
}

Θέλεις να το ταξινομήσεις αριθμητικά. Τώρα πρέπει να καταλάβεις πώς η συνάρτηση sort της Perl δουλεύει. Η συνάρτηση sort συγκρίνει δύο μεταβλητές, $a και $b. Πρέπει να ονομάζονται $a και $b, διαφορετικά δεν θα δουλέψει. Οι $a και $b συγκρίνονται και το αποτέλεσμα είναι :

Η συνάρτηση sort δουλεύει, όσο παίρνει μια από τις παραπάνω τιμές. Αυτό σημαίνει ότι μπορούμε να γράψουμε τις δικές μας ρουτίνες ταξινόμησης και να τις τροφοδοτήσουμε για ταξινόμηση. Για παράδειγμα, γνωρίζουμε την εξ’ ορισμού ταξινόμηση, που είναι η αλφαβητική.

Αν όμως γράψουμε αυτό:

%countries=('976','Mongolia','52','Mexico','212','Morocco','64','New Zealand','33','France');

foreach (sort supersort keys %countries) {
        print "$_ $countries{$_}\n";
}

sub supersort {
        if ($a > $b) {
                return 1;
        } elsif ($a < $b) { 
		return -1;
	} else { 
		return 0; 
	}
}

Θα δουλέψει σωστά. Φυσικά υπάρχει και ευκολότερος τρόπος. Ο τελεστής ‘spaceship’

σ . Κάνει ακριβώς ότι η υπορουτίνα supersort κάνει, και επιστρέφει 1, -1 ή 0 εξαρτώντας από την σύγκριση των δύο δοθέντων τιμών. Έτσι γράφουμε το παραπάνω με πολύ πιο εύκολο τρόπο :

%countries=('976','Mongolia','52','Mexico','212','Morocco','64','New Zealand','33','France');

foreach (sort { $a <=> $b } keys %countries) {
        print "$_ $countries{$_}\n";
}

Υπάρχει ένας ισοδύναμος τελεστής του σ , που ονομάζεται cmp. Κάνει ακριβώς το ίδιο πράγμα, αλλά φυσικά συγκρίνει τις τιμές σαν αλφαριθμητικά και όχι σαν νούμερα. Θυμήσου ότι αν συγκρίνεις αριθμούς ο τελεστής σύγκρισης δεν πρέπει να περιλαμβάνει γράμματα. Αν όμως συγκρίνεις αλφαριθμητικά, τότε ο τελεστής πρέπει να περιλαμβάνει μόνο γράμματα.

Πολλαπλές Ταξινομημένες Λίστες

Μπορείς να ταξινομήσεις αρκετές λίστες την ίδια στιγμή :

%countries=('976','Mongolia','52','Mexico','212','Morocco','64','New Zealand','33','France');
@nations=qw(China Hungary Japan Canada Fiji);

@sorted= sort values %countries, @nations;

foreach (@nations, values %countries) {
        print "$_\n";
}

print "#----\n";

foreach (@sorted) {
        print "$_\n";
}

Αυτό ταξινομεί τον @nations και τις τιμές του %countries σε ένα καινούριο πίνακα.

Το παράδειγμα δείχνει επίσης ότι μπορείς να κάνεις foreach σε περισσότερες από μια τιμές της λίστας.

 

Grep και Map

Grep

Αν θέλεις να ψάξεις μια λίστα και να φτιάξεις μια άλλη λίστα από πράγματα που έχεις βρεί, τότε η grep είναι μια λύση. Ακολουθεί ένα παράδειγμα που επίσης δείχνει την join :

@stuff=qw(flying gliding skiing dancing parties racing);	# quote-worded list

@new = grep /ing/, @stuff;	# Creates @new, which contains elements of @stuff 
				# matching with 'ing' in them.

print join ":",@stuff,"\n";	# first makes one string out of the elements of @stuff, joined
				# with ':' , then prints it, then prints \n

print join ":",@new,"\n";

Το ‘qw’ σημαίνει ‘quote words’, και έτσι οι λέξεις σύνορα χρησιμοποιούνται στη θέση των οριοθετών. Η συνάρτηση grep πρέπει να τροφοδοτεί μια λίστα στη δεξιά πλευρά. Στην αριστερή μεριά μπορείς να δηλώσεις το αποτέλεσμα σε μια λίστα ή σε μια βαθμωτή μεταβλητή. Η δήλωση σε λίστα σου δίνει κάθε πραγματικό στοιχείο, και σε μια βαθμωτή μεταβλητή τον αριθμό των συνδυασμών που βρέθηκαν :

@stuff=qw(flying gliding skiing dancing parties racing);

$new = grep /ing/, @stuff;

print join ":",@stuff,"\n";

print "Found $new elements of \@stuff which matched\n";

 

Αν θέλεις να τροποποιήσεις τα στοιχεία μέσω της grep, στην πραγματικότητα τροποποιείς την αυθεντική λίστα. Χρειάζεται προσοχή.

@stuff=qw(flying gliding skiing dancing parties racing);

@new = grep s/ing//, @stuff;

print join ":",@stuff,"\n";
print join ":",@new,"\n";

Για να αποφασίσεις τι πραγματικά αντιστοιχεί μπορείς να χρησιμοποιήσεις είτε μια έκφραση είτε ένα block. Μέχρι τώρα χρησιμοποιούσαμε εκφράσεις, αλλά όταν τα πράγματα γίνονται περισσότερο πολύπλοκα, χρησιμοποίησε ένα block :

@stuff=qw(flying gliding skiing dancing parties racing);

@new = grep { s/ing// if /^[gsp]/ } @stuff;

print join ":",@stuff,"\n";
print join ":",@new,"\n";

Αν δοκιμάσεις να μετακινήσεις τις παρανθέσεις θα καταλήξεις σε λάθος. Το κόμμα πρίν τη λίστα έχει φύγει. Τώρα είναι φανερό που τελειώνει η έκφραση, καθώς βρίσκεται μέσα σε μπλόκ που οριοθετείται από { }. Το regex λέει αν το στοιχείο αρχίζει από g, s ή p και μετά μετακινεί το ing. Το αποτέλεσμα απλά δηλώνεται στον @new, αν η έκφραση είναι τελείως αληθής - το ‘parties’ αρχίζει από p και έτσι αυτό δουλεύει. Αλλά το s/ing// αποτυγχάνει, και έτσι το τελικό αποτέλεσμα είναι false και η τιμή δεν δηλώνεται στον @new.

Map

Το map λειτουργεί με τον ίδιο τρόπο του grep. Έτσι και τα δύο κάνουν επανάληψη σε μια λίστα και επιστρέφουν μια λίστα. Ωστόσο υπάρχουν δυο σημαντικές διαφορές :

4 η grep επιστρέφει την τιμή από οτιδήποτε εκτιμάει σαν true

4 η map επιστρέφει το αποτέλεσμα από οτιδήποτε εκτιμάει.

Ένα παράδειγμα :

@stuff=qw(flying gliding skiing dancing parties racing);

print "There are ",scalar(@stuff)," elements in \@stuff\n";
print join ":",@stuff,"\n";

@mapped  = map  /ing/, @stuff;
@grepped = grep /ing/, @stuff;

print "There are ",scalar(@stuff)," elements in \@stuff\n";
print join ":",@stuff,"\n";

print "There are ",scalar(@mapped)," elements in \@mapped\n";
print join ":",@mapped,"\n";

print "There are ",scalar(@grepped)," elements in \@grepped\n";
print join ":",@grepped,"\n";

Μπορείς να δεις ότι ο @mapped είναι απλά μια λίστα των 1. Υπάρχουν πέντε 1, ενώ υπάρχουν έξι στοιχεία στον αυθεντικό πίνακα, @stuff. Αυτό συμβαίνει επειδή ο @mapped περιέχει τα αληθή αποτελέσματα του map – σε κάθε περίπτωση η έκφραση /ing/ είναι επιτυχής, εκτός από το ‘parties’. Σ’ εκείνη την περίπτωση η έκφραση είναι false και έτσι το αποτέλεσμα αποβάλλεται. Αντιπαράθεσε την ενέργεια αυτή με την συνάρτηση grep, η οποία επιστρέφει την πραγματική τιμή, αλλά όχι μόνο αν είναι true. Δοκίμασε αυτό :

@letters=(a,b,c,d,e);

@ords=map ord, @letters;
print join ":",@ords,"\n";

@chrs=map chr, @ords;   
print join ":",@chrs,"\n";

Χρησιμοποιεί την συνάρτηση ord για να αλλάξει κάθε γράμμα στην ισοδύναμη ASCII μορφή, μετά η συνάρτηση chr μετατρέπει τους αριθμούς ASCII σε χαρακτήρες. Αν αλλάξεις το map σε grep στο παραπάνω παράδειγμα δεν θα παρατηρήσεις καμιά διαφορά. Αυτό που συμβαίνει είναι ότι η grep δοκιμάζει την έκφραση σε κάθε στοιχείο και αν αυτό πετύχει (δηλαδή είναι true), τότε επιστρέφει το στοιχείο και όχι το αποτέλεσμα. Η έκφραση πετυχαίνει για κάθε στοιχείο και γι’ αυτό κάθε στοιχείο επιστρέφεται στη σειρά.

Άλλο παράδειγμα :

@stuff=qw(flying gliding skiing dancing parties racing);

print join ":",@stuff,"\n";

@mapped  = map  { s/(^[gsp])/$1 x 2/e } @stuff;
@grepped = grep { s/(^[gsp])/$1 x 2/e } @stuff;

print join ":",@stuff,"\n";
print join ":",@mapped,"\n";
print join ":",@grepped,"\n";

Ανακεφαλαίωση στο regex Αυτό που κάνει είναι να συνδυάζει κάθε στοιχείο που αρχίζει από g, s ή p και να το αντικαταστεί με το ίδιο στοιχείο δύο φορές. Το σύμβολο ^ δίνει το συνδυασμό στην αρχή του string, τα square brackets δηλώνουν ένα χαρακτήρα κλάσσης και το /e κάνει την Perl να εκτιμάει το RHS σαν μια έκφραση. Το αποτέλεσμα από αυτό είναι ένα μείγμα του 1 και τίποτα για το map, και ένας πίνακας τριών στοιχείων που ονομάζεται @grepped από το grep. Άλλο παράδειγμα :

@mapped  = map  { chop } @stuff;
@grepped = grep { chop } @stuff;

Η συνάρτηση chop μετακινεί τον τελευταίο χαρακτήρα από ένα string και το επιστρέφει. Έτσι αυτό είναι που επιστρέφεται από το ^, το αποτέλεσμα της έκφρασης. Η συνάρτηση grep σου δίνει ότι απομένει από την αυθεντική τιμή.

Γράφοντας τις δικές σου Grep και Map functions

Μπορείς να γράψεις τις δικές σου συναρτήσεις:

@stuff=qw(flying gliding skiing dancing parties racing);

print join ":",@stuff,"\n";

@mapped  = map  { &isit } @stuff;
@grepped = grep { &isit } @stuff;

print join ":",@mapped,"\n";
print join ":",@grepped,"\n";

sub isit {
        ($word)=/(^.*)ing/;

        if (length $word == 3) {
                return "ok";
        } else {
                return 0;
        }
}

Η υπορουτίνα isit πρώτα παίρνει οτιδήποτε μέχρι το ‘ing’, το τοποθετεί μέσα στο $word, μετά επιστρέφει ‘ok’ αν υπάρχουν τρείς χαρακτήρες στο $word. Αν όχι επιστρέφει την false τιμή 0. Μπορείς να κάνεις αυτές τις υπορουτίνες (σκέψου τες σαν συναρτήσεις) όσο περίπλοκες επιθυμείς.

Πολλές φορές είναι πολύ χρήσιμο να κάνεις map επιστρέφοντας την πραγματική τιμή από ότι το αποτέλεσμα. Η απάντηση είναι εύκολη αλλά όχι προφανής. Οι υπορουτίνες επιστρέφουν την τιμή από την τελευταία έκφραση που εκτιμάται. Έτσι, σ’ αυτή την περίπτωση φτιάξε blocks :

@grepstuff=@mapstuff=qw(flying gliding skiing dancing parties racing);

print join " ",map  { s/(^[gsp])/$1 x 2/e } @mapstuff;
print "\n";
print join " ",grep { s/(^[gsp])/$1 x 2/e } @grepstuff;

Τώρα σιγουρέψου ότι το $_ είναι το τελευταίο που εκτιμάται :

@grepstuff=@mapstuff=qw(flying gliding skiing dancing parties racing);

print join " ",map  { s/(^[gsp])/$1 x 2/e;$_} @mapstuff;
print "\n";
print join " ",grep { s/(^[gsp])/$1 x 2/e } @grepstuff;

Εξωτερικές Εντολές

Η Perl μπορεί να εκτελέσει εξωτερικές εντολές. Για να γίνει αυτό υπάρχουν πέντε κύριοι τρόποι :

- system

- exec

- εντολή εισαγωγής, γνωστή επίσης ως ‘backticks’

- Piping δεδομένα από ένα επεξεργαστή

- εκτέλεση αναφοράς

Πρώτα θα συγκρίνουμε το system και το exec.

Exec

Το exec λειτουργεί στην Perl για Win32. Αυτό που πρέπει να κάνει είναι να σταματήσει να τρέχει το δικό σου Perl script και να αρχίσει να τρέχει οτιδήποτε του πεις. Αν δεν μπορεί να αρχίσει την εξωτερική διεργασία, πρέπει να επιστρέψει με ένα κώδικα λάθους. Αυτό δεν λειτουργεί σωστά στην Perl για Win32. Η συνάρτηση exec λειτουργεί σωστά στην Perl standard.

System

Αυτό τρέχει μια εξωτερική εντολή και μετά συνεχίζει με το script. Πάντα επιστρέφει μια τιμή, η οποία πηγαίνει στην ‘$?’. Αυτό σημαίνει ότι μπορείς να ελέγξεις για να δεις αν το πρόγραμμα δουλεύει. Στην πραγματικότητα ελέγχεις αν μπορεί να αρχίσει, τι κάνει το πρόγραμμα όταν τρέχει χωρίς τον έλεγχό σου αν χρησιμοποιείς την system.

Το παράδειγμα αυτό δείχνει την system στην πράξη. Τρέξε την εντολή ‘vol’ πρώτα από το command prompt αν δεν έχεις εξοικιωθεί με αυτήν. Μετά τρέξε την εντολή ‘vole’. Υποθέτω ότι δεν έχεις κάποιες cute furry εκτελέσιμες που ονομάζονται vole στο σύστημά σου ή τουλάχιστον στο μονοπάτι. Αν όμως έχεις κάποια εκτελέσιμη που ονομάζεται ‘vole’ πρέπει να την αλλάξεις.

system("vole");

print "\n\nResult: $?\n\n";

system("vol");

print "\n\nResult: $?\n\n";

Όπως διαπιστώνεις, ένα πετυχημένο κάλεσμα συστήματος επιστρέφει 0. Ένα αποτυχημένο όμως επιστρέφει μια τιμή την οποία πρέπει να διαιρέσεις με το 256 για να πάρεις την πραγματική τιμή που επιστρέφεται. Μπορείς επίσης να δεις το αποτέλεσμα. Και επειδή η system επιστρέφει τιμή, ο κώδικας μετά το πρώτο system κάλεσμα εκτελείται. Όχι όμως με την exec, η οποία θα τερματίσει το έγγραφό της Perl αν αυτό είναι επιτυχές.

Backticks

Τα backticks `` είναι διαφορετικά στην system και exec. Αρχίζουν την εξωτερική επεξεργασία αλλά και επιστρέφουν το αποτέλεσμα της επεξεργασίας. Τότε μπορείς να κάνεις οτιδήποτε θέλεις με το αποτέλεσμα. Αν δεν είσαι σίγουρος που βρίσκονται τα backticks στο πληκτρολόγιό σου, δοκίμασε το πάνω αριστερά πλήκτρο του πλήκτρου 1. Μην μπερδεύεις τα single quotes ‘’ με τα backticks ``.

$volume=`vol`;

print "The contents of the variable \$volume are:\n\n";

print $volume;

print "\nWe shall regexise this variable thus :\n\n";

$volume=~m#Volume in drive \w is (.*)#;

print "$1\n";

Όπως φαίνεται εδώ, η εντολή vol Win32 εκτελείται. Απλά την εκτυπώσαμε, τερματίζοντας την $ στο όνομα της μεταβλητής. Μετά χρησιμοποιείται ένα απλό regex με το # σαν οριοθέτη.

Πότε να χρησιμοποιείς εξωτερικές κλήσεις

Πρίν να δημιουργήσετε επεξεργασμένα scripts που βασίζονται στο αποτέλεσμα των εντολών δικτύου των ΝΤ, προσέξτε ότι υπάρχουν πολλά εξαιρετικά modules που κάνουν καλή δουλειά σε τέτοιες περιπτώσεις, και ότι κάθε τύπος εξωτερικής κλήσης επεξεργασίας καθυστερεί το έγγραφό σου. Επίσης υπάρχουν πολλές εντολές στις συναρτήσεις όπως η ‘readdir’, η οποία μπορεί να χρησιμοποιηθεί στη θέση της ‘`dir`’. Πρέπει να χρησιμοποιείς συναρτήσεις της Perl, όπου είναι δυνατό, από το να καλείς εξωτερικά προγράμματα, γιατί οι συναρτήσεις της Perl είναι :

n Φορητές (συνήθως, αλλά υπάρχουν και εξαιρέσεις). Αυτό σημαίνει ότι μπορείς να γράψεις ένα script στο δικό σου Mac PowerBook, να το ελέγξεις σε ένα NT box και μετά να το χρησιμοποιήσεις στο δικό σου Unix box χωρίς να τροποποιήσεις μια μοναδική γραμμή του κώδικα.

n Γρηγορότερες, καθώς κάθε εξωτερική επεξεργασία καθυστερεί σημαντικά το πρόγραμμά σου.

n Συνήθως δεν απαιτείται regexing για να βρείς το αποτέλεσμα που θέλεις.

n Μην βασίζεσαι σε αποτέλεσμα ενός ειδικού format, το οποίο μπορεί να αλλάξει στην επόμενη έκδοση του δικού σου OS ή εφαρμογή.

n Είναι περισσότερο κατανοητές στους προγραμματιστές της Perl. Για παράδειγμα, $files= ‘ls’; στο Unix box δεν σημαίνει πολλά για κάποιον που δεν γνωρίζει ότι το ls είναι εντολή Unix για να βγάζει τα αρχεία σε λίστα, όπως η dir είναι στα Windows.

Μην αρχίσεις να χρησιμοποιείς backticks παντού όταν η system θα το κάνει. Μπορεί να έχεις ένα μεγάλο αριθμό επιστρεφόμενων τιμών τις οποίες δεν χρειάζεσαι και κατά συνέπεια θα καταναλώσεις πολλή μνήμη. Χρησιμοποίησέτες μόνο όταν πραγματικά θέλεις να ελέγξεις τα επιστρεφόμενα αλφαριθμητικά.

 

Ανοίγοντας μια Διεργασία

Το πρόβλημα με τα backticks είναι ότι πρέπει να περιμένεις να ολοκληρωθεί ολόκληρη η επεξεργασία, μετά να αναλύσεις ολόκληρο τον κώδικα που επιστρέφεται. Αυτό είναι ένα μεγάλο πρόβλημα αν έχεις μεγάλους κώδικες που επιστρέφονται ή αργές επεξεργασίες. Για παράδειγμα η εντολή tree του DOS. Αν δεν είσαι εξοικιωμένος με αυτή την εντολή, τρέξε το DOS/command prompt, πήγαινε στο root directoty (c:\) και πληκτρολόγησε tree. Έλεγξε το αποτέλεσμα.

Μπορούμε να ανοίξουμε μια διεργασία, και να βάλουμε δεδομένα σε ένα αρχείο με τον ίδιο ακριβώς τρόπο που θα διάβαζες ένα αρχείο. Ο παρακάτω κώδικας είναι ακριβώς ο ίδιος με το άνοιγμα ενός αρχείου σε ένα αρχείο, με δυο εξαιρέσεις :

  1. χρησιμοποιούμε μια εξωτερική εντολή, όχι ένα όνομα αρχείου. Είναι το όνομα της επεξεργασίας, σ’ αυτή την περίπτωση, tree.
  2. Ένα pipe, δηλαδη |, προστίθεται στο όνομα της επεξεργασίας
		open TRIN, "tree c:\\ /a |" or die "Can't see the tree :$!";

			while (<TRIN>) {
				print "$. $_";
			}

Πρόσεξε το | που δείχνει ότι τα δεδομένα πρέπει να εισαχθούν από την ειδική επεξεργασία. Μπορείς επίσης να εισάγεις δεδομένα σε μια επεξεργασία χρησιμοποιώντας το | σαν πρώτο χαρακτήρα.

Ως συνήθως, το $. είναι ο αριθμός γραμμής. Αυτό που μπορούμε τώρα να κάνουμε είναι να τερματίσουμε το tree νωρίτερα.

open TRIN, "tree c:\\ /a |" or die "Can't see the tree :$!";

while (<TRIN>) {
	printf "%3s $_", $.;
	last if $. == 10;
}

Όσο το $. είναι 10 τελειώνει η επεξεργασία βγαίνοντας από την loop. Εδώ είναι εύκολο. Αν όμως ήταν ένα μεγάλο πρόγραμμα και ξεχνούσες εκείνη την γραμμή του κώδικα, η οποία τερματίζει την loop; Υπέθεσε ότι το $. με κάποιο τρόπο πήγε από 9 σε 11. Δεν θα έφτανε ποτέ το 10.

Έτσι λοιπόν:

open TRIN, "tree c:\\ /a |" or die "Can't see the tree :$!";

while (<TRIN>) {
	printf "%3s $_", $.;
	last if $. = 10;
}

Για περισσότερη ασφάλεια ίσως πρέπει να δημιουργήσεις τον δικό σου μετρητή, γιατί η $. είναι μια γενική μεταβλητή.

Ίσως έχεις παρατηρήσει την παρουσία μιας νέας λέξης κλειδί : printf. Δουλεύει όπως η print, αλλά κάνει format στο string πριν να το τυπώσει. Το format ελέγχεται από παραμέτρους όπως η %3s, η οποία σημαίνει “pad out to a total of three spaces”. Μετά το doublequoted string έρχεται οτιδήποτε θέλεις να τυπωθεί στο ορισμένο format. Ακολουθούν κάποια παραδείγματα. Μετά τον κώδικα ακολουθεί εξήγηση, αφού υπάρχουν αρκετά νέα στοιχεία σε αυτόν :

$windir=$ENV{'WINDIR'};		# yes, you can access the environment variables !

$x=0;

opendir WDIR, "$windir" or die "Can't open $windir !!! Panic : $!";

while ($file= readdir WDIR) {
	next if $file=~/^\./;		# try commenting this line to see why it is there

	$age= -M "$windir/$file";	# -M returns the age in days
	$age=~s/(\d*\.\d{3}).*/$1/;	# hmmmmm

	#### %4.4d - must take up 4 columns, and pad with 0s to make up space
	####         and minimum width is also 4
	#### %10s  - must take up 10 columns, pad with spaces
	# printf "%4.4d %10s %45s \n", $x, $age, $file;

	#### %-10s - left justify
	# printf "%4.4d %-10s %-45s \n", $x, $age, $file;

	####  %10.3 - use 10 columns, pad with 0s if less than 3 columns used
	# printf "%4.4d %10.3d %45s \n", $x, $age, $file;

	$x++;

	last if $x==15;			# we don't want to go through all the files :-)
}

Υπάρχουν αρκετές καινούριες συναρτήσεις εδώ.

Πρώτον, όλες οι μεταβλητές περιβάλλοντος μπορούν να προσπελαστούν και να καθοριστούν μέσω της Perl. Είναι στο %ENV hash. Η πιο γνωστή μεταβλητή περιβάλλοντος είναι το path και μπορείς να δεις την τιμή της.

Το regex / ^\./ απορρίπτει τις άκυρες εισαγωγές πριν να αποφασίσουμε να κάνουμε οποιαδήποτε επεξεργασία σε αυτές. Καλή πρακτική προγραμματισμού. Αυτό που συνδυάζει είναι ‘οτιδήποτε αρχίζει με .’ . Τα caret anchors ταιριάζουν στην αρχή του string, και καθώς η ‘.’ είναι ένας μεταχαρακτήρας πρέπει να τερματιστεί.

Η Perl έχει αρκετά test να εφαρμόσει σε αρχεία. Το -Μ test επιστρέφει την ηλικία σε ημέρες. Πρόσεξε ότι το κάλεσμα της readdir επιστρέφει απλά το αρχείο, όχι το ολοκληρωμένο pathname.

Προσπάθησε να σχολιάσεις $age=~s/(\d*\.\d{3}).*/$1/ και σημείωσε το μέγεθος του $age.

Αυτό που κάνει το regex είναι :

Πρέπει επίσης να γίνει αναφορά στην sprintf, που είναι ακριβώς ίδια με την printf εκτός από το να τυπώνει. Χρησιμοποίησέ την απλά για να κάνεις format σε strings, με τα οποία μπορείς να κάνεις κάτι αργότερα. Για παράδειγμα :

open TRIN, "tree c:\\ /a |" or die "Can't see the tree :$!";

while (<TRIN>) {
	$line= sprintf "%3s $_", $.;
	print $line;
	last if $. == 10;
}

Εκτέλεση κατά κυριολεξία – Quote Execute

@opts=qw(w on ad oe b);

for (@opts) {
	$result=qx(dir /$_);
	print "dir /$_ resulted in:\n$result",'-' x 79;
	sleep 1;
}

Οτιδήποτε μέσα σε qx( ) εκτελείται, ακόμη και μεταβλητή διαταξινόμησης. Αυτό το παράδειγμα επίσης δείχνει το qw που είναι ‘quote words’, και έτσι τα στοιχεία του @opts οριοθετούνται από λέξεις σύνορα, όχι το συνηθισμένο κόμμα. Μπορείς να χρησιμοποιήσεις επίσης την for στη θέση της foreach και είναι το ίδιο.

Θα έχεις παρατηρήσει ότι η system εξάγει το αποτέλεσμα της εντολής στην οθόνη όπου το qx δεν υπάρχει.

Όλα σε μια γραμμή

Ένα σύντομο παράδειγμα

Θα παρατήρησες ότι η Perl βάζει αρκετό υλικό σε μια μικρή ποσότητα κώδικα. Μπορείς να τροφοδοτήσεις τον κώδικα Perl κατευθείαν στη γραμμή εντολής. Αυτό είναι γνωστό σαν oneliner, για προφανής λόγους.

Ένα παράδειγμα :

perl -e"for (55..75) { print chr($_) }"

Το -e λέει στην Perl ότι ακολουθεί μια εντολή. Η εντολή πρέπει να περικλείεται από doublequotes “ ” και όχι singles ‘ ’ όπως στο Unix. Η ίδια η εντολή σ’ αυτή την περίπτωση απλά τυπώνει τον κώδικα ASCII για τον αριθμό 55 μέχρι 75 συμπεριλαμβανομένων και αυτών.

Προσπέλαση Αρχείου

Είναι μια απλή ρουτίνα εύρεσης. Καθώς χρησιμοποιεί regex, είναι ανώτερη του NT’s findstr :

perl -e"while (<>) {print if /^[bv]/i}" shop.txt

Το while (< >) θα ανοίξει οτιδήποτε είναι μέσα στο @ARGV. Σ’ αυτή την περίπτωση έχουμε shop.txt και έτσι ανοίγει και τυπώνουμε γραμμές που αρχίζουν με b ή v. Αυτό μπορεί να γίνει συντομότερο. Τρέξε το perl-h και θα δεις μια ολόκληρη λίστα από αλλαγές. Αυτή που θα χρησιμοποιήσουμε τώρα είναι η -n, η οποία τοποθετεί ένα while (< >) { } loop οποιοδήποτε κώδικα χρησιμοποιείς με το –e. Έτσι :

perl -ne"print if /^[bv]/i" shop.txt

Κάνει ακριβώς το ίδιο με το προηγούμενο πρόγραμμα, αλλά χρησιμοποιεί το –n για να τοποθετήσει ένα while (< >) loop. Μια άλλη έκδοση είναι :

perl -ne"printf \"$ARGV : %3s : $_\",$. if /^[bv]/i" shop.txt

Η παραπάνω δείχνει ότι τα doublequotes πρέπει να τερματίσουν.


Τροποποιώντας αρχεία με Oneliner και $^Ι

Αφού γίνει μια επανάληψη για να ξαναθυμηθούμε τη χρήση της $^Ι , άλλαξε το shop.txt σε shop2.txt

perl -i.bk -ne"printf \"%4s : $_\",$." shop2.txt

Χρειαζόμαστε ακόμη το –n.

Αν έχεις ένα συνηθισμένο quoted email μήνυμα όπως το :

>> this is what was said
>> blah blah
> blaaaaahhh

The new text

και ήθελες να αφαιρέσεις το >, τότε :

perl -i.bk -pe"s/^>+ ?//" email.txt

Το ^ συνδιάζει ότι ακολουθεί την αρχή του string, το + σημαίνει ένα ή περισσότερα (δεν χρησιμοποιούμε το * που σημαίνει 0 ή περισσότερα), μετά θα συνδιάσουμε ένα κενό με το \s, αλλά δεν είναι αναγκαίο να υπάρχει το κενό για να είναι επιτυχές το ταίριασμα.

Το καινούριο στην ορολογία των oneliners είναι η χρήση του –p, το οποίο κάνει ακριβώς το ίδιο με το –n εκτός από το να προσθέτει επίσης και μια πρόταση print. Αν αναρωτιέσαι γιατί στο προηγούμενο παράδειγμα χρησιμοποιήθηκε το –n και αυτό χρησιμοποιεί το –p τότε :

Το προηγούμενο παράδειγμα χρησιμοποιούσε εκτύπωση δεδομένων με την printf, ενώ αυτό το παράδειγμα δεν έχει μια σαφή πρόταση εκτύπωσης και έτσι παρέχουμε μια με τη χρήση του –p.

Μερικές άλλες χρήσιμες oneliners. Ένας υπολογιστής και ένας ASCII αριθμός ψάχνουν :

perl -e"print 50/200+2"
perl -e"for (50..90) { print chr($_) }"

Υπάρχουν πολλές άλλες oneliners και αποτελούν σημαντικό τμήμα κάθε sysadmin toolbox. Τα δύο παρακάτω παραδείγματα είναι ισοδύναμα αλλά το δεύτερο είναι ίσως κάπως ευκολότερο στην ανάγνωση :

perl -e"for $i (50..90) { print chr($i),\" is $i\n\" }"

perl -e"for $i (50..90) { print chr($i),qq| is $i\n| }

Οτιδήποτε ακολουθεί το qq χρησιμοποιείται σαν οριοθέτης, αντί να πρέπει να τερματίσεις το backslash. Επίσης αναφέρεται ότι δεν είναι αναγκαίο να κλείσεις τα doublequotes.

Υπορουτίνες και Παράμετροι

Στην Perl οι υπορουτίνες είναι συναρτήσεις. Μια υπορουτίνα είναι μια συνάρτηση που ορίζεται από το χρήστη. Είναι σαν να καλεί ένα έγγραφο κάποιο πρόγραμμα ή κάποιο πρόγραμμα ένα έγγραφο. Στο εγχειρίδιο αυτό θα αναφερόμαστε στις συναρτήσεις σαν υπορουτίνες, εκτός εάν τις ονομάσουμε συναρτήσεις.

Στο κεφάλαιο αυτό θα αναπτύξουμε ένα μικρό πρόγραμμα, το οποίο, μέχρι το τέλος, θα δείξει πως δουλεύει μια υπορουτίνα. Το επιλεγμένο θέμα είναι τα αεροπλάνα χωρίς τις κινητήριες μηχανές τους (gliding). Το θέμα είναι, για κάθε πιλότο τέτοιου αεροπλάνου, πόσο μακρυά μπορεί να πετάξει από το ύψος στο οποίο βρίσκεται. Αυτό θα υπολογίσει και το πρόγραμμά μας. Για να το κάνουμε ευκολότερο υποθέτουμε ότι ο καιρός είναι καλός. Ο αέρας για παράδειγμα θα ήταν μια περιπλοκή, που δεν θέλουμε.

Αυτό που χρειαζόμαστε για να υπολογίσουμε την απόσταση που μπορεί να πετάξει είναι :

Προφανώς είναι απαραίτητη η εισαγωγή δεδομένων. Είναι ευκολότερο να εισάγουμε από τη γραμμή εντολής και έτσι κοιτάμε στο @ARGV για παραμέτρους γραμμής εντολής. Έτσι :

($height,$angle)=@ARGV;		# @ARGV is the command line parameters

$distance=$height*$angle;	# an easy calculation

print "With a glide ratio of $angle:1 you can fly $distance from $height\n";

Το παραπάνω θα πρέπει να εκτελεστεί έτσι :

perl yourscript.pl 5000 24

Το παραπάνω δουλεύει. Αν όμως υπάρχει μια μικρή διαφορά; Ο πιλότος έχει κάποιο έλεγχο πάνω στο glide ratio. Έτσι ίσως πρέπει να δώσουμε κάποιες επιλογές ή τμήματα των δοθέντων παραμέτρων:

($height,$angle)=@ARGV;

$distance=$height*$angle;
print "With a glide ratio of $angle:1 you can fly $distance from $height\n";

$angle++;			# add 1 to $angle
$distance=$height*$angle;
print "With a glide ratio of $angle:1 you can fly $distance from $height\n";

$angle-=2;			# subtract 2 from $angle so it is 1 less than the original
$distance=$height*$angle;
print "With a glide ratio of $angle:1 you can fly $distance from $height\n";

Είναι ένας μη εύχρηστος κώδικας. Επαναλαμβάνουμε ακριβώς την ίδια πρόταση. Γίνεται σπατάλη χώρου και αν θέλουμε να το αλλάξουμε υπάρχουν τρείς επιλογές. Η καλύτερη λύση είναι να τοποθετηθεί σε μια υπορουτίνα :

($height,$angle)=@ARGV;

&howfar;
print "With a glide ratio of $angle:1 you can fly $distance from $height\n";

$angle++;
&howfar;
print "With a glide ratio of $angle:1 you can fly $distance from $height\n";

$angle-=2;
&howfar;
print "With a glide ratio of $angle:1 you can fly $distance from $height\n";

sub howfar {				# sub subroutinename
	$distance=$height*$angle;
}

Είναι μια βασική υπορουτίνα. Τώρα υπάρχει λιγότερη δουλειά, και λιγότερες πιθανότητες λάθους. Βελτιώσεις ωστόσο μπορούν πάντα να γίνουν. Για παράδειγμα οι πιλότοι στην Αμερική μετρούν το ύψος σε πόδια και οι glider πιλότοι ανησυχούν συνήθως για το πόσα χιλιόμετρα ταξίδεψαν πάνω από το έδαφος. Έτσι προσαρμόζουμε το πρόγραμμά μας, ώστε να δεχτεί ύψος σε πόδια και να βγάζει την απόσταση σε χιλιόμετρα :

($height,$angle)=@ARGV;

$height/=3.2;			# divide feet by 3.2 to get metres

&howfar;
print "With a glide ratio of $angle:1 you can fly $distance from $height\n";

$angle++;
&howfar;
print "With a glide ratio of $angle:1 you can fly $distance from $height\n";

$angle-=2;
&howfar;
print "With a glide ratio of $angle:1 you can fly $distance from $height\n";

sub howfar {
	$distance=$height*$angle;
}

Όταν το τρέξεις θα πάρεις πιθανόν ένα αποτέλεσμα το οποίο περιέχει κάποια ψηφία μετά τη δεκαδική τελεία. Αυτό μπορεί να διορθωθεί με τη συνάρτηση int, η οποία στην Perl και σε πολλές άλλες γλώσσες, επιστρέφει έναν αριθμό σαν ακέραιο, δηλαδή χωρίς αυτά τα νούμερα μετά τη δεκαδική τελεία.

Επίσης άλλο ένα σημάδι κακής προγραμματιστικής πρακτική στο τελευταίο παράδειγμα είναι το ‘3.2’ που χρησιμοποιείται για να μετατρέπει τα πόδια σε μέτρα. Μπορεί να μην υπάρξει μετατροπή του αλλά να αποφασίσουμε εμείς να χρησιμοποιούμε το 3.208 στη θέση του 3.2. Μπορεί να αποφασίσουμε να κάνουμε μετατροπή από πόδια σε ναυτικά μίλια. Δεν ξέρουμε τι μπορεί να συμβεί. Ο κώδικας πρέπει να είναι ευέλικτος και αυτό σημαίνει να αποφεύγεται η χρήση σταθερών.

Ακολουθεί η νέα βελτιωμένη έκδοση με τη χρήση της int και την αποφυγή των σταθερών :

($height,$ratio)=@ARGV;
$cnv1=3.2;			# now it is a variable.  Could easily be a cmd line 
				# parameter too.  We have the flexibility.
$height  =int($height/$cnv1);	# divide feet by 3.2 to get metres

&howfar;
print "With a glide ratio of $ratio:1 you can fly $distance from $height\n";

$ratio++;
&howfar;
print "With a glide ratio of $ratio:1 you can fly $distance from $height\n";

$ratio-=2;
&howfar;
print "With a glide ratio of $ratio:1 you can fly $distance from $height\n";

sub howfar {
	$distance=int($height*$ratio);
}

Μπορούμε φυσικά να βάλουμε την print πρόταση μέσα στην υπορουτίνα, αλλά συνήθως διαχωρίζουμε την παρουσίαση εισαγωγής από τον υπολογισμό. Είναι ευκολότερο έτσι να το τροποποιήσουμε αργότερα.

Παράμετροι

Μια λύση είναι να περάσεις τις παραμέτρους της υπορουτίνας.

($height,$ratio)=@ARGV;
$cnv1=3.2;			

&howfar($height,$ratio);
print "With a glide ratio of $ratio:1 you can fly $distance from $height\n";

&howfar($height,$ratio+1);
print "With a glide ratio of ",$ratio+1,":1 you can fly $distance from $height\n";

&howfar($height,$ratio-1);
print "With a glide ratio of ",$ratio-1,":1 you can fly $distance from $height\n";

sub howfar {
	print "The parameters passed to this subroutine are @_\n";
	($ht,$rt)=@_;
	$ht  =int($ht/$cnv1);
	$distance=int($ht*$rt);
}

Κάποια πράγματα έχουν αλλάξει εδώ. Πρώτον, η υπορουτίνα καλείται με παραμέτρους. Αυτές είναι μια λίστα που οριοθετείται με κόμμα μετά το κάλεσμα της υπορουτίνας. Οι δύο παράμετροι είναι $height και $ratio. Οι παράμετροι τελειώνουν στην υποτουτίνα όπως ο @_ πίνακας.. Όλες οι συνηθισμένες λειτουργίες πίνακα δουλεύουν. Αυτό που έχουμε να κάνουμε είναι να δηλώσουμε τα περιεχόμενα του πίνακα σε δυο μεταβλητές.

Έχουμε επίσης μετακινήσει τη συνάρτηση μετατροπής μέσα στην υπορουτίνα, επειδή θέλουμε να τοποθετήσουμε όλο τον κώδικα για τη γενίκευση της απόστασης σε ένα μέρος.

 

Namespaces

Δεν μπορούμε να χρησιμοποιήσουμε τις μεταβλητές που ονομάζονται $height και $ratio, επειδή τις τροποποιήσαμε στην υπορουτίνα και αυτό θα επηρεάσει το κύριο πρόγραμμα. Γι’ αυτό επιλέγουμε νέες μεταβλητές για να συνεχίσουμε την λειτουργία. Τέλος, μια μικρή αλλαγή έγινε στο αποτέλεσμα εξαγωγής.

Η προσέγγιση αυτή δουλεύει αρκετά καλά για το δικό μας μικρό πρόγραμμα εδώ. Για μεγαλύτερα προγράμματα, το να σκέφτεσαι ονόματα μεταβλητών συνεχώς είναι αρκετά δύσκολο. Θα ήταν ακόμη δυσκολότερο αν διαφορετικοί προγραμματιστές δούλευαν σε διαφορετικά τμήματα του προγράμματος. Θα ήταν απίθανο αν ένα πρόγραμμα γραφόταν, μετά η επέκταση γινόταν από κάποιο πρόσωπο κάπου αλλού και αυτή η ίδια η επέκταση θα έπρεπε να χρησιμοποιούνταν από πολλούς ανθρώπους σε πολλά διαφορετικά προγράμματα. Φυσικά το ρίσκο της χρησιμοποίησης του ίδιου ονόματος μεταβλητής είναι πολύ μεγάλο. Υπάρχει λύση. Φαντάσου ότι σου ανήκει ένα σπίτι με δυο κήπους. Έχεις δύο σκυλιά, ένα στον μπροστινό κήπο και ένα στον πίσω. Και τα δύο όμως τα σκυλιά ονομάζονται Rover. Μπορείς φυσικά να αλλάξεις κάποιο από αυτά χωρίς να επηρεαστεί το άλλο. Δεν πρόκειται να τα μπερδέψεις αφού βρίσκονται σε διαφορετικά μέρη, σε δυο διαφορετικά namespaces.

 

Εμβέλεια Μεταβλητής

$name='Rover';
$pet ='dog';
$age =3;

print "$name the $pet is aged $age\n";

{
	my $age =4;	  # run this again, but comment this line out
	my $name='Spot';  # and this one
	$pet    ='cat';

	print "$name the $pet is aged $age\n";
}

print "$name the $pet is aged $age\n";

Η αρχή του block δίνεται από το {. Ένα χαρακτηριστικό του block είναι ότι μπορεί να έχει το δικό του namespace. Η δήλωση μεταβλητών, δηλαδή η αρχικοποίηση, μέσα σε αυτό το block είναι απλά κανονικές μεταβλητές, εκτός αν δηλώνονται με το my.

Όταν οι μεταβλητές δηλώνονται με το my είναι ορατές μόνο μέσα στο block. Επίσης, κάθε μεταβλητή που έχει το ίδιο όνομα έξω από το block αγνοείται.

Σημεία που πρέπει να προσεχθούν από το παραπάνω παράδειγμα είναι:

 

Οι μεταβλητές my

Τα namespaces δουλεύουν για όλους τους άλλους τύπους των μεταβλητών επίσης, όπως πίνακες και hashes. Είναι ένας τρόπος να γράψεις κώδικα και να μην νοιάζεσαι τι ονόματα μεταβλητών χρησιμοποιούν οι υπόλοιποι. Απλά δηλώνεις τα πάντα με το my. Το πρόγραμμα που χρησιμοποιήσαμε πριν για το gliding μπορεί τώρα να βελτιωθεί ως εξής :

($height,$ratio)=@ARGV;
$cnv1=3.2;			

&howfar($height,$ratio);
print "With a glide ratio of $ratio:1 you can fly $distance from $height\n";

&howfar($height,$ratio+1);
print "With a glide ratio of ",$ratio+1,":1 you can fly $distance from $height\n";

&howfar($height,$ratio-1);
print "With a glide ratio of ",$ratio-1,":1 you can fly $distance from $height\n";

sub howfar {
	my ($height,$ratio)=@_;
	$height  =int($height/$cnv1);
	$distance=int($height*$ratio);
}

Η μόνη αλλαγή είναι ότι οι παράμετροι στην υπορουτίνα, π.χ τα περιεχόμενα του πίνακα @_, δηλώνονται με το my. Αυτό σημαίνει ότι τώρα είναι ορατοί μόνο μέσα στο block. Το block συμβαίνει να είναι επίσης και υπορουτίνα. Έξω από το block οι κανονικές μεταβλητές είναι ακόμη προσπελάσιμες. Σ’ αυτό το σημείο εισάγουμε έναν τεχνικό όρο, τον ‘lexical scoping’. Αυτό σημαίνει ότι η μεταβλητή είναι ορισμένη στο block – δηλαδή είναι ορατή μόνο μέσα στο block.

Ακόμη πρέπει να ασχοληθούμε με το τι μεταβλητές χρησιμοποιούμε μέσα στην υπορουτίνα. Η μεταβλητή $distance δημιουργείται μέσα στην υπορουτίνα και χρησιμοποιείται έξω από αυτή. Σε μεγαλύτερα προγράμματα θα δημιουργηθεί ακριβώς το ίδιο πρόβλημα με αυτό προηγουμένως – πρέπει να προσέξεις ότι οι μεταβλητές που χρησιμοποιείς στην υπορουτίνα είναι ίδιες με αυτές που είναι εκτός της υπορουτίνας. Η προφανής λύση είναι να δηλώσεις την $distance με το my. Αν γίνει αυτό, τότε πώς θα πάρουμε το αποτέλεσμα της υπορουτίνας; Όπως παρακάτω :

($height,$ratio)=@ARGV;
$cnv1=3.2;			

$distance=&howfar($height,$ratio);  # run this again and delete '$distance='
print "With a glide ratio of $ratio:1 you can fly $distance from $height\n";

$distance=&howfar($height,$ratio+1);
print "With a glide ratio of ",$ratio+1,":1 you can fly $distance from $height\n";

$distance=&howfar($height,$ratio-1);
print "With a glide ratio of ",$ratio-1,":1 you can fly $distance from $height\n";

sub howfar {
	my ($height,$ratio)=@_;
	my $distance;
	$height  =int($height/$cnv1);
	$distance=int($height*$ratio/1000); 	# output result in kilometres not metres
}

Πρώτη αλλαγή, η $distance δηλώνεται με το my. Δεύτερον, το αποτέλεσμα της υπορουτίνας ορίζεται σε μια μεταβλητή, η οποία επίσης ονομάζεται $distance. Ωστόσο, η $distance αυτή είναι σε διαφορετικό namespace. Θυμήσου το παράδειγμα που αναφέρθηκε με τους δυο κήπους. Ίσως θέλεις να διαγράψεις την ‘$distance=’ από την πρώτη δήλωση και να ξανατρέξεις τον κώδικα. Η μόνη αλλαγή είναι αυτή που αλλάζει το αποτέλεσμα από μέτρα σε χιλιόμετρα.

Βάζουμε στην υπορουτίνα δυο αριθμούς, οι οποίοι μπορεί να είναι ή να μην είναι μεταβλητές. Δηλώνουμε το αποτέλεσμα της υπορουτίνας σε μια μεταβλητή. Δεν ενδιαφερόμαστε τι συμβαίνει μέσα στην υπορουτίνα, τι μεταβλητές χρησιμοποιεί ή τι μαγικό παρουσιάζει. Είναι το πως θα έπρεπε να λειτουργούν οι υπορουτίνες. Η μόνη εξαίρεση είναι η μεταβλητή $cnv1. Αυτή δηλώθηκε στο κύριο σώμα του προγράμματος, αλλά χρησιμοποιείται επίσης και στην υπορουτίνα. Αυτό έγινε σε περίπτωση που χρειάζεται να χρησιμοποιήσουμε την μεταβλητή κάπου αλλού.

 

Πολλαπλές Επιστροφές Τιμών

Αυτό που θα κάνουμε είναι να βρούμε πόσο θα πάρει στον glider πιλότο να κάνει την απόσταση. Για τον υπολογισμό αυτό πρέπει να γνωρίζουμε την ταχύτητα στον αέρα που χρησιμοποιεί. Αυτό θα είναι μια τρίτη παράμετρος. Ο πραγματικός υπολογισμός θα είναι τμήμα του howfar.

Μια εύκολη αλλαγή :

 ($height,$ratio,$airspeed)=@ARGV;
$cnv1=3.2;			
$cnv2=1.8;

($distance,$time)=&howfar($height,$ratio,$airspeed);
print "Glide ratio $ratio:1, $distance from $height taking $time\n";

($distance,$time)=&howfar($height,$ratio+1,$airspeed);
print "Glide ratio ",$ratio+1,":1, $distance from $height taking $time\n";

($distance,$time)=&howfar($height,$ratio-1,$airspeed);
print "Glide ratio ",$ratio-1,":1, $distance from $height taking $time\n";

sub howfar {
	my ($height,$ratio,$airspeed)=@_;
	my ($distance,$time);			    # how to 'my' multiple variables
	$airspeed*=$cnv2;		 	    # convert knots to kmph
	$height  =int($height/$cnv1);
	$distance=int($height*$ratio/1000);
	$time	 =int($distance/($airspeed/60));    # simple time conversion
	# print "Time:$time, Distance:$distance\n"; # uncomment this later
}

Το παραπάνω δεν λειτουργεί σωστά. Πρώτον, οι αλλαγές. Το αποτέλεσμα του howfar τώρα ορίζεται σε δύο μεταβλητές. Οι υπορουτίνες επιστρέφουν μια λίστα, και έτσι η δήλωση σε κάποιες βαθμωτές μεταβλητές μεταξύ παρενθέσεων που χωρίζονται με κόμα θα δουλέψει. Είναι ακριβώς το ίδιο με το διάβασμα των ορισμάτων γραμμής εντολής από τον @ARGV.

Επίσης έχουμε μια νέα παράμετρο, την $airspeed. Είναι μια άλλη αλλαγή και υπολογισμός μιας γραμμής που παρέχει το ποσό των λεπτών που χρειάζεται να πετάξει $distance.

Αν κοιτάξεις προσεκτικά θα διαπιστώσεις που βρίσκεται το λάθος. Το πρόβλημα είναι ότι η Perl επιστρέφει το αποτέλεσμα της τελευταίας έκφρασης που εκτιμάται. Σ’ αυτή την περίπτωση, η τελευταία έκφραση είναι αυτή που υπολογίζει την $time και έτσι η τιμή $time επιστρέφεται και είναι η μοναδική τιμή που επιστρέφεται. Η τιμή της $time αντιστοιχείται στην $distance και η $distance η ίδια δεν παίρνει στην πραγματικότητα μια τιμή.

Ξανατρέξε το πρόγραμμα αλλά αυτή τη φορά αφήνοντας ασχολίαστη τη γραμμή στην υπορουτίνα, η οποία τυπώνει την $distance και $time. Θα προσέξεις ότι η τιμή είναι 1, που σημαίνει ότι η έκφραση ήταν επιτυχής. Η Perl επιστρέφει την τιμή της τελευταίας έκφρασης εκτιμημένη.

Αυτά όμως δεν είναι ότι χρειαζόμαστε. Αυτό που απαιτείται είναι μια μέθοδος που θα λέει στην Perl τι χρειάζεται να επιστραφεί :

($height,$ratio,$airspeed)=@ARGV;
$cnv1=3.2;			
$cnv2=1.8;

($distance,$time)=&howfar($height,$ratio,$airspeed);
print "Glide ratio $ratio:1, $distance from $height taking $time\n";

($distance,$time)=&howfar($height,$ratio+1,$airspeed);
print "Glide ratio ",$ratio+1,":1, $distance from $height taking $time\n";

($distance,$time)=&howfar($height,$ratio-1,$airspeed);
print "Glide ratio ",$ratio-1,":1, $distance from $height taking $time\n";

sub howfar {
	my ($height,$ratio,$airspeed)=@_;
	my ($distance,$time);			 # how lexically scope multiple variables
	$airspeed*=$cnv2;			 # convert knots to kmph
	$height  =int($height/$cnv1);
	$distance=int($height*$ratio/1000); 	 # output result in kilometres not metres
	$time	 =int($distance/($airspeed/60)); # simple time conversion
	return ($distance,$time);		 # explicit return
}

Τώρα, έχοντας κάνει μια απλή διόρθωση, λέμε στην Perl τι να επιστρέψει με την συνάρτηση return. Με την συνάρτηση αυτή ελέγχουμε πλέον τι θα επιστραφεί και πότε. Είναι αρκετά συνηθισμένο να χρησιμοποιούμε if προτάσεις για τον έλεγχο διαφορετικών επιστρεφόμενων τιμών. Υπάρχει ένα μικρό ελάττωμα στο παραπάνω πρόγραμμα. Τρέξε το παρακάτω :

 ($height,$ratio,$airspeed)=@ARGV;
$cnv1=3.2;			
$cnv2=1.8;

($distance,$time)=&howfar($height,$ratio,$airspeed);
print "Glide ratio $ratio:1, $distance from $height taking $time\n";

($distance,$time)=&howfar($height,$ratio+1,$airspeed);
print "Glide ratio ",$ratio+1,":1, $distance from $height taking $time\n";

$distance=&howfar($height,$ratio-1);	# old way of calling it
print "With a glide ratio of ",$ratio-1,":1 you can fly $distance from $height\n";

sub howfar {
	my ($height,$ratio,$airspeed)=@_;
	my ($distance,$time);			 
	$airspeed*=$cnv2;			 
	$height  =int($height/$cnv1);
	$distance=int($height*$ratio/1000); 	 
	$time	 =int($distance/($airspeed/60)); 
	return ($distance,$time);
}

A division by 0 results third time around. Αυτό γίνεται φυσικά επειδή η $airspeed δεν υπάρχει και έτσι είναι επόμενο να είναι 0. Το να κάνεις τις υπορουτίνες να δουλεύουν προς τα πίσω είναι σημαντικό σε μεγάλα προγράμματα. Υπάρχουν αρκετοί τρόποι για να διορθώσεις το πρόβλημα, και ένας από αυτούς είναι ο ακόλουθος :

 ($height,$ratio,$airspeed)=@ARGV;
$cnv1=3.2;			
$cnv2=1.8;

($distance,$time)=&howfar($height,$ratio,$airspeed);
print "Glide ratio $ratio:1, $distance from $height taking $time\n";

($distance,$time)=&howfar($height,$ratio+1,$airspeed);
print "Glide ratio ",$ratio+1,":1, $distance from $height taking $time\n";

$distance=&howfar($height,$ratio-1);
print "With a glide ratio of ",$ratio-1,":1 you can fly $distance from $height\n";

print "Direct print: ",join ",",&howfar(5000,55,60)," not bad for no engine!\n";

sub howfar {
	my ($height,$ratio,$airspeed)=@_;
	my ($distance,$time);			 # how to 'my' multiple variables
	$airspeed*=$cnv2;			 # convert knots to kmph
	$height  =int($height/$cnv1);
	$distance=int($height*$ratio/1000); 	 # output result in kilometres not metres
	if ($airspeed  0) {
		$time	 =int($distance/($airspeed/60));
		return ($distance,$time);
	} else {
		return $distance;
	}
}

Εδώ ελέγχουμε την $airspeed ώστε να μην γίνουν διαιρέσεις με το 0. Επίσης επηρεάζει αυτό που επιστρέφουμε. Υπάρχει μια νέα print πρόταση, η οποία δείχνει ότι δεν χρειάζεται να ορίζεις ενδιάμεσες μεταβλητές ή ακόμη να περάσεις μεταβλητές ως παραμέτρους.

 

LOCAL

Ακολουθεί άλλη μια συνάρτηση:

@words=@ARGV;

print "Output Field Separator is :$,:\n";
print '1. Words:', @words, "\n";

&change;

$,='_';

print "\nOutput Field Separator is :$,:\n";
print '2. Words:', @words, "\n";

&change;

sub change {
	print '   Words:', @words, "\n";
}

η οποία θα μπορούσε να εκτελεστεί όπως παρακάτω:

perl test.pl sarcasm is the lowest form of wit

Η ειδική μεταβλητή ‘$,’ ορίζει τι πρέπει να τυπώσει η Perl μεταξύ των λιστών που δίνονται. Εξ’ ορισμού δεν τυπώνει τίποτα. Έτσι οι πρώτες δύο τυπώσεις δεν πρέπει να έχουν κενά μεταξύ των λέξεων. Μετά δηλώνουμε το ‘ _ ’ στην ‘$,’ και έτσι οι επόμενες τυπώσεις έχουν υπογραμμίσεις μεταξύ των λέξεων.

Αν θέλουμε να χρησιμοποιήσουμε μια διαφορετική τιμή για την ‘$,’, στην υπορουτίνα change, και να μην πειράξουμε την κύρια τιμή, θα υπάρξει κάποιο πρόβλημα. Αυτές δεν μπορούν να διορθωθούν με το ‘my’, γιατί οι γενικές μεταβλητές όπως η ‘$,’ δεν μπορούν αυτή τη φορά να είναι ορατές. Έτσι:

@words=@ARGV;

print "Output Field Separator is :$,:\n";
print '1. Words:', @words, "\n";

&change;

$,="_";

print "\nOutput Field Separator is :$,:\n";
print '2. Words:', @words, "\n";

&change;

sub change {
	$save=$,;
	$,='*';
	print '   Words:', @words, "\n";
	$,=$save;
}

Το παραπάνω δουλεύει αλλά δεν είναι και ό,τι καλύτερο. Η Perl για τις περιπτώσεις αυτές έχει μια ειδική συνάρτηση, που ονομάζεται: local. Ακολουθεί ένα παράδειγμα με την local:

@words=@ARGV;

print "Output Field Separator is :$,:\n";
print '1. Words:', @words, "\n";

&change;

$,="_";

print "\nOutput Field Separator is :$,:\n";
print '2. Words:', @words, "\n";

&change;

sub change {
        local $,="!-!";
	print '   Words:', @words, "\n";
}

Αν δοκιμάσεις την παραπάνω με το ‘my’ δεν θα δουλέψει. Η συνάρτηση ‘local’ δουλεύει με τον ίδιο τρόπο που δουλεύει και η ‘my’, αλλά ορίζει προσωρινές τιμές στις γενικές μεταβλητές. Η συνάρτηση ‘my’ δημιουργεί νέες μεταβλητές που έχουν το ίδιο όνομα.

Στην πράξη η διαφορά είναι:

π lexically scoped variables, δηλαδή αυτές που δηλώνονται με την ‘my’,

είναι πιο γρήγορες από τις υπόλοιπες, δηλαδή τις non-lexically scoped variables.

π Οι μεταβλητές της ‘local’ είναι ορατές όταν καλούν υπορουτίνες.

π Η ‘my’ δεν δουλεύει με γενικές μεταβλητές όπως η ‘$,’, γι’ αυτό καλύτερα να χρησιμοποιείς την ‘local’.

 

Επιστροφή πινάκων

Θα έχεις παρατηρήσει ότι η Perl επιστρέφει μια μεγάλη λίστα από υπορουτίνες. Υπέθεσε όμως ότι θέλεις να διαχωρίσεις λίστες, για παράδειγμα δύο πίνακες. Ακολουθεί ένας τρόπος για να γίνει αυτό:

 ($w1,$w2)=&wordfunc("Hello World");	# Assign the array references to scalars

print "@$w1 and @$w2\n";		# deference, ie access, the arrays referred to
#print "$w1 and $w2\n";			# uncomment this next time round

sub wordfunc {
        my $phrase=shift;
	my (@words,@word1,@word2);	# declare three variables lexically
	@words=split /\s+/,$phrase;	# split the phrase on whitespace
	@word1=split //,$words[0];	# create array of letters from the first word
	@word2=split //,$words[1];	# and the second
	return (\@word1,\@word2);	# return references to the two arrays -- scalars
}

Όπως γνωρίζουμε η Perl επιστρέφει μόνο μια λίστα. Έτσι κάνουμε την Perl να επιστρέφει μια λίστα των πινάκων που έχει μόλις δημιουργηθεί. Δεν κάνουμε χρήση των ίδιων των πινάκων αλλά κάνουμε αναφορές σε αυτούς. Κάτι σαν τη λίστα με τα ψώνια, η οποία είναι ένα κομμάτι από χαρτί και όχι τα ίδια τα αγαθά. Η αναφορά αυτή δημιουργείται με τη χρήση του backslash ‘\’. Όταν επιστραφούν δύο αναφορές πινάκων δηλώνονται σε βαθμωτές μεταβλητές. Αν αφήσεις ασχολίαστη την δεύτερη γραμμή τύπωσης θα δεις δύο αναφορές σε πίνακες. Το επόμενο πρόβλημα είναι πώς θα διαχωρίσεις τις αναφορές ή πως θα διασχίσεις τους πίνακες. Αυτό γίνεται με το ‘@$xxx’.

 

Ενότητες – (Modules)

 Εισαγωγή

Οι υπορουτίνες είναι κομμάτια του κώδικα που επαναχρησιμοποιείται. Υπάρχουν και μπορείς να ξαναχρησιμοποιήσεις τον κώδικα χωρίς να πρέπει να τον ξαναγράψεις. Μια ενότητα είναι παρόμοια με την υπορουτίνα. Είναι επίσης ένα αχρησιμοποίητο κομμάτι κώδικα. Η διαφορά είναι ότι η ενότητες δεν υπάρχουν στο πρόγραμμά σου. Είναι ξεχωριστά έγγραφα. Για παράδειγμα μπορείς να γράψεις μια ρουτίνα για να στείλεις e-mail. Έπειτα μπορείς να χρησιμοποιήσεις αυτόν τον κώδικα ακόμη και σε χίλια διαφορετικά προγράμματα με μια απλή αναφορά στο αρχικό πρόγραμμα.

Η Perl διαθέτει ένα μεγάλο αριθμό ενοτήτων. Αυτές έχουν γραφεί από άλλους προγραμματιστές. Τον έφτιαξαν σε ενότητες και τον έχουν διαθέσει και στους υπόλοιπους. Εκτός από τις ενότητες που περιλαμβάνει η Perl, υπάρχουν πολλές διαθέσιμες και στο CPAN (Comprehensive Perl Archive Network).

 

File::Find—Χρησιμοποιώντας μια ενότητα

Ένα παράδειγμα ενότητας της Perl είναι το File::Find.Υπάρχουν αρκετές ενότητες στο τμήμα File::Find, όπως τα File::Basetree, File::Compare, File::Start. Ακολουθεί ένα παράδειγμα για τον τρόπο που χρησιμοποιείται το File::Find:

use File::Find;

$dir1='/some/dir/with/lots/of/files';
$dir2='/another/directory/';

find(\&wanted, $dir1,$dir2);

sub wanted {
	print "Found it $File::Find::dir/$_\n" if /^[a-d]/i;

}

 

Η πρώτη γραμμή είναι και η πιο σημαντική. Η συνάρτηση use φορτώνει την ενότητα File::Find, και έτσι είναι διαθέσιμη για χρήση η δύναμη της File::Find. Όπως η συνάρτηση της Find. Αυτή δέχεται δύο βασικές παραμέτρους:

 

Η υπορουτίνα wanted απλά τυπώνει στο directory το αρχείο που έχει βρεθεί σ’ αυτό , αν το όνομα αρχείου αρχίζει με a, b, c ή d.Φτιάξε το δικό σου regex. Η γραμμή $File::Find::dir σημαίνει: την μεταβλητή $dir στην ενότητα $File::Find.Αυτό όμως θα εξηγηθεί στην επόμενη ενότητα. Η παράμετρος \$wanted είναι αναφορά σε μια υπορουτίνα. Αυτό σημαίνει ότι ο κώδικας στο fil::find γνωρίζει πού να βρει την υπορουτίνα $wanted.

 

ChangeNotify

Άλλο ένα παράδειγμα είναι το Win32::ChangeNotify. Όπως ίσως περίμενες υπάρχει ένας αριθμός από Win32-Ειδικές ενότητες, και η ChangeNotify είναι μία από αυτές. Περιμένει μέχρι να αλλάξει κάτι σε ένα directory και μετά ενεργεί. Τι περιμένει και τι κάνει εξαρτάται από σένα, για παράδειγμα:

use Win32::ChangeNotify;

$Path='/downloads';
$WatchSubTree=0;
$Events='FILE_NAME';
$browser='E:/progs/netscape/Communicator/program/netscape.exe';
$changes=0;

$notify = Win32::ChangeNotify-new($Path,$WatchSubTree,$Events);

while (1) {
	print "- ",scalar(localtime)," $changes so far to $Path.\n";
	$notify-wait;
	++$changes;
	print "- ",scalar(localtime), " Launching $browser...\n";
	system("$browser $Path");
	$notify-reset;
}


Και πάλι η ενότητα είναι ενσωματωμένη μέσα στο πρόγραμμα με την use. Δημιουργείται ένα αντικείμενο για αναφορά από την μεταβλητή $Notify. Οι παράμετροι που περάστηκαν είναι το μονοπάτι που θα ειδωθεί κάθε φορά που θέλουμε να δούμε τα subtrees και τι είδους γεγονότα θέλουμε να προσεχθούν, στην περίπτωση αυτή μόνο οι αλλαγές στο όνομα αρχείου.

Μετά, εισάγουμε μια loop η οποία συνεχίζει όσο το 1 είναι true-κάτι το οποίο θα ισχύει για πάντα.

Το πρόγραμμα σταματάει όταν η μέθοδος wat της $Notify καλείται. Μόνο όταν υπάρχει μια αλλαγή στο directory, τότε η υπόλοιπη υπορουτίνα ολοκληρώνεται, ξεκινώντας τον browser. Πρέπει να ξαναδοκιμάσεις το $Notify Object. Μπορείς να χρησιμοποιήσεις όσες ενότητες θέλεις στο πρόγραμμα.

 

Η δική σου ενότητα

Είναι πραγματικά εύκολο να δημιουργήσεις τις δικές σου ενότητες. Πρώτα θα δημιουργήσουμε το φανταστικό bit του κώδικα που θέλουμε να ξαναχρησιμοποιηθεί παντού. Θα γράψουμε ένα κανονικό πρόγραμμα στην Perl.

$name=shift;

print &logname($name);

sub logname {
	my $name=shift;
	my @namebits;
	my ($logon,$inital);
	@namebits=split /\s+/,$name;
	($inital)=$name=~/(\w)/;
	$logon=$inital.$namebits[$#namebits];
	$logon=lc $logon;
	return $logon;
}

Εκτελείται κάπως έτσι: Perl script.pl “Nick Bladon’’. Το ίδιο το script δεν είναι κάτι το εκπληκτικό. Η συνάρτηση lc ονομάζεται: LowerCase ή πιθανόν lOWERcASE. Για να το μετατρέψεις σε μια ενότητα ακολούθησε τα παρακάτω βήματα:

1. Βρές πού έχει εγκατασταθεί το αντίγραφο της Perl. Για παράδειγμα c:\progs\perl.

2. Μέσα σε αυτό το directory πρέπει να υπάρχει το lib directory.

3. Φτιάξτε ένα directory μέσα στο lib, για παράδειγμα c:\progs\perl\lib\RMP\.

Τώρα θα φτιάξουμε την ενότητα. Θυμήσου ότι η ενότητα είναι απλά ένας κώδικας τον οποίο πρόκειται να ξαναχρησιμοποιήσεις. Παρακάτω ξαναγράφουμε το παράδειγμα έτοιμο για να γίνει ενότητα :

sub logname {
        my $name=shift;
        my @namebits;
        my ($logon,$inital);
        @namebits=split /\s+/,$name;
        ($inital)=$name=~/(\w)/;
        $logon=$inital.$namebits[$#namebits];
        $logon=lc $logon;
        return $logon;
}

1;

Το ψηφίο που έχει προστεθεί είναι το 1 στο τέλος του κώδικα. Γιατί; Η Perl απαιτεί όλες οι ενότητες να επιστρέψουν true. Γνωρίζουμε ότι η υπορουτίνα πάντα επιστρέφει την τιμή της τελευταίας εκτιμημένης έκφρασης. Καθώς το 1 εκτιμάται σαν true, αυτό θα επιτευχθεί. Πρέπει να το σώσεις σαν logon.pm στο πρόσφατα δημιουργημένο σου directory στο lib. Το pm σημαίνει Perl Module.

Αυτό ήταν. Έχει δημιουργηθεί μια ενότητα. Για να την χρησιμοποιήσεις φτιάξε ένα κανονικό έγγραφο Perl όπως το παρακάτω.

use RMP::logon;

$name=shift;

print logname($name);

Bondage and Discipline

Η Perl είναι πολύ ευέλικτη γλώσσα.

Για μικρά έγγραφα δεν χρειάζεται να δηλώνεις μεταβλητές, τύπους και γενικά να σπαταλάς χρόνο φτιάχνοντας κανόνες. Η Perl δεν απαιτεί όλα τα παραπάνω στοιχεία της καλής προγραμματιστικής πρακτικής.

Η Perl έχει δύο βασικές μεθόδους που ενισχύουν την πειθαρχεία. Αυτές είναι:

θ -w για warnings

θ use strict;

@input=@ARGV;

$outfile='outfile.txt';
open OUT, "$outfile" or die "Can't open $outfile for write:$!\n";

$input2++;
$delay=2 if $input[0] eq 'sleep';

sleep $delay;

print "The first element of \@input is $input[0]\n";
print OUY "Slept $delay!\n";

Το παραπάνω δεν κάνει αρκετά πράγματα. Απλά εκτυπώνει τον πρώτο ορισμό που παρέχεται και δείχνει την συνάρτηση sleep. Το ίδιο το πρόγραμμα είναι γεμάτο από λάθη και είναι μόνο λίγες γραμμές. Πόσα μπορείς να προσέξεις; Μέτρησέ τα. Όταν τελειώσεις , εκτέλεσε το πρόγραμμα χρησιμοποιώντας τους ελέγχους λαθών:

perl -w script.pl hello

Η Perl βρίσκει λίγα λάθη. Το –w βρίσκει , ανάμεσα σε άλλες τρομακτικές αμαρτίες:

 

Έτσι γενικά το ‘-w’ είναι καλό αφού βοηθάει στο να γράψεις ένα καθαρότερο κώδικα. Σε πολύ μικρά προγράμματα βέβαια μπορείς και να μην το χρησιμοποιήσεις.

 

Shebang

Γνωρίζεις ότι μπορείς να ανοίξεις τα ‘warnings’ με το ‘-w’ στη γραμμή εντολής. Επίσης μπορείς να τα ανοίξεις μέσα σε ένα script. Για το λόγο αυτό μπορείς να δώσεις στην Perl κάθε επιλογή γραμμής εντολής μέσα στο ίδιο το script. Για παράδειγμα:

perl script.pl hello

για να εκτελέσει αυτό:

#!perl -w

@input=@ARGV;

$outfile='outfile.txt';
open OUT, "$outfile" or die "Can't open $outfile for write:$!\n";

$input2++;
$delay=2 if $input[0] eq 'sleep';

sleep $delay;

print "The first element of \@input is $input[0]\n";
print OUY "Slept $delay!\n";

έχει το ίδιο αποτέλεσμα με το :

perl -w script.pl hello

Ίσως είναι πιο βολικό να τοποθετήσεις το flag μέσα στο script. Δεν χρειάζεται να είναι απλά το ‘-w’, μπορεί να είναι κάθε όρισμα που η Perl υποστηρίζει. Τρέξε το

perl -h

για μια ολοκληρωμένη λίστα.

Η πρώτη γραμμή, ‘#!Perl –w’ είναι η γραμμή Shebang. Αυτό ορίστηκε από το UNIX όπου η Perl πρωτοαναπτύχθηκε. Τα UNIX systems κάνουν ένα έγγραφο εκτελέσιμο αλλάζοντας μια ιδιότητα. Το λειτουργικό σύστημα έπειτα φορτώνει το αρχείο και προσπαθεί να το εκτελέσει-στην περίπτωση αυτή κοιτώντας στην πρώτη γραμμή και μετά φορτώνοντας το διερμηνέα της Perl. Τα windows systems ξέρουν ότι όλα τα αρχεία με μια επέκταση πρέπει να περαστούν σε ένα πρόγραμμα για εκτέλεση, για παράδειγμα όλα τα ‘ .bat ’ αρχεία περνιούνται στο ‘ command.com’, και όλα τα ‘ .xls’ αρχεία στο excel. Αυτό σημαίνει ότι η Shebang line δεν είναι τελείως απαραίτητη, αλλά δεν βλάπτει και η χρησιμομποίησή της.

 

Use strict;

Τι είναι το strict και πώς χρησιμοποιείται; Η ενότητα strict περιορίζει τις μη ασφαλείς κατασκευές σύμφωνα με τα Perldocs. Η ενότητα strict είναι pragma, το οποίο είναι ένας υπαινιγμός που πρέπει να υπακούεται.

Όταν καθιστάς ικανή την ενότητα strict, τα τρία πράγματα που η Perl κάνει είναι:

· μεταβλητές ‘vars’

· αναφορές ‘refs’

· υπορουτίνες ‘subs’

Οι strict μεταβλητές είναι χρήσιμες. Αυτό σημαίνει ότι πρέπει να δηλώνονται όλες οι μεταβλητές. Επιπλέον, κάθε μεταβλητή πρέπει να ορίζεται με το my ή να είναι πλήρως ορισμένη. Αυτό είναι ένα παράδειγμα ενός προγράμματος που δεν είναι strict, και θα έπρεπε να εκτελείται κάπως έτσι:

perl script.pl "Alain James Smith";

Όπου τα “ ” περικλείουν το string σαν μια μοναδική παράμετρο σε αντίθεση με τις τρείς ξεχωριστές space-delimited παραμέτρους.

#use strict;			# uncomment after running a couple of times

$name=shift;			# shifts @ARGV if no arguments supplied

print "The name is $name\n";
$inis=&initials($name);

$luck=int(rand(10)) if $inis=~/^(?:[a-d]|[n-p]|[x-z])/i;

print "The initials are $inis, lucky number: $luck\n";

sub initials {
        my $name=shift;
        $initials.=$1 while $name=~/(\w)\w+\s?/g;
        return $initials;
}

Όταν αφήνεις ασχολίαστο το use strict; και ξανατρέξεις το πρόγραμμα θα πάρεις κάποιο αποτέλεσμα σαν το παρακάτω:

Global symbol "$name" requires explicit package name at n1.pl line 3.
Global symbol "$inis" requires explicit package name at n1.pl line 6.
Global symbol "$luck" requires explicit package name at n1.pl line 8.
Global symbol "$initials" requires explicit package name at n1.pl line 14.
Execution of n1.pl aborted due to compilation errors.

Αυτές οι προειδοποιήσεις σημαίνουν ότι η Perl δεν είναι αρκετά ξεκάθαρη με το τι εμβέλεια έχουν οι μεταβλητές. Έτσι πρέπει είτε να δηλώνεις τις μεταβλητές με το my, ώστε να είναι περιορισμένες στο τρέχον block, ή να τις αναφέρεις με το πλήρες όνομά τους. Ένα παράδειγμα, χρησιμοποιώντας και τις δυο μεθόδους:

use strict;

$MAIN::name=shift;			# shifts @ARGV if no arguments supplied

print "The name is ",$MAIN::name,"\n";
my $inis='';
my $luck='';

$inis=&initials($MAIN::name);

$luck=int(rand(10)) if $inis=~/^(?:[a-d]|[n-p]|[x-z])/i;

print "The initials are $inis, lucky number: $luck\n";

sub initials {
        my $name=shift;
	my $initials;
        $initials.=$1 while $name=~/(\w)\w+\s?/g;
        return $initials;
}

Οι μεταβλητές my στην υπορουτίνα δεν είναι κάτι καινούριο. Είναι όμως οι μεταβλητές my εκτός της υπορουτίνας. Το κύριο πρόγραμμα είναι επίσης ένα είδος block και έτσι οι μεταβλητές μπορούν να ειδωθούν μόνο μέσα στο block. Το άλλο σημαντικό bit είναι το $MAIN::name business. Αυτό είναι ένα πλήρως ορισμένο όνομα της μεταβλητής. Το πρώτο τμήμα είναι το package όνομα, εδώ το MAIN. Το δεύτερο τμήμα είναι τοπραγματικό όνομα μεταβλητής.

 

Διόρθωση λαθών – Debugging

Κάποια στιγμή θα πρέπει να προβείς και στην διόρθωση λαθών. Αυτό μπορεί να καθυστερήσει αν χρησιμοποιήσεις strict, –w και γράψεις τις υπορουτίνες σου με τον κατάλληλο τρόπο, αλλά το debugging δεν πρόκειται να το αποφύγεις. Κάποιες φορές μπορεί το πρόβλημα να μην σου είναι εμφανές και να συναντάς δυσκολίες.

Κάποιες χρήσιμες τεχνικές είναι:

¨ Σε συχνά διαστήματα κάνε εκτύπωση των μεταβλητών και άλλων πληροφοριών που χρησιμοποιείς.

¨ Χώριζε σε μικρότερα τμήματα κάποια δύσκολα σημεία του προγράμματος. Αφού γίνει αυτό γράψε πίσω στο κυρίως πρόγραμμα τα αποτελέσματα.

¨ βάζε συχνά σχόλια.

Ο διορθωτής λαθών της perl θα σου φανεί χρήσιμος όταν κάπου κολλήσεις. Τρέξε αυτό τον κώδικα κανονικά την πρώτη φορά:

$name=shift;

print "Logon name creation program\n:Converting '$name'\n";

print &logname($name),"\n\n";

print "Program ended at", scalar(localtime),"\n";

sub logname {
        my $name=shift;
        my @namebits;
        my ($logon,$inital);
        @namebits=split /\s+/,$name;
        ($inital)=$name=~/(\w)/;
        $logon=$inital.$namebits[$#namebits];
        $logon=lc $logon;
        return $logon;
}

Θα τον τρέξουμε με τον διορθωτή λαθών ώστε να δείς τη λειτουργία της perl, καθώς ο κώδικας τρέχει:

perl -d script.pl "Peter Dakin";

και είσαι μέσα στον διορθωτή λαθών, το οποίο μοιάζει με :

c:\scripts\db.plperl -d db.pl "Peter Dakin"

Πληκτρολόγησε h ή `h h' για βοήθεια.

Enter h or `h h' for help.

main::(db.pl:1):        $name=shift;
  DB<1

db.pl

Το όνομα του script που εκτελείται

1

Αριθμός γραμμής του script που είναι έτοιμη να εκτελεστεί.

$name=shift;

Ο κώδικας που είναι έτοιμος για εκτέλεση.

 

Πληκτρολόγησε s για ένα βήμα και πάτησε enter. Ο κώδικας $name=shift; θα εκτελεστεί και η perl περιμένει την επόμενη εντολή σου. Συνέχισε να εισάγεις το s μέχρι το πρόγραμμα να τερματίσει.

Αυτό είναι χρήσιμο από μόνο του καθώς βλέπεις τη ροή της υπορουτίνας, αλλά αν πατήσεις h για βοήθεια θα δείς ένα μπερδεμένο πλήθος από επιλογές αποσφαλματοποίησης. Θα αναφερθούν μερικές από αυτές που κρίνονται και περισσότερο χρήσιμες:

n

Εκτελεί το κύριο πρόγραμμα, αλλά παραλείπει το κάλεσμα της υπορουτίνας. Η υπορουτίνα εκτελείται, αλλά δεν βρίσκεσαι μέσα σ’αυτήν. Προσπάθησε να χρησιμοποιήσεις το n στη θέση του s.

/xx/

Ψάχνει μέσα στο πρόγραμμα για xx.

p

Τυπώνει, για παράδειγμα p @namebits, p $name

Enter

Πατώντας το πλήκτρο Enter επαναλαμβάνει την τελευταία n ή s εντολή.

perlcode

Μπορείς να πληκτρολογήσεις οποιονδήποτε κώδικα perl και θα εκτιμηθεί, έχοντας κάποιο αποτέλεσμα στο πρόγραμμά σου. Στο παρακάτω παράδειγμα έχουν. μετακινηθεί τα κενά από την $name. Εισάγει σε bold:

main::(db.pl:1):        $name=shift;
  DB<1 s
main::(db.pl:3):        print "Logon name creation program\n:Converting '$name'\n";
  DB<1 $name=~s/\s//g;

  DB<2 print $name
MarkGray
  DB<3

 

Υπάρχουν πραγματικά πολλές επιλογές αποσφαλματοποίησης οι οποίες είναι χρήσιμες. Πληκτρολόγησε h για μια ολοκληρωμένη λίστα.

Λογικοί Τελεστές

Λογικοί τελεστές είναι οι: or, not, and. Όλοι εκτιμούν κάποιες εκφράσεις. Η έκφραση εκτιμάται σαν true (αληθής) ή false (ψευδής). Εξαρτάται από τον τελεστή τι κριτήρια θα χρησιμοποιούνται για εκτίμηση.

OR

Ο τελεστής or λειτουργεί όπως ακολουθεί:

open STUFF, $stuff or die "Cannot open $stuff for read :$!";

Αυτή η γραμμή σημαίνει: αν η λειτουργία για το άνοιγμα του STUFF αποτύχει, τότε κάνε κάτι άλλο.

Άλλο παράδειγμα :

$_=shift;

/^R/ or print "Doesn't start with R\n";

Αν η κανονική έκφραση είναι false, τότε τυπώνεται ό,τι είναι στο αριστερό τμήμα του or. Όπως γνωρίζεις, η shift δουλεύει στον @ARGV αν δεν δίνεται κανένας στόχος, ή @_ μέσα στην υπορουτίνα.

Η perl διαθέτει δύο τελεστές or. Ο ένας είναι ο ήδη γνωστός or και ο άλλος είναι ο ||.

 

Προτεραιότητα: Τι έρχεται πρώτο

Για να φανεί η διαφορά μεταξύ των δύο πρέπει να μιλήσουμε για προτεραιότητα. Ένα καλό παράδειγμα είναι:

perl -e"print 2+8

το οποίο ξέρουμε ότι ισοδυναμεί με 10.

Αλλά, αν προσθέσουμε :

perl -e"print 2+8/2

τώρα θα είναι 2+8= =10 και διαιρείται με το 2= = 5;

Ή μήπως θα είναι 8/2= = 4 συν 2= = 6;

Η προτεραιότητα σημαίνει πιο πρέπει να εκτελεστεί πρώτα. Στο παραπάνω παράδειγμα βλέπεις ότι η διαίρεση έγινε πρώτη και μετά ακολούθησε η πρόσθεση. Έτσι ισχύει ότι η διαίρεση έχει μεγαλύτερη προτεραιότητα από την πρόσθεση. Μπορείς να κάνεις τη λειτουργία περισσότερο εμφανή με τη χρήση των παρενθέσεων :

perl -e"print ((2+8)/2)

εδώ είναι φανερό ότι θα γίνει πρώτα η πρόσθεση 2+8 και μετά θα διαιρεθεί το αποτέλεσμα με το 2.

Η πρώτη, λοιπόν, διαφορά μεταξύ των τελεστών or και || είναι η προτεραιότητα.

Στο παρακάτω παράδειγμα προσπαθούμε να αντιστοιχίσουμε δύο μεταβλητές σε μη υπάρχοντα στοιχεία ενός πίνακα.

Αυτό θα αποτύχει :

@list=qw(a b c);

$name1 =  $list[4] or "1-Unknown";

$name2 =  $list[4] || "2-Unknown";

print "Name1 is $name1, Name2 is $name2\n";

print "Name1 exists\n" if defined $name1;
print "Name2 exists\n" if defined $name2;

Το αποτέλεσμα είναι ενδιαφέρον. Η μεταβλητή $name2 είχε δημιουργηθεί αν και με ψευδή τιμή. Ωστόσο, η $name1 δεν υπάρχει. Ο λόγος είναι μόνο η προτεραιότητα. Ο τελεστής or έχει χαμηλότερη προτεραιότητα από τον ||.

Αυτό σημαίνει ότι ο or κοιτάζει σε ολόκληρη την έκφραση στο αριστερό του τμήμα. Σε αυτή την περίπτωση, είναι $name1=$list[4]. Εάν είναι true, γίνεται. Αν είναι false, τότε το δεξί τμήμα εκτιμάται ενώ το αριστερό αγνοείται σαν να μην υπήρχε ποτέ. Στο παραπάνω παράδειγμα, μόλις το αριστερό τμήμα βρέθηκε να είναι false, τότε όλο το δεξί τμήμα που εκτιμάει είναι 1-unknown”, το οποίο μπορεί να είναι true αλλά δεν παράγει κάποιο αποτέλεσμα.

Στην περίπτωση του ||, που έχει και μεγαλύτερη προτεραιότητα, εκτιμάται κατευθείαν ο κώδικας στο αριστερό τμήμα του τελεστή. Στην περίπτωση αυτή, είναι $list[4]. Είναι false, οπότε εκτιμάται αμέσως ο κώδικας στα δεξιά του τελεστή. Αλλά, ο γνήσιος κώδικας αριστερά δεν εκτιμήθηκε, $name2= δεν ξεχάστηκε. Ωστόσο, η έκφραση εκτιμάται ως $name2= “2-unknown”.

Το παρακάτω παράδειγμα θα βοηθήσει ώστε να ξεκαθαριστούν κάποια πράγματα:

@list=qw(a b c);

$ele1 = $list[4] or print "1 Failed\n";
$ele2 = $list[4] || print "2 Failed\n";

print <<PRT;
ele1 :$ele1:

ele2 :$ele2:

PRT

Οι δυο αποτυχημένοι κώδικες τυπώνονται, αλλά για διαφορετικούς λόγους. Ο πρώτος τυπώνεται επειδή δηλώνουμε στην $ele1 μια ψευδή τιμή, οπότε το αποτέλεσμα της λειτουργίας είναι false. Έτσι εκτιμάται το δεξί τμήμα.

Ο δεύτερος τυπώνεται επειδή το $list[4] από μόνο του είναι false. Επίσης, όπως μπορείς να δεις, το $ele2 υπάρχει. Γιατί; Ο λόγος είναι ότι το αποτέλεσμα του print “2-Failed\n” έχει δηλωθεί στην $ele2. Αυτό είναι επιτυχές και έτσι επιστρέφει 1.

Άλλο ένα παράδειγμα:

$file='not-there.txt';

open FILE, $file   || print "1: Can't open file:$!\n";

open FILE, $file   or print "2: Can't open file:$!\n";

Στο πρώτο παράδειγμα το μήνυμα λάθους δεν τυπώνεται. Αυτό συμβαίνει επειδή η $file εκτιμήθηκε ως true. Ωστόσο, στο δεύτερο παράδειγμα, ο or κοιτάζει σε ολόκληρη την έκφραση, όχι μόνο τι είναι αμέσως αριστερά και δρα στο αποτέλεσμα της εκτίμησης ολόκληρου του αριστερού τμήματος, όχι μόνο στην έκφραση αμέσως στα αριστερά του.

Μπορείς να διορθώσεις τα πράγματα με την χρήση παρενθέσεων:

$file='not-there.txt';

open FILE, $file   || print "1: Can't open file:$!\n";

open FILE, $file   or print "2: Can't open file:$!\n";

open (FILE, $file) || print "3: Can't open file:$!\n";

Μπορείς να χρησιμοποιείς παρενθέσεις οπουδήποτε αλλού:

@list=qw(a b c);

$name1 =  $list[4]   or "1-Unknown";

($name2 =  $list[4]) || "2-Unknown";

print "Name1 is $name1, Name2 is $name2\n";

print "Name1 exists\n" if defined $name1;
print "Name2 exists\n" if defined $name2;

Τώρα, το ($name2=$list[4]) εκτιμάται σαν μια ολοκληρωμένη έκφραση, όχι απλά όταν η $list[4] εκτιμάται σαν μια ολοκληρωμένη έκφραση και όχι μόνο σαν $list[4], και έτσι παίρνουμε ακριβώς το ίδιο αποτέλεσμα όπως αν χρησιμοποιούσαμε τον τελεστή or.

And

Οι λογικοί τελεστές AND εκτιμούν δυο εκφράσεις, και επιστρέφουν true μόνο αν και οι δυο αυτές είναι true. Σύγκρινέ το με τον or, ο οποίος επιστρέφει true μόνο όταν μια ή περισσότερες εκφράσεις είναι true. Η Perl έχει αρκετούς τελεστές AND. Ο πρώτος τύπος του AND που θα δούμε είναι ο &&:

@list=qw(a b c);

print "List is:@list\n";

if ($list[0] eq 'x' && $list[2]++ eq 'd') {
	print "True\n";
	} else {
	print "False\n";
}

print "List is:@list\n";

Το αποτέλεσμα εδώ είναι false. Είναι φανερό ότι αυτό δεν είναι ίσο με x. Καθώς, οι προτάσεις AND επιστρέφουν true μόνο όταν και οι δυο εκφράσεις που εκτιμούνται είναι true, και καθώς η πρώτη πρόταση είναι false, τότε είναι φανερό ότι η Perl δεν θα προχωρήσει στην δεύτερη πρόταση.

Ο δεύτερος τύπος της πρότασης AND είναι &. Είναι παρόμοιος με τον &&. Πρόσεξε ποια είναι η διαφορά στο παρακάτω παράδειγμα:

@list=qw(a b c);

print "List is:@list\n";

if ($list[0] eq 'x' & $list[2]++ eq 'd') {
	print "True\n";
	} else {
	print "False\n";
}

print "List is:@list\n";

Η διαφορά είναι ότι στο δεύτερο τμήμα της έκφρασης εκτιμάται χωρίς να έχει καμία σημασία ποιο είναι το αποτέλεσμα του πρώτου τμήματος. Εκτός του γεγονότος ότι η πρόταση AND δεν μπορεί πιθανόν να επιστρέψει true, η Perl πηγαίνει κατευθείαν να εκτιμήσει το δεύτερο τμήμα της πρότασης, εδώ το $list[2] καταλήγει σαν d. Ο τρίτος τύπος του AND που θα δούμε είναι ο and. Αυτός συμπεριφέρεται με τον ίδιο τρόπο με τον &&, αλλά είναι χαμηλότερης προτεραιότητας.

 

Άλλοι λογικοί τελεστές

Η Perl έχει τον not, ο οποίος δουλεύει όπως ο ! εκτός της χαμηλής προτεραιότητας. Θυμίσου τη χρήση του ! :

$x !~/match/;

if ($t != 5) {

Υπάρχει επίσης το αποκλειστικό or, ή xor.

Αυτό σημαίνει:

< Αν μια έκφραση είναι true, ο xor επιστρέφει true.

< Αν και οι δυο εκφράσεις είναι false, ο xor επιστρέφει false.

< Αν και οι δυο εκφράσεις είναι true, ο xor επιστρέφει false (η βασική διαφορά με τον τελεστή or)

Ακολουθεί ένα παράδειγμα. Η Jane και η Sonia είναι γνωστές για τις αταξίες που προκαλούν. Θέλεις μια από αυτές να δεχτείς στο πάρτυ σου και για αυτό έχει το παρακάτω έγγραφο της Perl στην πόρτα:

($name1,$name2)=@ARGV;

if ($name1 eq 'Jane' xor $name2 eq 'Sonia') {
	print "OK, allowed\n";
} else {
	print "Sorry, not allowed\n";
}

Τρέξτο όπως:

perl script.pl Jane Karen 

(ένα true, ένα false)

perl script.pl Jim Sonia 

(ένα true, ένα false)

perl script.pl Jane Sonia 

(και τα δύο true)

perl script.pl Jim Sam 

(και τα δύο false)

Λοιπόν, το script δεν είναι αρκετά τέλειο, καθώς όλα τα Jane και Sonia γράφονται με μικρά γράμματα, αλλά επιτυχώς δείχνεται το xor.

Δοκίμασε το:

$_=shift;

print "OK\n" unless not(!/r/i || /o/i & /p/ or /q/);

Η Perl παρέχει μια πληθώρα από λογικές λειτουργίες, ώστε να μην δικαιολογήσαι αν γράψεις έναν κακογραμμένο κώδικα.