Webprogrammierung mit Perl
programmer’s
choice
Die Wahl für professionelle Programmierer und Softwareentwickler. Anerkannte Experten wie z. B. Bjarne Stroustrup, der Erfinder von C++, liefern umfassendes Fachwissen zu allen wichtigen Programmiersprachen und den neuesten Technologien, aber auch Tipps aus der Praxis. Die Reihe von Profis für Profis!
Hier eine Auswahl:
Perl
PostgreSQL
MySQL
Farid Hajji 1184 Seiten, 2. Auflage € 49,95 [D]/sFr 88,00 ISBN 3-8273-1553-2
Bruce Momjian 540 Seiten € 44,95 [D]/sFr 78,00 ISBN 3-8273-1859-9
Heinz-Gerd Raymans 624 Seiten € 44,95 [D]/sFr 78,00 ISBN 3-8273-1887-4
Das Buch bietet eine fundierte Einführung in die Grundlagen von Perl. Das für Programmierer geschriebene Lehr- und Arbeitsbuch zeichnet sich durch viele Beispiele aus der Praxis aus (objektorientierte Programmierung, Tcl/Tk). Zahlreiche Übungsaufgaben vertiefen den Stoff oder geben weiterführende Tipps.
Dieses Buch bietet die lange gesuchte Einführung in PostgreSQL, ein komplexes, aber leistungsstarkes Datenbanksystem. Das Buch führt schrittweise von einfachen zu komplexen Datenbankabfragen und demonstriert die Anwendung von PostgreSQL anhand praktischer Beispiele.
MySQL ist die zurzeit populärste Datenbank im OpenSource-Bereich. Dieses Buch erklärt, wie Sie MySQL als Datenbank im WWW oder im lokalen Netz einsetzen können. Es konzentriert sich dabei besonders auf die wesentlichen Features und Befehle der MySQL-Datenbank und Möglichkeiten für den Datenzugriff.
Helmut Patay
Webprogrammierung mit Perl
An imprint of Pearson Education München • Boston • San Francisco • Harlow, England Don Mills, Ontario • Sydney • Mexico City Madrid • Amsterdam
Die Deutsche Bibliothek – CIP-Einheitsaufnahme Ein Titeldatensatz für diese Publikation ist bei Der Deutschen Bibliothek erhältlich. Die Informationen in diesem Produkt werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Abbildungen und Texten wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig. Fast alle Hardware- und Softwarebezeichnungen, die in diesem Buch erwähnt werden, sind gleichzeitig eingetragene Produktbezeichnungen oder sollten als solche betrachtet werden. Umwelthinweis: Dieses Produkt wurde auf chlorfrei gebleichtem Papier gedruckt. Die Einschrumpffolie – zum Schutz vor Verschmutzung – ist aus umweltverträglichem und recyclingfähigem PE-Material.
5 05
4 04
3
2
1
03
02
ISBN 3-8273-2053-4 © 2002 by Addison-Wesley Verlag, ein Imprint der Pearson Education Deutschland GmbH, Martin-Kollar-Straße 10–12, D-81829 München/Germany Alle Rechte vorbehalten Einbandgestaltung: Christine Rechl, München Titelbild: Liriodendron tulpifera, Tulpenbaum © Karl Blossfeldt Archiv Ann und Jürgen Wilde, Zülpich/ VG Bild-Kunst Bonn, 2002 Lektorat: Frank Eller,
[email protected] Korrektorat: G + U, Technische Dokumentation, Flensburg Herstellung: Monika Weiher,
[email protected] CD-Mastering: Gregor Kopietz,
[email protected] Satz: reemers publishing services gmbh, Krefeld, www.reemers.de Druck und Verarbeitung: Bercker Graphischer Betrieb, Kevelaer Printed in Germany
Inhalt
Über dieses Buch
13
1
Einführung
15
1.1 1.2 1.3 1.4 1.5 1.6 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.7 1.8 1.9 1.10 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.10.6 1.10.7 1.10.8 1.10.9
Was ist Perl? Wie installiert man Perl? Wie installiert man Zusatzmodule für Perl? Die Online Hilfe von Perl Perl-Homepage Wie sieht ein Perl-Skript aus? Was sind Statements? Was sind Direktiven? Die Hashbang-Zeile Exit-Status von Skripts Kommentare in Perl Wie sieht ein Perl-Modul aus? Wie sieht die Skriptumgebung in Perl aus? Wie findet Perl Module? Wie werden Skripts ausgeführt? Was ist ein Skriptargument? Skripts in UNIX ausführen Skripts in Windows ausführen Ausführen kurzer Programme Prüfen von Perl-Skripts Inline-Dokumentation im Quellcode Namenskonventionen für Perl-Skripts und Perl-Module Verzeichnistrenner BEGIN und END
15 16 19 20 21 21 23 23 24 27 27 27 29 30 31 32 32 32 33 34 34 35 36 37
2
Grundlagen
41
2.1 2.2 2.3 2.3.1 2.3.2 2.3.3
Grundbegriffe Datentypen Skalare Behandlung von Zahlen Darstellbarer Zahlenbereich Kennzeichnung von Strings (Quoting)
41 43 43 44 44 45
6
Inhalt
2.3.4 2.3.5 2.3.6 2.4 2.4.1 2.4.2 2.5 2.6 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.6.6 2.6.7 2.7 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.7.10 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.10 2.10.1 2.10.2 2.10.3 2.11 2.11.1 2.11.2
Der skalare Wert »undef« Boolesche Werte Referenzen Listen Arrays Hashes Konstanten Variablen Variablennamen (Identifier) Reservierte Wörter in Perl Geltungsbereich von Variablen Skalare Variablen Array-Variablen Hash-Variablen Referenzvariablen Operatoren Was sind Operatoren? Arithmetische Operatoren String-Operatoren Zuweisungsoperatoren Autoincrement- und Autodecrement-Operatoren Logische Operatoren Vergleichsoperatoren Vergleichsoperatoren für Zahlen Vergleichsoperatoren für Strings Bit-Operatoren Statements Statement if Statement unless Schleifen Statement return Funktionen Funktionsdefinition Funktionsaufruf Datenübergabe an Funktionen Datenübergabe an den Aufrufer einer Funktion Funktionskontext Module Die package-Direktive Die require-Direktive Die use-Direktive Ein-/Ausgabe (File I/O) FileHandles DirHandles
47 48 49 49 49 53 56 58 59 60 61 66 69 76 80 96 96 99 102 103 104 105 108 108 112 117 132 133 136 136 144 146 147 152 154 159 162 164 165 166 168 174 175 191
3
Pattern Matching
197
3.1 3.1.1 3.1.2
Matching-Optionen Option i Option m
205 205 206
Inhalt
7
3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.2 3.2.1 3.3 3.4 3.4.1 3.4.2 3.4.3 3.5 3.5.1 3.5.2 3.5.3 3.5.4
Option s Option ms Option g Speichern von Treffern Die Positionsvariablen @- und @+ Reguläre Ausdrücke Metazeichen Ersetzen von Zeichenketten Erweiterte Ausdrücke (?imsx-imsx) (?:pattern) und (?imsx-imsx:pattern) (?!pattern) Besondere Matchingvariablen $1, $2 ... @-, @+ $` und $' $+
208 211 211 213 219 221 221 241 251 251 253 254 255 255 256 256 259
4
Komplexe Datentypen
261
4.1 4.2 4.3
Mehrdimensionale Arrays Mehrdimensionale Hashes Hash-Arrays
261 266 276
5
Objektorientierte Programmierung
5.1 5.1.1 5.1.2 5.1.3 5.1.4 5.2 5.2.1 5.2.2 5.2.3 5.3
Klassen Klassenattribute und Klassenmethoden Konstruktor Instanzattribute und Instanzmethoden Fehlermeldungen von Klassen Vererbung Die Variable @ISA Overloading Overriding Factories
6
Die File-Module
325
6.1 6.1.1 6.1.2 6.2 6.2.1 6.3
File::Path File::Path::mkpath() File::Path::rmtree() File::Find File::Find::find() File::Copy
325 325 328 328 329 337
7
Anwendungsbeispiele
7.1 7.2 7.3
dos2Unix.pl unix2Dos.pl Hexdump von Dateien
279 282 282 283 289 298 301 302 307 309 321
339 339 345 350
8
Inhalt
7.4 7.4.1 7.4.2 7.5 7.6 7.7 7.8 7.9 7.10
Lesen von Properties-Dateien Prozedurale Implementierung Objektorientierte Implementierung Ausgabe aller Hypertext-Links dirname.pl basename.pl Pfadnamen mit Sonderzeichen finden Automatische Dateien erzeugen Dateibäume verwalten
8
CGI
8.1 8.1.1 8.1.2 8.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3 8.3.1 8.4 8.4.1 8.4.2 8.5 8.5.1 8.5.2 8.5.3
Das HTTP-Protokoll Der Request Die Response Cookies Notwendigkeit von Cookies Arbeitsweise von Cookies Netscape-Cookies Cookies gemäß Internet-Draft-Spezifikation Cookie-Beschränkungen CGI-Umgebung CGI-Kommunikation Templates Templatevariablen Template-Engine Das PERL-Modul CGI.pm Verarbeiten von HTML-Formularen Dynamische HTML-Formulare Arbeiten mit Cookies
9
Das Datenbank-Interface DBI
9.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.1.7 9.1.8 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.2.6
Kurzeinführung in SQL SQL-Clientprogramme Tabellen (Tables) Das INSERT-Statement Das DELETE-Statement Das UPDATE-Statement Das SELECT-Statement Joins Commit und Rollback Arbeiten mit dem Modul DBI.pm Voraussetzungen Abfrage verfügbarer Datenbanktreiber Abfrage verfügbarer Datenquellen Aufbauen der Datenbankverbindung Ausführen von SQL-Statements Benutzung von RaiseError
357 358 359 362 366 367 368 370 400
411 416 418 422 427 427 428 430 434 436 437 441 446 446 452 482 484 486 497
507 510 511 515 526 527 529 530 532 535 536 536 537 537 538 543 557
Inhalt
9
9.3 9.3.1 9.3.2 9.4 9.5
Sessions mit CGI und DBI Beispiel eines Workflows Implementierung einer Sessionverwaltung Rekursive Strukturen mit DBI Mehrsprachige Datensätze
558 559 574 606 669
10
Perl/Apache-Integration
10.1 10.2 10.2.1 10.3 10.4 10.5 10.6 10.7
Standard-CGI mod_perl Installation von mod_perl Apache-Module in Perl Authentifizierungs-Modul Web-Authentifizierung mit DBI Persistente Datenbankverbindungen AuthCookie – ein Beispiel
A
Style Guide
721
B
Vordefinierte Variablen
723
B.1 B.2 B.3 B.4 B.5 B.6 B.7 B.8 B.9 B.10 B.11 B.12 B.13 B.14 B.15 B.16 B.17 B.18 B.19 B.20 B.21
@_ @ARGV %ENV $0 @INC %INC $$, $PID, $PROCESS_ID $@, $EVAL_ERROR $_, $ARG $1, $2 ... @+, @LAST_MATCH_END @-, @LAST_MATCH_START $&, $MATCH $`, $PREMATCH $', $POSTMATCH $|, $OUTPUT_AUTOFLUSH $,, $OFS, $OUTPUT_FIELD_SEPARATOR $\, $ORS, $OUTPUT_RECORD_SEPARATOR $?, $CHILD_ERROR $!, $ERRNO, $OS_ERROR %SIG
C
Vordefinierte Funktionen
C.1 C.2 C.3
abs() atan2() binmode()
673 674 677 677 680 695 704 707 708
723 724 724 725 726 729 729 730 730 731 733 733 733 734 734 735 735 736 737 737 738
743 743 744 744
10 C.4 C.5 C.6 C.7 C.8 C.9 C.10 C.11 C.12 C.13 C.14 C.15 C.16 C.17 C.18 C.19 C.20 C.21 C.22 C.23 C.24 C.25 C.26 C.27 C.28 C.29 C.30 C.31 C.32 C.33 C.34 C.35 C.36 C.37 C.38 C.39 C.40 C.41 C.42 C.43 C.44 C.45 C.46 C.47 C.48
Inhalt bless() chdir() chmod() chomp() chop() chown() chr() cos() crypt() defined() delete() die() each() eof() eval() exists() exit() flock() getc() gmtime() grep() hex() int() join() keys() lc() lcfirst() length() localtime() log() lstat() mkdir() no() oct() ord() pack() pop() pos() print() printf() push() rand() read() ref() require()
746 747 747 748 749 750 750 751 751 752 754 755 757 757 758 762 764 765 771 771 772 774 774 776 777 778 778 778 779 780 780 780 781 782 783 783 785 785 786 788 789 789 790 791 792
Inhalt C.49 C.50 C.51 C.52 C.53 C.54 C.55 C.56 C.57 C.58 C.59 C.60 C.61 C.62 C.63 C.64 C.65 C.66 C.67 C.68 C.69 C.70 C.71 C.72 C.73 C.74 C.75 C.76 C.77 C.78
11 reverse() scalar() seek() select() shift() sin() sleep() sort() splice() split() sprintf() sqrt() srand() stat() substr() system() tell() time() truncate() uc() ucfirst() umask() undef() unlink() unpack() unshift() use() utime() values() wantarray()
Index
794 794 796 798 799 800 801 801 803 805 806 809 809 810 811 812 813 814 814 816 816 817 817 819 819 820 820 823 823 824
827
Über dieses Buch »Webprogrammierung mit Perl« soll Sie in die Lage versetzen, in kurzer Zeit effiziente Perl-Programme für praktische Problemlösungen zu erstellen. Wie der Titel des Buches bereits vermuten lässt, erhält das Thema »Webprogrammierung« eine besondere Gewichtung, ebenso habe ich viel Wert auf praxisorientierte Beispiele gelegt, die man in seinem Programmiererdasein häufig antrifft. Das Buch soll keine vollständige Referenz für die Programmiersprache sein, sondern vielmehr eine praktische Anleitung, wie man Probleme in möglichst kurzer Zeit löst. Es bietet nicht nur Informationen über die Programmiersprache Perl selbst, sondern vielfach auch übergreifendes Wissen über Datenbanken, Webtechnologien und Betriebssysteme, und entstand aus eben diesem Mangel an einer übergreifenden Dokumentation (meist muss man drei oder vier Bücher sowie etliche Manualpages gelesen haben, um eine Webanwendung performant und effizient zu programmieren). Wenn Sie die Konzepte und Beispiele dieses Buches verstanden haben, dann ist es ein Leichtes, mit Hilfe der ausgelieferten Dokumentation von Perl weiter in die Tiefen der Programmiersprache einzudringen. In jedem Fall können Sie sich nach dem erfolgreichen Durcharbeiten der Themengebiete dieses Buches als Perl-Experte bezeichnen, es ist aber auch möglich, dass Sie sich nur Basiswissen aneignen, ohne auf die tiefer gehenden Informationen näher einzugehen. Als Autor dieses Buches liegt mir besonders daran, Unterschiede zwischen den verschiedenen Betriebssystemen Unix und Windows herauszuschälen (als Entwickler neige ich natürlich mehr zu Unix-Systemen, weiß aber sehr wohl um die Erfordernis, auch Windows-Anwender zur Glückseligkeit zu führen). Wie oft schon sind Programmierer nahezu verzweifelt, nur weil sie vergessen haben, beim Dateitransfer den ASCII-Modus (oder auch den Binär-Modus) einzuschalten! Da ich nicht nur in Perl entwickle, sondern auch in JAVA und (sehr selten) in C oder C++, werden Sie in diesem Buch des Öfteren Vergleiche finden, was in welcher Sprache besser oder schlechter implementiert ist.
14
Über dieses Buch
Speziell bei Webanwendungen habe ich die Erfahrung gemacht, dass eine der häufigsten Aufgaben das Parsen (syntaktische beziehungsweise semantische Prüfung von Inhalten) von Formularen ist (so ist zum Beispiel die Prüfung einer E-Mail-Adresse eine echte Herausforderung für Programmierer). Für das Parsen von Text ist eine Unterstützung durch die Programmiersprache eine wirkliche Hilfe, und davon bietet Perl in Form von regulären Ausdrücken (Regular Expressions oder kurz auch »regex« genannt) reichlich. Zu dem Thema »reguläre Ausdrücke« sei betont, dass die Perl-Implementierung mittlerweile als Standard anerkannt wird, man findet sie sowohl in den neuesten JAVA-Versionen als auch im .NET Framework von Microsoft. Ich möchte hier anmerken, dass die Entwickler von JAVA ausdrücklich bekannt haben, reguläre Ausdrücke nicht zu verstehen (als Beispiel diente die Codierung von Strings in URL-Semantik, die in Perl eine einzeilige Anweisung darstellt); dies ist wohl der Grund dafür, dass reguläre Ausdrücke nicht von Anfang an in den Sprachwortschatz von JAVA mit aufgenommen wurden (was in Perl der Fall ist). Wenn man einmal die Leistungsfähigkeit der regulären Ausdrücke erkannt und verstanden hat, wird man ohne sie nicht mehr auskommen (zugegeben, es dauert, bis man mit der verwirrenden Syntax von regulären Ausdrücken vertraut ist).
Zielgruppe Dieses Buch wendet sich im Allgemeinen an alle, die effektiv programmieren wollen (oder müssen), im Speziellen möchte ich durch die Hervorhebung der Themen »Webprogrammierung« und »Datenbankprogrammierung« denen helfen, die Anwendungen im Umfeld des Internets erstellen, dazu gehören sowohl CGI-Skripts (das sind Programme, die vom Endbenutzer über einen Browser aufgerufen werden) als auch administrative Programme, die zur Verwaltung einer Website benötigt werden. Aber auch Einsteiger, die noch nicht über fundierte Kenntnisse des Programmierens verfügen, werden mit diesem Buch in die Lage versetzt, das notwendige Basiswissen für Problemlösungen zu erlernen, ohne sich in den Tiefen der Programmiersprache zu verlieren. Vorausgesetzt werden Kenntnisse in HTML und teilweise in JavaScript. Also: Sind Sie Anfänger: Lesen Sie alles durch, soweit Sie es verstehen. Sind Sie bereits in Perl tätig: Lesen Sie die Teile durch, die Neuigkeiten versprechen, es wird sich sicherlich etwas finden, das wertvoll für Sie ist!
1 Einführung Dieses Kapitel soll Ihnen einen Überblick verschaffen, worüber ich im Weiteren sprechen werde, nämlich »Perl«. Sie bekommen sozusagen ein Starterkit, mit dem Sie in der Lage sind, Perl auf dem Rechner zu installieren und die grundsätzlichen Dinge zu verstehen, die man braucht, um einfache Perl-Skripts auszuführen.
1.1 Was ist Perl? Perl ist die Abkürzung von »Practical Extraction and Report Language«, was frei übersetzt so viel heißt wie »sehr praktische Sprache für alles«. Sie wurde von Larry Wall ursprünglich für die Erstellung von Reports aus Rohdaten entwickelt und hieß anfangs »Pearl«, jedoch hat sich Larry schließlich für Perl entschieden, weil er keine fünf Buchstaben für den Namen der Programmiersprache vergeuden wollte. Zunächst galt Perl nur als gutes Tool für Administratoren, die Textdateien bearbeiten mussten. Viele C-Programmierer rümpften die Nase, wurden sie auf Perl angesprochen; für sie war die Programmiersprache C das einzig Wahre. Auch Java-Programmierer sehen Perl oft von oben herab an, ist doch Java die einzige Sprache, mit der sich beliebig skalierbare Anwendungen erstellen lassen. Manche wiederum finden die Syntax von Perl schlichtweg gewöhnungsbedürftig. Nun, auch ich brauchte eine gewisse Zeit, um mich mit dem Programmierstil von Perl anzufreunden. Sicherlich gibt es Anwendungen, bei denen man besser in Java programmiert. Jedoch hat sich Perl vom reinen Administratoren-Tool zu einer vielseitig einsetzbaren Programmiersprache gemausert, speziell im Umfeld der Webprogrammierung ist sie heute die Nummer eins. Nicht zu unterschätzen ist die Zeitersparnis bei der Implementierung von Anwendungen in Perl gegenüber anderen Programmiersprachen. Unschlagbar ist Perl in punkto Textbearbeitung, sind doch reguläre Ausdrücke vollständig im Sprachwortschatz von Perl integriert. Jeder, der schon einmal Formulardaten verarbeitet hat, weiß ein Loblied darauf zu singen.
16
1
Einführung
Oft wird bei Webanwendungen die Performance von CGI-Skripts mit Perl gegenüber Servlets in Java bemängelt, doch wir werden in diesem Buch sehen, dass diese Behauptung nicht richtig ist. Wenn Sie die Antwortgeschwindigkeit von CGI-Anwendungen mit »mod_perl« gesehen haben, werden Sie begeistert sein! Für mich stellt sich die Frage, welche Programmiersprache für welches Problem verwendet werden soll, nur noch sehr selten, da die Antwort fast immer Perl ist. So entwickelte ich vor einiger Zeit zum Beispiel ein Testprogramm, das mehrere LDAP-Server verschiedener Hersteller vergleichen sollte. Für die Erzeugung der Testdaten mussten Hashtabellen mit sehr vielen eindeutigen Schlüsseln erzeugt werden. Zunächst implementierte ich das Programm in Java, musste jedoch sehr schnell zu Perl übergehen, da die »Virtual Machine« (das ist sozusagen der Interpreter in Java) bereits nach etwa 60000 Tabelleneinträgen den Geist aufgab, sprich abstürzte, während für Perl auch die stattliche Anzahl von einer Million Einträgen kein Problem darstellte. Ein wesentliches Merkmal von Perl ist die Plattform-Unabhängigkeit, da Perl-Skripts in der Regel nicht als direkt vom Prozessor ausführbare Binärdateien vorliegen, sondern im Quelltext (englisch Sourcecode), der von einem Interpreter zeilenweise gelesen und nach einer Übersetzung in Maschinencode von diesem Interpreter ausgeführt wird. Dies hat Vor- und Nachteile. Der größte Vorteil ist, dass solche Skripts ohne Änderungen auf unterschiedlichen Rechnerarchitekturen laufen (obwohl man diesen Vorteil natürlich durch spezielle Programmierung wieder zunichte machen kann, indem man z.B. Betriebssystemkommandos vom Skript aus startet). Der größte Nachteil besteht darin, dass man keine Perl-Skripts ohne eine Installation des Perl-Interpreters ausführen kann (das gilt im übrigen nicht nur für Perl, sondern für alle Skript-basierten Sprachen, auch für Java). In der heutigen Zeit kann man jedoch davon ausgehen, dass auf Unix-Rechnern Perl fast immer installiert ist, und die Installation von Perl unter Windows ist ein Kinderspiel. Perl unterstützt sowohl prozedurale als auch objektorientierte Programmierung. Perl ist modulbasiert, d.h. der Funktionsumfang einer Perl Distribution lässt sich durch Einbinden weiterer Perl Module beliebig erweitern. Perl ist kostenlos, ebenso alle im Internet verfügbaren Zusatzmodule.
1.2 Wie installiert man Perl? Perl ist sowohl im Quellcode (Source Distribution) als auch für alle gängigen Betriebssysteme und Rechnervarianten als Binärpaket frei im Internet erhältlich. Auf LinuxSystemen ist Perl bereits fertig installiert mit in der Linux-Distribution enthalten.
Wie installiert man Perl?
17
Meist genügt es, die vorkompilierten Binärpakete von Perl zu installieren (unter Windows als selbstinstallierende exe-Datei, unter UNIX als RPM-, PKG-Datei oder als tarArchiv). In der Perl-Distribution enthalten sind Entwicklungs- sowie Laufzeitumgebung (das ist bei Perl dasselbe) sowie eine umfangreiche Dokumentation. Die derzeitig aktuellste Version von Perl ist 5.6.1. Vor allem für Windows-Anwender interessant: Auf der Website von ActiveSTATE findet man besonders zusammengestellte Binärdistributionen, deren Funktionsumfang über die Standardzusammenstellung von Perl hinausgeht. Es sind dort bereits Zusatzmodule eingebunden, die man sich normalerweise aus dem Internet besorgen müsste. Eine Übersicht der von ActiveSTATE frei erhältlichen binären Perl-Distributionen erhält man über folgenden URI: http://www.activestate.com/Products/Download/Download.plex?id=ActivePerl Man kann natürlich auch über den Menüpunkt DOWNLOADS der Perl-Homepage http://www.perl.com sowohl Binär- als auch Source-Distributionen herunterladen. Hinweis für Windows Benutzer Unter NT und Windows 98 benötigt man mindestens die Version 1.1+ (NT) bzw. 2.0+ (Windows 98) des Windows-Installationsprogramms (englisch »Windows Installer« oder auch »MSI Installer« genannt), bevor man die Perl-Distribution von ActiveSTATE installieren kann (bei Windows 2000 ist die benötigte Installer-Version bereits Bestandteil des Betriebssystems). Den Installer kann man über einen Hypertextlink ebenfalls von ActiveSTATE herunterladen und installieren (danach muss das System neu gebootet werden, wer hätte etwas anderes erwartet). Es ist zwar auch möglich, die Perl-Distribution als so genanntes AS-Package (das ist eine zipDatei, die man auspacken muss) zu installieren, diese Version enthält jedoch keinen Uninstaller. Merken Sie sich unbedingt das Verzeichnis auf der Festplatte, in dem Perl installiert wird. Nachdem die Perl-Distribution installiert ist, sollte man überprüfen, ob der Perl-Interpreter im Suchpfad der Kommandozeilen-Shell enthalten ist. Unter UNIX ist das die Bourne-Shell, die Kourne-Shell, die bash (frei verfügbare Shell) oder eine ähnliche Shell, unter Windows ist es die DOS-Box, die als Binärprogramm cmd.exe im Systempfad unter Windows abgelegt ist. Die Programmdatei des Perl-Interpreters liegt im Unterverzeichnis bin der Perl-Distribution und heißt unter UNIX einfach perl, unter Windows ist es die Datei perl.exe.
18
1
Einführung
Am einfachsten ruft man zu diesem Zweck den Perl-Interpreter mit dem Schalter -v auf, damit gibt das Programm nur einen informativen Text einschließlich der Version aus und beendet sich anschließend. Hier ein Beispiel auf meinem Windows-Rechner: D:\>perl -v This is perl, v5.6.1 built for MSWin32-x86-multi-thread (with 1 registered patch, see perl -V for more detail) Copyright 1987-2001, Larry Wall Binary build 630 provided by ActiveState Tool Corp. http://www.ActiveState.com Built 20:29:41 Oct 31 2001
Perl may be copied only under the terms of either the Artistic License or the GNU General Public License, which may be found in the Perl 5 source kit. Complete documentation for Perl, including FAQ lists, should be found on this system using `man perl' or `perldoc perl'. If you have access to the Internet, point your browser at http://www.perl.com/, the Perl Home Page.
D:\>
Wenn das System das Perl-Programm nicht finden kann und mit einer entsprechenden Fehlermeldung reagiert, dann sollte man die Umgebungsvariable PATH so erweitern, dass sie auch das Unterverzeichnis bin der Perl-Distribution enthält. In Windows-Systemen ist das Installationsverzeichnis meist C:\Perl oder C:\Programme\Perl: set PATH=%PATH%;C:\Perl\bin
Wenn die Umgebungsvariable PATH nur in der DOS-Box geändert wird, dann geht die Information nach dem Schließen der DOS-Box verloren, auch in anderen geöffneten DOS-Boxen ist sie nicht wirksam. Über die Systemeinstellungen im Menü »Umgebungsvariablen« kann man die Zuordnung permanent pro Benutzer oder auch für alle Windows Benutzer permanent speichern. In UNIX wird die Perl-Distribution oft nicht in einem einzelnen Wurzelverzeichnis installiert (das Wurzelverzeichnis wird im Kreise aller Programmierer auch »Root Directory« genannt), sondern an mehreren Orten. Die Binärprogramme landen meist im Verzeichnis /usr/local/bin oder /usr/bin, die Bibliotheken von Perl findet man häufig in /usr/local/lib/perlx oder /usr/lib/perlx, wobei das »x« für die Hauptversion der PerlDistribution steht (z.B. 5).
Wie installiert man Zusatzmodule für Perl?
19
Ausnahme: Distributionen, die sich unter /opt installieren, besitzen oft ein gemeinsames Wurzelverzeichnis. Um in UNIX das Unterverzeichnis bin der Perl-Distribution mit in den Suchpfad aufzunehmen, gibt es mehrere Möglichkeiten, normalerweise erweitert man jedoch die Umgebungsvariable PATH in der Datei .profile (Bourne-Shell, Kourne-Shell), .bashrc (bash) oder ähnlichen Startup-Dateien. Beispiel für Bourne- und Kourne-Shell: PATH=$PATH:/usr/local/bin; export PATH
Beispiel für bash: export PATH=$PATH:/usr/local/bin
Beispiel für csh: set PATH=$PATH:/usr/local/bin
1.3 Wie installiert man Zusatzmodule für Perl? Im Internet findet man tonnenweise Perl-Module, mit denen sich die Funktionalität der Basisinstallation beliebig erweitern lässt. Wollen Sie Zeiten mit einer Auflösung im Mikrosekundenbereich messen? Kein Problem, laden Sie sich das Modul Time::HiRes herunter. Brauchen Sie ein Tool, um effizient mit Datum einschließlich Zeitzone zu arbeiten? Holen Sie sich Date::DateManip. Ich könnte jetzt auf weiteren 100 Seiten mit der Liste von hilfreichen Modulen fortfahren. Die zentrale Sammelstelle für Perl-Zusatzmodule wird »CPAN« genannt. CPAN ist die Abkürzung von »Comprehensive Perl Archive Network« und bedeutet frei übersetzt »Alles Denkbare und Undenkbare für Perl«. Um einen Eindruck vom Umfang dieses Archivs zu gewinnen, verfolgen Sie doch einmal den CPAN-Hypertextlink auf der Homepage von Perl. Sie werden sich schier erschlagen fühlen ob der Unmenge an nützlichen Erweiterungen. Für die unterschiedlichen Varianten der Betriebssysteme stehen sowohl die automatische als auch die manuelle Installation von Zusatzmodulen zur Verfügung. Die automatische Installation hat den unschlagbaren Vorteil, dass andere Module, die wiederum vom zu installierenden Perl-Modul benötigt werden, automatisch mitinstalliert werden, man muss sich also nicht um zeitaufwändige und zermürbende Details kümmern. In diesem Buch möchte ich für Windows und UNIX jeweils nur die automatische Methode kurz erläutern, weitere Möglichkeiten der Installation sowie Detailinformationen finden Sie im Buch »Perl-Module«, das im gleichen Verlag erschienen ist.
20
1
Einführung
Windows-Installation von Zusatzmodulen: Wenn Sie eine Perl-Distribution von ActiveSTATE besitzen, können Sie Zusatzmodule sozusagen im Quick-and-Easy-Verfahren über ein Menü installieren, da im Unterverzeichnis bin der Perl-Distribution das Skript ppm.bat enthalten ist, das nahezu alle Arbeiten für Sie übernimmt. Informationen, wie das Skript zu handhaben ist, finden Sie ebenfalls im Buch »Perl Module«. Sie können in der Oberfläche des Skripts mit dem Befehl help aber auch eine Online Hilfe bekommen. An dieser Stelle sei nur angemerkt, dass Sie einen Proxyserver angeben müssen, falls Ihr Rechner hinter einer Firewall liegt. UNIX-Installation von Zusatzmodulen: In UNIX können Sie das in der Standard-Distribution enthaltene Perl-Modul CPAN benutzen, um weitere Zusatzmodule zu installieren. Geben Sie in der Shell das Kommando perl -MCPAN -e Shell
ein. Daraufhin werden Sie beim allerersten Aufruf einige Dinge gefragt, die meist mit dem Defaultwert beantwortet werden können. Die Anwendung führt Sie ähnlich wie das Skript ppm.bat in Windows durch die Installation. Weitere Informationen erhalten Sie in der Manualpage des Moduls CPAN. Ich werde weiter unten noch erklären, wie Sie die Perl-Dokumentation lesen.
1.4 Die Online Hilfe von Perl In der Perl-Distribution enthalten ist das Programm perldoc, mit dem man eine Online Hilfe über Perl selbst sowie über eingebaute Variablen, Funktionen, reguläre Ausdrücke, über objektorientierte Programmierung in Perl oder auch über installierte Perl Module erhält. Eine Beschreibung des Programms perldoc selbst kann man mit folgendem Aufruf in der Kommandozeile erhalten: perldoc perldoc
Hier weitere Beispiele für die Benutzung von perldoc: # Übersicht aller Hilfethemen perldoc perltoc # Hilfe für die integrierte Funktion print() perldoc -f print # Hilfe für das Perl-Modul CGI perldoc CGI
Perl-Homepage
21
# Hilfe für das Perl-Module IO::Handle perldoc IO::Handle # Hilfe für die vordefinierten Variablen in Perl perldoc perlvar # Hilfe für die integrierte Funktionen (Übersicht) perldoc perlfunc # Hilfe für die Operatoren perldoc perlop # Hilfe über Pattern Matching perldoc perlre # Hilfe über pod (Plain Old Documentation) perldoc perlpod
Das Programm perldoc besitzt keine grafische Oberfläche und wird aus der Shell über die Kommandozeile aufgerufen. Allen, die eine grafische Oberfläche bevorzugen, sei das Unterverzeichnis Docs der Perl-Distribution ans Herz gelegt. Dort sind alle Dateien der Dokumentation entweder als HTML-Seiten abgelegt, oder, falls man die Perl-Distribution von ActiveSTATE installiert hat, als kompilierte Hilfedatei, die man unter Windows über den Dateimanager mit einem Doppelklick der Maus direkt aufrufen kann.
1.5 Perl-Homepage Die offizielle Homepage von Perl ist unter http://www.perl.com im Internet zu finden. Von dort aus gelangt man über den Browser zu allen wichtigen Themen und anderen Websites, die sich mit dem Thema »Perl« beschäftigen. Unter anderem findet man hier die Anlaufstelle, um eine Perl-Distribution bzw. Zusatzmodule von CPAN herunterzuladen, die Online-Dokumentation zu Perl zu lesen, die neuesten Nachrichten zu erhalten usw. usw.
1.6 Wie sieht ein Perl-Skript aus? Bevor wir diese Frage beantworten können, müssen wir erst einmal wissen, was mit dem Begriff »Skript« (englisch: »Script«) gemeint ist. Einfach ausgedrückt ist ein Skript etwas Ähnliches wie ein Programm, das ausgeführt werden kann, indem man es zum Beispiel über die Kommandozeile der Shell aufruft. Im Gegensatz zum Programm, das Maschinencode in binärer Form enthält und somit direkt ohne Umschweife von der CPU eines Rechners ausgeführt werden kann, steht in einem Skript ASCII-Text, den man sich am Bildschirm ausgeben lassen oder mit einem Editor-Programm bearbeiten kann.
22
1
Einführung
Der ASCII Text stellt den Quellcode für einen Interpreter dar, welcher den Inhalt der Skriptdatei Zeichen für Zeichen liest, die Kommandos des Quellcodes der Reihe nach in Maschinencode umwandelt und den erzeugten binären Code dann von der CPU des Rechners ausführen lässt. Halten wir also fest: Ein Skript kann nicht direkt von der CPU ausgeführt werden, sondern benötigt immer ein Programm, den so genannten Interpreter, der die Anweisungen des Skripts in Maschinencode umwandeln muss, bevor diese unter der Kontrolle des Interpreters auf der CPU ablaufen können. Wer bereits Erfahrung mit den Programmiersprachen C oder C++ hat, kennt den Begriff »Compiler«. Dieser führt zwar die Übersetzung des gesamten Quellcodes in einem einzigen Schritt durch, speichert den entstandenen Maschinencode aber nur in einer binären Programmdatei ab, ohne das Programm anschließend auszuführen. Wie wir sehen, ist ein Interpreter nichts anderes als ein Compiler, der die Anweisungen im Skript nacheinander ausführt. Beides hat Vorteile und Nachteile, die ich an dieser Stelle nicht weiter erörtern möchte, da alle modernen Programmiersprachen wie Perl Skriptsprachen sind. Jeder, der in der DOS-Box oder in einer UNIX-Shell einmal das Kommando dir oder ls aufgerufen hat, ist im Prinzip ein kleiner Skriptprogrammierer. Die DOS-Box oder allgemein die Shell ist nämlich nichts anderes als ein so genannter KommandozeilenInterpreter, der auf Eingaben von der Tastatur wartet, jede Zeile als Anweisung auffasst, diese in Maschinencode übersetzt und ausführt. So, da wir nun wissen, was wir uns unter einem Skript vorzustellen haben, wollen wir uns nun einmal die grundsätzliche Struktur von Skripts ansehen.
Skriptdatei Hauptprogramm Anweisung 1 Anweisung 2 Anweisung 3 ... Anweisung n exit-Anweisung
Abbildung 1.1: Struktur von Perl-Skripts
Wie sieht ein Perl-Skript aus?
23
Wenn ich im Weiteren von Programm spreche, dann meine ich nicht den oben beschriebenen, binären Maschinencode, sondern den Quellcode von Skripts, der landläufig ganz einfach als Programm bezeichnet wird. Jede Skriptdatei besteht mindestens aus einem Hauptprogramm, in dem Anweisung für Anweisung der Reihe nach abgearbeitet wird. Das Hauptprogramm nennt man auch »main«.
1.6.1 Was sind Statements? Wieder ein kleiner Exkurs in Englisch: Der Begriff »Anweisung« heißt im Englischen »Statement«, was wir am Ende dieses Buches hoffentlich auswendig können. In Perl wird jedes Statement, wie fast in allen Programmiersprachen, mit einem Semikolon, landläufig auch als Strichpunkt bekannt, abgeschlossen. Die letzte Anweisung eines Hauptprogramms ist die »exit«-Anweisung, mit der das Programm explizit beendet wird. Sie kann auch entfallen, dann wird das Programm nach Abarbeiten der letzten Anweisung automatisch beendet. Meine Empfehlung: Schreiben Sie Ihren Quellcode immer so ausführlich wie möglich, das hilft ungemein, den Code später zu verstehen. Die exit-Anweisung sollte also nicht fehlen. Die exit-Anweisung kann jedoch auch mehrfach an beliebigen Stellen des Hauptprogramms stehen, in jedem Fall gilt aber, dass der Interpreter das Programm beendet, ohne dass weitere Anweisungen ausgeführt werden.
1.6.2 Was sind Direktiven? Direktiven sind nichts anderes als Statements, jedoch werden sie nicht vom Interpreter ausgeführt, sondern dienen der Kommunikation zwischen Skript und Interpreter. Mit Direktiven lädt man z.B. andere Perl-Module in sein Programm oder stellt bestimmte Eigenschaften des Interpreters ein. Die wohl wichtigste Direktive ist use. Jedes Perl Skript sollte am Beginn des Quellcodes die Direktive use strict;
enthalten. Damit stellt man den Interpreter auf »stur«, wenn man flapsigen Code schreibt. Perl verweigert dann nämlich den Dienst mit entsprechenden Fehlermeldungen. Die oben dargestellte Direktive macht im Prinzip nichts anderes, als das Perl-Modul strict.pm zu laden, wie wir später noch sehen werden. Das Modul gehört jedoch zu einer speziellen Modulgruppe, die man »Pseudomodule« nennt, weil es auch interne Einstellungen im Interpreter vornimmt.
24
1
Einführung
Ich komme später noch einmal auf das Thema zu sprechen, hier will ich Ihnen den Unterschied zwischen Direktive und Statement nur kurz am folgenden Quellcode erläutern: Die Zeile 03 des Beispiels enthält eine Direktive, während in Zeile 05 ein Statement steht. Sehen wir uns doch gleich ein sehr einfaches Perl-Skript an, das den oben gezeigten Aufbau hat (die Nummern am Anfang jeder Zeile gehören natürlich nicht zum Programmcode, sondern dienen lediglich der Nummerierung der einzelnen Zeilen des Skripts): 01 02 03 04 05 06 07
#!/usr/bin/perl -w use strict; print( "Alles klar oder?\n" ); exit( 0 );
Das gesamte Skript besteht aus einem Hauptprogramm mit insgesamt 3 Anweisungen (englisch: »Statements«). Es gibt nur den Text »Alles klar oder?« und einen Zeilenvorschub am Bildschirm aus. Sie verstehen den Code nicht? Kein Problem, mit ein wenig Geduld werden wir bald so weit sein. Nur die allererste Zeile möchte ich hier genauer erklären:
1.6.3 Die Hashbang-Zeile Was bedeutet »Hashbang«? Nun, nichts anderes, als dass die Zeile mit einem Hashzeichen gefolgt von einem Ausrufezeichen beginnt. Sie wird bei nahezu allen Skripts, die von einer Shell aus aufgerufen werden, benutzt, um dem Kommandozeilen-Interpreter mitzuteilen, welches Programm für die Ausführung des Skripts benutzt werden soll. Wie wir ja wissen, können Skripts im Gegensatz zu Programmen nicht direkt ausgeführt werden, sondern benötigen immer ein Interpreter-Programm. Nach dem »Hashbang« folgt der Dateiname des Interpreters (in unserem Fall ist der Interpreter unter /usr/bin/perl zu finden, das sieht mir ganz nach UNIX aus). Zu guter Letzt kann man in der ersten Zeile noch Argumente angeben, die dem Interpreter von der Shell übergeben werden. In unserem Beispiel lautet das übergebene Argument -w: Es schaltet den Perl-Interpreter in den Warnungsmodus, er gibt dann verschiedene Meldungen aus, wenn Laufzeitfehler auftreten. Hier ein kleines Beispiel, das den Inhalt der Variablen $v ausgibt. Aus der Kommandozeile einer DOS-Box heraus wird der Perl-Interpreter aufgerufen. Anschließend habe ich ein paar Kommandos eingegeben, die ich am Ende mit der Tastenkombination (Strg) + (Z) abschließe. Da ich in der Variablen $v nichts abgelegt habe, wird auch nichts ausgegeben:
Wie sieht ein Perl-Skript aus?
25
D:\temp>perl use strict; my $a; print( $v ); ^Z D:\temp>
Ich habe beim Aufruf des Perl-Interpreters den Schalter -w weggelassen, dies will ich nun nachholen: D:\temp>perl -w use strict; my $a; print( $v ); ^Z Use of uninitialized value in print at - line 3. D:\temp>
Nun verhält sich der Interpreter anders als vorher und gibt eine Warnung aus, dass die Variable $v nicht initialisiert ist. Was das genau bedeutet, werden wir später noch sehen, an dieser Stelle sei nur angemerkt, dass ich Sie höflich bitte, diesen Schalter IMMER anzugeben. Glauben Sie mir, er ist mehr als nur nützlich. Die Hashbang-Zeile muss übrigens immer die erste Zeile im Quellcode sein, es darf also auch keine Leerzeile davor stehen. In Perl-Modulen, die kein Hauptprogramm enthalten, kann die Hashbang-Zeile entfallen. Wie Sie sehen, sind auch Leerzeilen im Skript vorhanden. Das ist durchaus gewollt, um die einzelnen logischen Einheiten des Programms optisch voneinander zu trennen, und dient der besseren Lesbarkeit des Quellcodes. Ich werde mich in Anhang A noch ausführlicher zu diesem Thema in Form eines Styleguides für guten Programmierstil äußern. Manche Programmierer denken immer noch, Platz im Editorfenster sei Mangelware, und schreiben den Quellcode vielleicht so: 01 #!/usr/bin/perl -w 02 use strict;print "Alles klar oder?\n";exit 0;
Wie wir deutlich sehen, ist das Skript nun wesentlich kürzer geworden. Aber ich denke, schon bei diesem sehr einfachen Beispiel tut man sich schwer, auf Anhieb zu erkennen, was das Programm eigentlich macht. Also: Weniger ist nicht immer mehr. Häufig werden bestimmte Sequenzen von Anweisungen an mehreren Stellen des Hauptprogramms benötigt. Diese Sequenzen lagert man in so genannte Unterfunktionen oder auch kurz Funktionen (englisch: »functions« oder »subroutines«) aus. Damit
26
1
Einführung
vermeidet man, dass der betreffende Programmcode in Duplikaten mehrfach aufgeschrieben werden muss. Funktionen werden auch verwendet, um den Programmcode übersichtlicher zu gestalten. Im Gegensatz zum Hauptprogramm wird eine Funktion nicht mit einer exit-Anweisung, sondern mit einer return-Anweisung beendet. Das folgende Bild demonstriert die erweiterte Struktur eines Perl-Skripts:
Skriptdatei Hauptprogramm Anweisungen exit-Anweisung
Funktion 1 Anweisung 1 Anweisung 2 ... return-Anweisung Es können beliebig viele Funktionsblöcke folgen
Abbildung 1.2: Erweiterte Struktur von Perl-Skripts
Als Anschauungsmaterial noch einmal das erste Skript, Diesmal erfolgt die Ausgabe des Textes nicht im Hauptprogramm, sondern in einer Funktion, die vom Hauptprogramm aufgerufen wird: 01 02 03 04 05 06 07 08 09 10 11
#!/usr/bin/perl -w use strict; printItOut(); exit( 0 ); sub printItOut { print( "Alles klar oder?\n" ); }
Wie sieht ein Perl-Modul aus?
27
Auch bei diesem Beispiel gilt: Verstehen werden wir den Code erst später. Einen wichtigen Punkt möchte ich Ihnen aber schon hier nicht vorenthalten:
1.6.4 Exit-Status von Skripts Jedes Programm (und damit auch jedes Skript) gibt nach der Beendigung seines Hauptprogramms einen numerischen Status an das Betriebssystem zurück. Damit lässt sich überprüfen, ob ein Fehler aufgetreten ist oder nicht. Grundsätzlich gilt: Im Erfolgsfall gibt ein Skript immer den Status 0 zurück, bei einem Fehler hat sich in UNIX der Status 1 eingebürgert. Wie man im Skript einen numerischen Status zurückgibt, sehen wir im obigen Beispiel in Zeile 07.
1.6.5 Kommentare in Perl Bevor Sie sich in den folgenden Zeilen den Kopf darüber zerbrechen, was Zeilen mit einem führenden Hashzeichen # bedeuten: Das Hashzeichen leitet einen Kommentar ein, der Rest der Zeile wird also nicht vom Perl-Interpreter beachtet. Man kann in Perl-Skripts an fast allen Stellen des Codes Kommentare einfließen lassen. Ein Perl-Kommentar beginnt mit dem Hashzeichen # und erstreckt sich bis zum Ende der Zeile. Perl unterstützt keine mehrzeiligen Kommentare. Beispiel: # Das ist ein Kommentar als eigenständige Zeile print( "Hallo" ); # Kommentar am Ende einer Zeile
Als Regel des guten Programmierstils gilt: Man sollte seinem Mitteilungsbedürfnis ruhig freien Lauf gewähren und so viele Kommentare wie möglich im Quellcode einfließen lassen. Nebenbei bemerkt unterstützt Perl zusätzlich weitere Arten der Dokumentation von Quellcode. Wir werden weiter unten noch näher darauf eingehen.
1.7 Wie sieht ein Perl-Modul aus? Ich habe eingangs immer wieder den Begriff »Zusatzmodule von Perl« erwähnt. Bisher wissen Sie nur, dass man darunter etwas Nützliches versteht, nun wollen wir uns ansehen, was sich in Wirklichkeit dahinter verbirgt: Ein Perl-Modul ist im Wesentlichen nichts anderes als ein Perl-Skript ohne Hauptprogramm. Sehen wir es uns in einer Abbildung an:
28
1
Einführung
Abbildung 1.3: Struktur von Perl-Modulen
Das Auffälligste gegenüber Perl-Skripts ist, dass Perl-Module kein Hauptprogramm enthalten. Module dienen der Erweiterung von bestehender Funktionalität und werden von Perl-Skripts (oder auch von anderen Perl-Modulen) benutzt, ähnlich wie dies bei Funktionen der Fall ist. Perl-Module erhalten über die package-Direktive einen Namen (siehe Bild). Direktiven sind, wie weiter oben bereits erwähnt, spezielle Anweisungen, die nicht als Programmcode ausgeführt werden, sondern für besondere Interaktionen mit dem Interpreter verwendet werden. Wie wir im Bild sehen, habe ich dem Modul den Namen »MyPackage« gegeben. Der Modulname, den man mit der package-Direktive vergibt, ist in der Regel mit dem Dateinamen verknüpft, in welcher der Programmcode des Moduls abgespeichert wird. Der Dateiname besteht aus demselben String wie der Modulnname, an den die Endung .pm angehängt wird. Unser Beispielmodul MyPackage sollte also den Dateinamen MyPackage.pm haben. Sie sollten Modulnamen (und damit auch die entsprechenden Dateinamen) immer mit einem Großbuchstaben beginnen, auch unter Windows. Eine wichtige Eigenart von Perl-Modulen ist das letzte Element im Bild, nämlich eine Zeile mit dem Statement 1;. Das bedeutet nichts anderes, als dass das Modul an den aufrufenden Programmcode den Status 1 zurückliefert. Ohne diese Zeile läuft gar nichts, der Interpreter verweigert schlicht den Dienst mit einer Fehlermeldung, wenn
Wie sieht die Skriptumgebung in Perl aus?
29
man versucht, ein Modul zu laden, das diese Zeile nicht enthält. Nun gut, haken wir es unter dem Thema »Ist so und nicht zu ändern« ab und schreiben unsere Module in etwa nach folgendem Schema: package aNewPackage; use strict; # Programmcode ... 1;
Wie man wegen des Strichpunktes nach der 1 bereits vermuten wird, handelt es sich bei der letzten Zeile um nichts anderes als um ein Statement, das aus einer Konstanten besteht. Damit Sie sehen, wie der Quellcode wirklich aussieht, habe ich in diesem Codebeispiel auf die Zeilennummerierung verzichtet. Die Nummerierung von Zeilen in diesem Buch dient wie gesagt nur dem Zweck, Ihnen erklären zu können, in welcher Zeile was passiert.
1.8 Wie sieht die Skriptumgebung in Perl aus? Das folgende Schaubild zeigt die Vernetzung von Skript, Funktionen der StandardDistribution und Zusatzmodulen sowie Funktionen in selbst implementierten Modulen (im Beispiel werden die Module »Util« und »Log« verwendet).
Abbildung 1.4: Typische Skriptumgebung in Perl
30
1
Einführung
Wie wir sehen, kann man aus dem Hauptprogramm (und natürlich auch aus Funktionen des Skripts) heraus nicht nur auf Funktionen innerhalb der Skriptdatei und der Standardmodule zugreifen, sondern auch auf die Funktionen der Zusatzmodule (die in einem Standardverzeichnis der Perl-Distribution installiert sind), sowie auf beliebige, selbst implementierte oder auch kopierte Modulfunktionen, die nicht Bestandteil der Perl-Distribution sind und sich irgendwo auf der Festplatte befinden können. Wir werden weiter unten noch sehen, was man tun muss, damit der Perl-Interpreter Module findet, wenn sie von einem Skript geladen werden. An dieser Stelle sei erwähnt, dass sich Zusatzmodule und selbst implementierte Module nur dadurch unterscheiden, dass Zusatzmodule grundsätzlich in einem ganz bestimmten Unterverzeichnis der Perl-Distribution installiert und somit Bestandteil der Perl-Installation werden, während Module der letzteren Art normalerweise an beliebiger Stelle auf der Festplatte gespeichert und damit nicht Bestandteil der PerlInstallation sind. Über Module gibt es noch viele interessante Dinge zu sagen (was ich auch tun werde), aber an dieser Stelle möchte ich Sie damit nicht überfordern, immerhin wissen wir ja noch nicht einmal, was die einzelnen Anweisungen in den bisherigen Quellcode-Beispielen bedeuten.
1.9 Wie findet Perl Module? Alle Module der Standard-Distribution von Perl werden im Unterverzeichnis lib abgelegt, während Zusatzmodule im Unterverzeichnis site landen. In diesen Verzeichnissen, sowie im aktuellen Verzeichnis der Shell, in der ein Skript ausgeführt wird, sucht der Perl Interpreter nach Modulen, wenn sie vom Skript mit der Direktive use geladen werden. Ein kleines Beispielskript soll dies erläutern (Windows-Installation): #!D:/Perl/bin/perl.exe -w print( join( ", ", @INC ), "\n" );
Wie wir an der Hashbang-Zeile sehen, habe ich einen Windows-Rechner benutzt, auf dem Perl unter D:\Perl installiert ist. Das print()-Statement gibt alle Verzeichnisse aus, in denen vom Interpreter nach Modulen gesucht wird. Was die Variable @INC bedeutet, werden wir in Anhang B sehen. Wenn das Skript ausgeführt wird, erhalten wir als Ausgabe: D:/Perl/lib, D:/Perl/site/lib, .
Wie findet Perl Module?
31
Wie wir sehen, sucht der Interpreter nur in diesen Verzeichnissen nach Modulen. Oft hat man selbst entwickelte Module irgendwo auf der Festplatte, die man in seinen Skripts benutzen möchte. Damit Perl diese Module finden kann, muss man die Liste der Suchpfade erweitern. Am einfachsten geht das mit der Umgebungsvariable PERLLIB. Auch hierzu ein Beispiel. Wir erweitern die Liste um das Verzeichnis D:\myModules, indem wir vor dem Aufruf des Skripts die Umgebungsvariable PERLLIB setzen (Hinweis für UNIX: Das Setzen der Umgebungsvariable funktioniert genauso wie weiter oben für die Variable PATH gezeigt): D:\>set PERLLIB=D:\myModules
Wenn wir nun das Skript noch einmal aufrufen, erhalten wir: D:\myModules, D:/Perl/lib, D:/Perl/site/lib, .
Die Liste der Suchpfade ist nun um unser eigenes Verzeichnis erweitert (die Betonung liegt auf »erweitert«, da die Liste in jedem Fall die Standard-Suchpfade enthält). Zu erwähnen ist noch, dass der Interpreter bei der Suche nach Modulen die Liste der Reihe nach durchläuft. In unserem Beispiel würde also als Erstes im Verzeichnis D:\myModules gesucht werden.
1.10 Wie werden Skripts ausgeführt? Bis jetzt haben wir zwar gelernt, wie Skripts aussehen, wissen aber noch nicht, wie man sie ausführt. Das will ich jetzt nachholen: Am einfachsten führt man ein Skript aus, indem man in der Shell den Perl-Interpreter aufruft und ihm als Argument in der Kommandozeile den Dateipfad des Skripts angibt (das Verzeichnis, in dem das binäre Programm perl bzw. perl.exe steht, muss in der Umgebungsvariable PATH enthalten sein, was aber bei einer Standard Installation von Perl zumindest unter Windows und Linux der Fall) ist: D:\>perl -w C:/tst.pl
Das Beispiel zeigt den Aufruf von Perl unter Windows mit dem Skript C:\tst.pl als Argument. Wie wir sehen, kann (und sollte) statt des Backslashs »\« als Verzeichnistrenner der wesentlich schönere Trenner Slash »/« verwendet werden. Der Schalter -w stellt den Interpreter auf »bei nicht initialisierten Daten bitte Warnungen ausgeben« ein.
32
1
Einführung
Ein Skript kann aber auch direkt über die Kommandozeile ausgeführt werden. Der Perl Interpreter wird dann implizit von der Shell gestartet (die Hashbang Zeile gibt der Shell den Pfad zum Interpreter an).
1.10.1 Was ist ein Skriptargument? Wenn man ein Skript von der Shell aus aufruft, kann man wie bei Binärprogrammen Aufrufparameter angeben. Im Programmcode des Skripts kann man die Argumente dann über die vordefinierte Variable @ARGV auslesen. Was diese Variable bedeutet, werden wir bald erfahren. Sie wird außerdem in Anhang B erläutert.
1.10.2 Skripts in UNIX ausführen Bevor in UNIX ein Programm oder ein Skript ausgeführt werden kann, muss man die Datei mit dem »chmod«-Kommando als ausführbar kennzeichnen. Nehmen wir eine Standard-Installation von Perl unter Linux an. Dort ist der Perl-Interpreter in der Regel unter /usr/bin/perl zu finden. Wenn wir nun ein Skript mit dem Pfad /tmp/myScript.pl erstellt haben, dann müssen wir zunächst folgendes Kommando aufrufen: chmod +x /tmp/myScript.pl
Dieses Kommando ändert den Modus der Datei /tmp/myScript.pl so, dass man es direkt über die Kommandozeile aufrufen kann. Bitte nicht vergessen: Die Hashbang-Zeile des Skripts muss wie folgt aussehen: #!/usr/bin/perl -w
Nun können wir das Skript direkt aufrufen: /tmp/myScript.pl
1.10.3 Skripts in Windows ausführen In Windows sieht es ähnlich aus, nur entfällt die Änderung des Datei-Modus, weil das Betriebssystem eine feste Zuordnung zwischen Dateiendung und Dateityp macht. Als Beispiel wollen wir das Skript C:\temp\myScript.pl nehmen. Die Hashbang-Zeile muss z.B. so aussehen (die Perl-Installation sei unter D:\Perl): #!D:/Perl/bin/perl.exe -w
Wir führen das Skript nun direkt aus: C:\temp\myScript.pl
Wie findet Perl Module?
33
1.10.4 Ausführen kurzer Programme Wenn man nur ein paar Anweisungen ausprobieren möchte, dann kann man sich das Erstellen des Quellcodes in einem Editor ganz sparen, indem man in der Kommandozeile der Shell einfach nur den Perl-Interpreter ohne ein Skript als Argument aufruft. Man landet dann im interaktiven Modus von Perl, bei dem man den Quellcode Zeile für Zeile eintippt und am Ende unter UNIX ^D, unter Windows ^Z gefolgt von einem Zeilenvorschub, eingibt. Beispiel für Windows: D:\>perl -w use strict; print( "hallo\n" ); ^Z hallo D:\>
Die ersten vier Zeilen des Listings zeigen die Tastatureingaben, nach dem ^Z sieht man die Ausgabe des Programms. Meist jedoch sieht die Sache unter anderem so aus: D:\>perl -w use strict; print( $v, "\n" ); ^Z Name "main::v" used only once: possible typo at - line 2. Use of uninitialized value in print at - line 2.
Was ist hier passiert? Nun, wir versuchen, den Inhalt einer Variable $v auszugeben, ohne dass wir diese vorher initialisiert haben. Aufgrund des Schalters -w beim Aufrufen des Interpreters schimpft dieser in Form einer Compiler-Fehlermeldung, dass wir die Variable nur ein einziges Mal benutzen, was meist auf einen Fehler hindeutet (aber nicht immer der Fall ist, wie wir weiter unten noch sehen werden). Anschließend meldet sich Perl gleich noch einmal mit einer Laufzeit-Fehlermeldung, dass die Variable nicht initialisiert ist. Für die Ungeduldigen: Ich komme auf Variablen weiter unten noch ausführlich zu sprechen. Noch ein Wort zu »Compiler-Fehlermeldung« und »Laufzeit-Fehlermeldung«: Eine »Compiler-Fehlermeldung« erscheint dann, wenn der Interpreter z.B. auf einen Syntaxfehler im Quellcode während des Parse-Phase in der Übersetzungszeit stößt (der Code wird eingelesen und geprüft) der Quellcode wird zu diesem Zeitpunkt noch nicht ausgeführt. Fehlermeldungen zur Laufzeit treten auf, wenn der Quellcode bereits
34
1
Einführung
kompiliert worden ist und von der CPU im Maschinencode ausgeführt wird. Diese Art von Fehlern sind üblicherweise die schlimmsten, weil sie häufig nur in bestimmten Situationen auftreten, die man bei den Tests des Programms während der Entwicklungsphase nicht vorhergesehen hat. Die Ausgabe at- bedeutet, dass das Einlesen des Quellcodes von der Standard-Eingabe (meist ist das die Tastatur) erfolgte. Wenn ein normales Skript ausgeführt wird, das in einer Datei abgespeichert ist, stünde an Stelle des Minuszeichens der Pfadname der Skriptdatei.
1.10.5 Prüfen von Perl-Skripts Oft ist es ratsam, ein Perl-Skript vom Interpreter nur auf seine Syntax überprüfen zu lassen, bevor man es ausführt. Für diese Prüfung kann man beim Aufruf des Interpreters den Schalter -c angeben. Wollen wir uns ein Beispiel ansehen: D:\>perl -cw C:/temp/myScript.pl
Der Schalter -c wird einfach durch Zusammenfassen mit dem Schalter -w verknüpft. Die Ausgabe im Erfolgsfall (was gerade zu Beginn oft ein Glücksfall ist, und Glücksfälle sind selten, wie wir aus leidvoller Erfahrung wissen) sieht etwa so aus: C:/temp/myScript.pl syntax OK
Kleiner Tipp meinerseits: Hat man in seinem Skript Statements in einem BEGIN-Block, dann werden diese schon bei der Syntaxprüfung des Interpreters während der Übersetzungszeit ausgeführt, alle anderen Statements im Quellcode werden während dieser Phase nur überprüft, aber nicht ausgeführt. Auf BEGIN-Blocks werden wir weiter unten noch näher eingehen.
1.10.6 Inline-Dokumentation im Quellcode Unter Inline-Dokumentation versteht man eine Dokumentation des Quellcodes innerhalb derselben Datei, also dem Perl-Skript oder Perl-Modul selbst. Die einfachste Art der Inline Dokumentation haben wir bereits in Form von Kommentaren kennen gelernt. Hier noch mal ein Beispiel: #!/usr/bin/perl -w # Mit diesem Kommentar kann ich den folgenden Code # näher beschreiben: Hier wird nur Text ausgegeben print( "hallo allerseits\n" ); my $i = 0; # Integer Variable
Wie findet Perl Module?
35
Eine weitere Möglichkeit, den Quellcode im Skript oder Modul selbst zu dokumentieren, sei in folgendem Beispiel demonstriert (man beachte die Hashbang-Zeile. Diesmal scheinen wir eine Windows-Umgebung zu haben; auf den Slash als Trenner in Verzeichnisnamen komme ich weiter unten noch zu sprechen): 01 02 03 04 05 06 07 08 09 10 11 12
#!D:/Perl/bin/perl.exe -w print( "bla bla\n" ); exit( 0 ); __END__
Mein Skript Doku zu meinem Skript
...
Der erste Teil des Skripts sieht ganz normal aus, es wird einfach nur ein Text ausgegeben. Die Zeile 07 jedoch ist neu. Diese und die darauf folgende Leerzeile teilen dem Perl-Interpreter mit, dass hier das Skript zu Ende ist. Alles, was nach Zeile 08 steht, wird vom Interpreter ignoriert und eignet sich somit hervorragend für eine möglichst ausführliche Dokumentation. Wie wir sehen, wurde in dem Beispiel das HTML-Format für die Doku benutzt, im Prinzip können aber beliebige Formate verwendet werden (auch der Inhalt einer Word-Datei könnte hier stehen, es würde den Interpreter völlig kalt lassen). Statt des Identifiers (zu deutsch »Bezeichner«) __END__ kann man übrigens auch __DATA__ verwenden; beide bewirken dasselbe. Wichtig ist nur, dass danach in jedem Fall eine Leerzeile stehen muss. Es gibt noch eine weitere Art der Inline-Dokumentation, die so genannte pod-Dokumentation. Das Kürzel »pod« steht für »plain old documentation«, das sollte man aber nicht zu wörtlich nehmen. Der Vorteil von pod gegenüber Kommentaren ist, dass man den Text der Doku auszeichnen kann (z.B. durch Fettdruck oder Ähnliches). Außerdem sind verschiedene Formatierprogramme für pod verfügbar, mit denen sich die pod Dokumentation unter anderem in HTML oder anderen Ausgabeformaten anzeigen lässt. Wie wäre es, wenn Sie mit dem Kommando perldoc perlpod
ein bisschen schmökern und die dort beschriebenen Möglichkeiten einfach ausprobieren?
1.10.7 Namenskonventionen für Perl-Skripts und Perl-Module Hier heißt es aufpassen, wenn man des Öfteren zwischen UNIX und Windows wechselt. UNIX ist generell case sensitive, während in Windows normalerweise kein Unterschied zwischen Groß- und Kleinbuchstaben gemacht wird.
36
1
Einführung
Dateinamen von Perl-Skripts beginnen mit einem Kleinbuchstaben und haben normalerweise die Endung .pl (in UNIX muss das nicht so sein, in Windows ist es die Regel, weil Windows eine feste Zuordnung zwischen der Endung des Dateinamens und dem Dateityp besitzt). Leerzeichen im Dateinamen verbieten sich von selbst, da sie speziell in UNIX tödlich für viele Anwendungen sind. Dateinamen von Perl-Modulen beginnen mit einem Großbuchstaben und sollten immer die Endung .pm haben (Preisfrage: wofür steht wohl »pm«?). Ansonsten gilt dieselbe Regel für den Dateinamen wie bei Perl-Skripts. Außerdem sollte man sich angewöhnen, für den Packagenamen dieselbe Bezeichnung zu wählen wie für den Dateinamen (natürlich ohne die Endung .pm). Beispiel für das Modul in der Datei Util.pm: package Util; ... 1;
1.10.8 Verzeichnistrenner Als Bill Gates das Betriebssystem DOS an IBM verkaufte, hat er den Backslash »\« als Trennzeichen für Verzeichnisse auserkoren. Aus heutiger Sicht kann man sagen, dass dies so ziemlich die ungünstigste Wahl war, die man treffen konnte, weil der Backslash in sämtlichen Programmiersprachen eine besondere Bedeutung hat. Beispiel für eine Pfadangabe in Windows: C:\WINNT\system32
In UNIX würde derselbe Pfad etwa so aussehen: /dev/c/winnt/system32
Wie wir sehen, wird unter UNIX der Slash »/« als Zeichen für den Verzeichnistrenner verwendet. Perl ist so konzipiert, dass alle Pfade im Dateisystem mit dem Verzeichnistrenner »/« angegeben werden können, egal, auf welchem Betriebssystem man ein Skript ausführt. Deshalb sollten Sie sich tunlichst angewöhnen, nur Slashes als Verzeichnistrenner zu verwenden, niemals Backslashes. Die beiden folgenden Pfade in einem Perl-Skript sind in Windows für Perl also identisch: "D:/Programme/myProgram" "D:\\Programme\\myProgram"
In UNIX stellt sich diese Frage erst gar nicht, dort gibt es keine Backslashes als Verzeichnistrenner.
Wie findet Perl Module?
37
1.10.9 BEGIN und END Wie ich weiter oben bereits erwähnt habe, wird ein Skript in der Übersetzungszeit vom Interpreter zunächst gelesen und auf seine Syntax hin geprüft. Man nennt diesen Vorgang auch »parsen«. Anschließend werden die Anweisungen im Skript der Reihe nach kompiliert und ausgeführt; dies geschieht in der sogenannten »Laufzeit«. Manchmal ist es jedoch notwendig, bestimmte Dinge zu erledigen, bevor der Interpreter den eigentlichen Code übersetzt und ausführt. Hierzu dient der spezielle Programmblock BEGIN. Nun werden Sie sagen, was ist ein Programmblock? Ein Programmblock ist nichts anderes als eine Reihe von Anweisungen (englisch »Statements«), die von geschweiften Klammern umgeben sind. Wir werden später noch auf die Bedeutung von geschweiften Klammern zurückkommen, und glauben Sie mir, geschweifte Klammern können alles Mögliche bedeuten! Also merken Sie sich schon mal vor: geschweifte Klammern = wichtig. Der Clou eines BEGIN-Blocks ist, dass alle darin enthaltenen Anweisungen interpretiert und ausgeführt werden, bevor der Rest des Skripts vom Interpreter überhaupt gelesen wird. In einen solchen BEGIN-Block packt man also alle Anweisungen, die in jedem Falle vom Interpreter ausgeführt werden, bevor er Anweisungen des Hauptprogramms selbst ausführt. Ein Beispiel: Wir wollen im Perl-Skript Module verwenden, die der Interpreter normalerweise nicht findet, weil sie nicht im Standardverzeichnis für Perl-Module stehen. Deshalb müssen wir dafür sorgen, dass vor dem Lesen des Skripts das Verzeichnis, in welchem sich die benutzten Module befinden, in den Suchpfad des Interpreters mit aufgenommen werden. Das tun wir im BEGIN-Block. Angenommen, wir benutzen ein Modul namens »MyUtil«, das in der Datei D:\myModules\MyUtil.pm gespeichert ist. Wenn wir das Modul in einem Skript benutzen wollen, muss der Perl-Interpreter in der Lage sein, es auch zu finden. Dies teilen wir mit folgendem Code mit: #!D:/Perl/bin/perl.exe -w BEGIN { unshift( @INC, "D:/myModules" ); } use MyUtil; ...
Bevor der Interpreter die use-Anweisung interpretiert und ausführt, werden die Statements im BEGIN-Block ausgeführt. Dort wird der Suchpfad zum Auffinden von PerlModulen um das Verzeichnis erweitert, in dem die benötigten Module stehen. Wenn
38
1
Einführung
der Interpreter also auf die Direktive trifft, in der das Modul »MyUtil« geladen werden soll, kann er es finden, da er nun auch unter dem Verzeichnis D:\myModules danach sucht. Eine weitere Möglichkeit, den Suchpfad für Perl Module zu erweitern, besteht darin, die Umgebungsvariable PERLLIB zu setzen, oder beim Aufruf des Perl Interpreters den Schalter -I anzugeben. Beispiel für Windows: D:\>set PERLLIB=D:\myModules
Unter UNIX (für bash): /home/hemu: % export PERLLIB=/home/hemu/myModules
UNIX (für sh): $ PERLLIB=/home/hemu/myModules ; export PERLLIB
So viel zum BEGIN-Block. Wollen wir uns nun dem END-Block zuwenden. Dieser wird ähnlich wie der BEGIN-Block vor dem Rest des Skripts interpretiert, die darin enthaltenen Statements werden aber erst dann ausgeführt, wenn der zugehörige Geltungsbereich, in welchem der END-Block definiert ist, verlassen wird. Das hört sich ziemlich akademisch und damit automatisch unverständlich an. Sagen wir es in einfachen Worten: Besitzt ein Perl-Skript einen END-Block, dann werden die darin enthaltenen Anweisungen ohne Wenn und Aber ausgeführt, wenn das Skript beendet wird, egal auf welche Weise. Benutzt wird der END-Block häufig, um verwendete Ressourcen wieder freizugeben, wenn das Skript beendet wird. Eine Ressource kann zum Beispiel eine geöffnete Datei sein. Im END-Block kann man auch den exit Status eines Skripts abfragen oder sogar verändern. Beispiel für die Benutzung eines END-Blocks: 01 02 03 04 05 06 07
#!/usr/bin/perl -w use strict; use FileHandle; my $fh = new FileHandle( "/tmp/bla.txt", "w" );
Wie findet Perl Module? 08 09 10 11 12 13 14
39
# hier folgt weiterer Programmcode exit( 0 ); END { if ( $fh ) { $fh->close(); } }
Erläuterungen: In Zeile 06 wird die Datei /tmp/bla.txt für schreibenden Zugriff angelegt. Damit wird zugleich eine Ressource des Betriebssystems in Form eines so genannten »FileHandles« verbraucht (FileHandles werden in einem eigenen Abschnitt behandelt). Um sicherzustellen, dass die belegte Ressource wieder freigegeben wird, wenn das Skript beendet wird (das Skript kann nach dem Anlegen der Datei vom Interpreter auch hart abgebrochen werden, weil ein Programmfehler aufgetreten ist), geben wir das FileHandle im END-Block wieder frei. Da der END-Block in jedem Fall vor Beendigung des Skripts durchlaufen wird, ist sichergestellt, dass die Systemressource auch in Fehlerfällen wieder freigegeben wird.
2 Grundlagen Dieses Kapitel behandelt die Grundlagen der Programmiersprache Perl. Sie sind zwar meist ermüdend, aber leider notwendig, man will ja schließlich mehr als nur kopierte Skripts ausführen können. Sie lernen hier alles, was zum Programmieren in Perl wichtig ist, angefangen von Datentypen über Variablen, Operatoren, Funktionen und Modulen, bis hin zur Datenverarbeitung im Dateisystem (I/O). Sehen wir uns als Erstes an, was Perl an Datentypen zu bieten hat.
2.1 Grundbegriffe Bevor ich Sie mit unverständlichen Begriffen bombardiere, möchte ich einen kleinen Überblick geben, der manche Begriffe kurz erläutert: Begriff
Kurzbeschreibung
Skript
Ein Skript ist Perl-Code, der in einer Datei mit der Endung .pl abgespeichert ist (dies ist allerdings nur eine Namenskonvention, um ein Perl-Skript zu kennzeichnen, vor allem in UNIX kann ein Perl-Skript in einer Datei mit beliebigem Namen gespeichert sein). Ein Skript enthält mindestens ein Hauptprogramm (im Englischen nennt man dies »main«). Zusätzlich kann ein Skript auch Funktionen enthalten (die man historisch bedingt auch »Unterfunktionen« nennt). Skripts sind im Prinzip Programme, mit dem Unterschied, dass sie keinen Binärcode enthalten, der direkt von der CPU ausgeführt wird, sondern von einem Interpreter verarbeitet werden müssen.
Hauptprogramm
Darunter versteht man denjenigen Programmteil eines Skripts, der nicht als Funktion oder Unterfunktion definiert ist. Er wird durch den Interpreter ausgeführt.
Tabelle 2.1: Grundbegriffe
42
2
Grundlagen
Begriff
Kurzbeschreibung
Package
Ein Package ist derjenige Programmcode, der in einem Modul mit der packageDirektive gekennzeichnet wird. In den meisten Fällen ist ein Package der Name eines Perl-Moduls ohne die Datei-Endung .pm. Ein Package besteht im Wesentlichen aus Funktionen, Variablendefinitionen und Initialisierungscode, enthält aber kein Hauptprogramm.
Modul
Unter einem Modul versteht man eine Datei mit der Endung .pm. Sie enthält PerlCode ohne Hauptprogramm. Ein Modul ist meist identisch mit einer Datei, die ein Package gleichen Namens enthält (ohne die Endung .pm). Sie kann aber auch mehrere Packages enthalten.
Funktion, Unterfunktion
Eine Funktion (historisch bedingt auch »Unterfunktion« genannt) enthält Programmcode, der meist aus mehreren Anweisungen besteht, die bestimmte Aktionen durchführen und in einem Block zusammengefasst werden. Über so genannte »Funktionsparameter« oder auch »Funktionsargumente« kann man einer Funktion dynamisch zur Laufzeit Daten übergeben und somit das Verhalten einer Funktion von außen beeinflussen. Fast immer geben Funktionen dem Aufrufer einen Status zurück, der Erfolg oder Misserfolg kennzeichnet.
Direktive
Eine Direktive ist in Perl meist identisch mit einer Funktion, hat aber einen anderen Zweck. Mit Direktiven stellt man das Verhalten des Perl-Interpreters ein. So kann man z.B. weitere Module in den Hauptspeicher laden oder den Interpreter anpassen, so dass er z.B. Warnungen ausgibt (oder auch nicht). Die wichtigsten Direktiven in Perl sind:
왘 use 왘 require 왘 no Operator
Operatoren werden benötigt, um Daten miteinander zu verküpfen. Sie kennen sicherlich den »+«-Operator, der eine Addition durchführt. Er hat zwei so genannte »Operanden«, einen links, einen rechts (z.B. 5 + 3).
Bareword
In Perl ist alles, was nicht in so genannten »Quotes« (zu Deutsch Anführungszeichen) steht, ein Bareword (zu Deutsch: »nacktes Wort«). Immer dann, wenn der Perl-Interpreter beim Lesen des Programmcodes auf ein Bareword trifft, versucht er, dieses Bareword als Funktionsname, Packagename oder Konstantenname zu interpretieren. Wir werden im Weiteren noch sehr häufig auf diesen Begriff stoßen. Hier nur als Beispiel: use strict; enthält einen Funktionsaufruf und ein Bareword als Argument. use ist der Funktionsaufruf (der eigentlich eine Direktive darstellt), strict ist ein Bareword und bedeutet: »Lade bitte das Modul strict.pm«.
Tabelle 2.1: Grundbegriffe (Forts.)
Wenn Sie jetzt noch nicht alle Begriffe verstanden haben, macht das nichts, sie werden im weiteren Verlauf ausgiebig erläutert. Beginnen wir mit dem Einfachsten:
Datentypen
43
2.2 Datentypen Perl besitzt nur 3 fest im Sprachwortschatz eingebaute (englisch: »built-in«) Datentypen: Skalare, Arrays und Hashes (Hashes werden auch »assoziative Arrays« genannt). Die beiden letztgenannten Datentypen werden auch als Listen bezeichnet. Im Folgenden sind in den Beispielen Variablen und Funktionen enthalten, die erst in späteren Abschnitten erklärt werden. Denken Sie sich nichts dabei, wenn Teile der Beispiele noch unverständlich sind, der Aha-Effekt kommt später. Zunächst wollen wir einen Blick auf den skalaren Datentyp werfen:
2.3 Skalare Skalare sind Zahlen, Strings (Zeichenketten) oder Referenzen auf andere Daten. Sie zeichnen sich dadurch aus, dass sie nur einen einfachen Wert beinhalten. Referenzen sind ähnlich wie in der Programmiersprache C Zeiger auf andere Daten wie zum Beispiel Variablen, Funktionen etc. und enthalten die Speicheradresse des Ziels, auf das sie zeigen. Sie können überall verwendet werden, wo skalare Daten erlaubt sind, was sie zu einem sehr leistungsfähigen Programmiermittel macht. Eine Referenz wird durch Voranstellen eines Backslashs »\« gekennzeichnet. Neben dieser Art von Referenzen, die man im Englischen auch als »soft references« bezeichnet, gibt es noch harte Referenzen (englisch: »hard references«), die durch ein Sternchen »*« gekennzeichnet werden. Auf diesen Referenztyp wird hier nicht eingegangen, weil man sie schlicht so gut wie nie benötigt. Nach so vielen für den Laien nichts sagenden Begriffen nun ein paar Beispiele für skalare Werte: 3 # Ganze Zahl (Integerzahl) -3.14 # Festkommazahl 7.04E-12 # Gleitkommazahl 7.04e-12 # dasselbe "hallo" # String '17' # Zahl als String angegeben "17" # noch einmal dasselbe anders "das\tist\nein String mit Steuerzeichen" 'das\tist\nein String ohne Steuerzeichen' \"Referenz auf diesen String"
44
2
Grundlagen
2.3.1 Behandlung von Zahlen Perl unterstützt sowohl Integerwerte (ganze Zahlen) als auch Festkomma- und Gleitkommawerte für Zahlen. Integerwerte können sowohl im Dezimalsystem als auch zu den Basen 2 (Dualsystem), 8 (Oktalsystem) und 16 (Hexadezimalsystem) angegeben werden. Der Exponent von Gleitkommawerten wird gekennzeichnet durch Voranstellen des Buchstabens E oder e, beide Varianten sind erlaubt. Hier einige Beispiele: # Integerwerte 7 -3 0x7f # Hexzahl 0377 # Oktalzahl 0b100110 # Dualzahl -0x3A9F # negative Hexzahl # Festkommawerte 3.7 -17.4 # Hex-, Oktal- oder Dualzahlen als # Festkommawerte sind nicht erlaubt. 0b1.01 # ist ungültig 0x3.14 # ebenso ungültig Gleitkommawerte 5.1E3 -30.5e-3 # Werte mit Unterstrich für Tausenderstellen 3_756_455 (entspricht 3756455) # Das Komma als Dezimalpunkt ist nicht erlaubt! 320,17 # liefert einen Fehler
Für Zahlenumwandlungen aus dem Hexadezimal- und dem Oktal- bzw. Dualsystem in das Dezimalsystem stehen die Funktionen hex() sowie oct() zur Verfügung. Diese Funktionen werden weiter unten noch näher besprochen.
2.3.2 Darstellbarer Zahlenbereich In Perl hängt der darstellbare Zahlenbereich von der Breite der CPU-Register ab. Bei den heute gängigen 32-Bit-Rechnern ist die größte Integerzahl also 4 *1024 *1024 *1024 (ohne Vorzeichen). Diese Einschränkung betrifft vor allem Bit-Operationen. Bei arithmetischen Operationen können Integerzahlen bis (z.B. 100.000.000.000.000) ohne Verlust von Präzision verarbeitet werden.
ca. 10E14
Skalare
45
Mit den Zusatzpaketen Math::BigInt und Math::BigFloat können beliebig große Zahlen verarbeitet werden, jedoch sinkt in diesem Fall die Performance.
2.3.3 Kennzeichnung von Strings (Quoting) In Perl werden Zeichenketten (Strings) entweder in doppelte Anführungszeichen »"« (englisch: »double quote«) oder in einfache Anführungszeichen »'« (englisch: »single quote«) gesetzt. Sie können alle Zeichen, auch Sonderzeichen und sogar binär 0 enthalten. Bei Zeichenketten in einfachen Anführungszeichen interpretiert Perl jedes Zeichen literal (genau so, wie es geschrieben ist), während bei Strings in doppelten Anführungszeichen eine Interpretation von Sonderzeichen erfolgt. Sonderzeichen werden durch Voranstellen eines Backslash »\« gekennzeichnet. Folgende Zeichen in Strings mit doppelten Anführungszeichen haben eine besondere Bedeutung: Sonderzeichen
Bedeutung
\n
Zeilenvorschub
\t
Tabulator
\r
Wagenrücklauf (DOS und MAC)
\\
Backslash
\"
doppeltes Anführungszeichen
\'
einfaches Anführungszeichen
\x{zzzz}
Zeichen im Unicode-Format (zzzz ist der Hexcode des Zeichens)
Das Dollarzeichen »$« und das At-Zeichen »@« haben in Zeichenketten, die durch doppelte Anführungszeichen angegeben werden, ebenfalls eine besondere Bedeutung, die wir später bei der Behandlung von Variablen kennen lernen werden. Die Tabelle liefert einige Beispiele: String
Beschreibung
"einfacher String"
String in doppelten Anführungszeichen.
'einfacher String'
String in einfachen Anführungszeichen.
"String mit '"
Einfaches Anführungszeichen ist Bestandteil des Strings.
'String mit "'
Doppeltes Anführungszeichen ist Bestandteil des Strings.
"String mit \""
Hier muss vor dem doppelten Anführungszeichen ein Backslash stehen, weil der String selbst bereits durch doppelte Anführungszeichen dargestellt ist.
46
2
Grundlagen
String
Beschreibung
'String mit \''
Hier muss vor dem einfachen Anführungszeichen ein Backslash stehen, weil der String selbst bereits durch einfache Anführungszeichen dargestellt ist.
"String mit \n"
\n wird als Sonderzeichen für Zeilenvorschub interpretiert.
'String mit \n'
Hier sind \n zwei literale Zeichen ohne besondere Bedeutung.
"String mit \x{0041}"
\x{0041} wird als Unicode interpretiert und damit zum Zeichen "A".
'String mit \x{0041}'
Hier wird \x{0041} literal ohne besondere Bedeutung behandelt.
"String mit $Zeichen"
Das Dollarzeichen wird als Kennzeichen für eine skalare Variable interpretiert.
'String mit $Zeichen'
Das Dollarzeichen hat keine besondere Bedeutung.
"String mit @Zeichen"
Das At-Zeichen wird als Kennzeichen für eine Array Variable interpretiert.
'String mit @Zeichen'
Das At-Zeichen hat keine besondere Bedeutung.
Hinweis für C- und Java-Programmierer Perl kennt keinen Datentyp char: Dieser wird wie ein String behandelt, der nur aus einem Zeichen besteht. Gibt man im Skript einen String mit dem Sonderzeichen für Zeilenvorschub \n aus, dann hängt es vom Betriebssystem ab, ob wirklich nur ein Zeilenvorschub (UNIX) oder aber zusätzlich ein Wagenrücklauf \r, z.B. in DOS, ausgegeben wird. Ich habe schon Fälle erlebt, wo Programmierer deshalb Stunden und Tage auf Fehlersuche waren. Dieses Verhalten kann man abschalten, wenn man vor der Ausgabe das betreffende Ausgabemedium mit der Funktion binmode() auf Binärmodus umstellt. Beispiel: print( "hallo\n" ); # Das Statement gibt unter UNIX exakt # den angegebenen String aus # Unter Windows wird der String "hallo\r\n" ausgegeben # Auf einem Macintosh wird der String # "hallo\r" ausgegeben
Weiter unten komme ich noch einmal auf dieses Thema zurück.
Skalare
47
2.3.4 Der skalare Wert »undef« Dieser Pseudowert wird bei skalaren Daten verwendet, um diesen einen nicht definierten Zustand zu geben. In Funktionen kann man den Wert undef an den Aufrufer zurückgeben, um einen Fehler anzuzeigen. Meist jedoch macht sich der Wert undef unangenehm in Fehlermeldungen des Interpreters bemerkbar. Ich habe noch keinen Entwickler gesehen, der ein umfangreiches Programm entwickelt hat, ohne solche Fehlermeldungen zu erhalten. Viele Module, die man aus dem Internet herunterladen kann, funktionieren in dem Moment nicht mehr, in dem man den Schalter -w von Perl aktiviert, weil sie unsauber geschrieben sind oder nicht initialisierte Variablen enthalten. undef ist insofern ein Pseudowert, als dass er im Gegensatz zu Zahlenwerten oder
Stringwerten das absolute Nichts darstellt (vergleichbar mit NULL-Werten in einer Datenbank). Konkret bedeutet undef bei Variablen, dass der Interpreter noch keinen Speicherplatz für eine Variable reserviert hat, die Variable also noch nicht initialisiert ist. Hierzu ein kleines Beispiel: 01 02 03 04
#!D:/Perl/bin/perl.exe -w my $i; print( "i = $i\n" ); exit( 0 );
In Zeile 02 wird die skalare Variable $i deklariert (ohne Initialisierung), in Zeile 03 soll der Variablenwert ausgegeben werden. Wenn wir das Skript ausführen, erhalten wir folgende Fehlermeldung: Use of uninitialized value in concatenation (.) or string at C:\temp\tst.pl line 3. i =
Durch eine kurze Überlegung ist klar, was passiert: Mit der Deklaration der Variable allein wird im Hauptspeicher noch kein Speicherplatz für den Wert der Variable belegt (englisch: »allocated«, neudeutsch: »alloziert«). In Zeile 03 soll aber der Wert der Variable ausgegeben werden. Dafür ist jedoch ein belegter Speicherplatz Voraussetzung, deshalb die Fehlermeldung. Wenn wir nun den Schalter -w weglassen: #!D:/Perl/bin/perl.exe my $i; print( "i = $i\n" ); exit( 0 );
dann erhalten wir folgende Ausgabe: i =
48
2
Grundlagen
Wie wir sehen, fehlt die Fehlermeldung des Interpreters. Ich möchte jedoch betonen, dass damit zwar die Auswirkung, nicht aber die Ursache behoben ist, der Fehler ist nach wie vor im Programm. Also merken wir uns: Immer den Schalter -w verwenden. Man sollte sich angewöhnen, Variablen bei ihrer Deklaration immer einen Wert zuzuweisen, auch wenn dieser undef ist. Damit sieht derjenige, der den Quellcode liest, sofort, was gemeint ist: #!D:/Perl/bin/perl.exe -w my $i = undef; ...
Wie wir später noch sehen werden, stellt Perl die Funktion defined() zur Verfügung, mit deren Hilfe man überprüfen kann, ob eine Variable initialisiert ist oder nicht. Bei Listen (Arrays und Hashes) gibt es den Wert undef nicht. Das Gefährliche ist nur, dass der Interpreter keine Fehlermeldung ausgibt, wenn man versucht, einem Array den Wert undef zuzuweisen. Bei Hashes hingegen wird ein Fehler gemeldet. Mehr zu diesem Verhalten, wenn wir Arrays kennen lernen.
2.3.5 Boolesche Werte Ein Boolean-Wert kann im Gegensatz zum Dezimalsystem, wo wir 10 Ziffern haben, nur in zwei verschiedenen Varianten vorkommen: unwahr (englisch: FALSE) oder wahr (englisch: TRUE). In vielen Programmiersprachen sind diese beiden Werte fester Bestandteil des Sprachwortschatzes in Form von reservierten Wörtern. Hier ein kleiner Auszug aus einem Java-Programm: boolean flag = false; // Hier kommt weiterer Quellcode ... if ( flag == false )
Leider existieren in Perl die booleschen Werte FALSE und TRUE nicht als reservierte Wörter, die man direkt im Programmcode benutzen kann. Im Gegensatz zu fast allen anderen Programmiersprachen ist in Perl ein Wert logisch unwahr (FALSE), wenn er entweder die Zahl 0, einen leeren String, die Ziffer 0 als einziges Zeichen eines Strings oder den Pseudowert undef enthält. Beispiele: if if if if if if
( ( ( ( ( (
0 ) # logisch false, unwahr "" ) # ebenfalls logisch false "0" ) # logisch false undef ) # gleichfalls logisch false -1 ) # logisch wahr "a" ) # logisch wahr
Listen
49
Man kann jedoch Konstanten mit einem TRUE- bzw. FALSE-Wert definieren, die den Namen true bzw. TRUE und false bzw. FALSE haben, wir werden bei der Beschreibung von Konstanten noch darauf zu sprechen kommen.
2.3.6 Referenzen Eine Referenz ist kein normaler Wert wie zum Beispiel eine Zahl oder ein String, sondern die Speicheradresse eines anderen Werts. In Perl kann man praktisch für alles eine Referenz verwenden, selbst für Programmcode. Wir werden uns weiter unten noch ausführlich mit Referenzen beschäftigen.
2.4 Listen Arrays und Hashes enthalten mehrere skalare Werte (Elemente). Man bezeichnet sie deshalb auch als Listen. Gekennzeichnet werden Listen durch runde Klammern: ( 1, "3", -4, "Stringwert" )
kennzeichnet eine Liste. In diesem Fall handelt es sich um ein Array mit 4 Elementen. Als Trennzeichen für die einzelnen Elemente der Liste wird das Komma verwendet. Wir können auch eine leere Liste angeben: ()
Eine leere Liste wird durch ein Paar runder Klammern ohne Inhalt angegeben. Ein Paar geschweifter Klammern hat eine andere Bedeutung!
2.4.1 Arrays Arrays sind Container für mehrere Elemente, die fortlaufend nummeriert werden. Das erste Element besitzt die Nummer 0. Die Nummern, unter denen die einzelnen Elemente angesprochen werden, nennt man auch Indizes. Jedes Element kann einen beliebigen skalaren Datentyp haben, es können also Zahlen und Strings sowie weitere skalare Datentypen wie Referenzen gemischt in einem Array enthalten sein. Man kann die Nummer für das erste Array-Element mit Hilfe der vordefinierten Perl-Variable $[ auch verändern, zum Beispiel auf den Wert 1. Davon möchte ich jedoch aus Portabilitätsgründen dringend abraten.
50
2
Grundlagen
Arrays sind Listen und werden als solche in runde Klammern eingeschlossen.
Beispiel für ein Array: # # # # # (
Die folgende Liste stellt ein Array bestehend aus 5 Elementen dar Jedes Element muss ein Skalar sein, es dürfen aber alle skalaren Datentypen gemischt vorkommen 17, 0, -1.3, "Stringwert", 'noch ein String', )
# Array mit 4 Elementen (Index 0 bis 3) # Das letzte Element enthält eine Referenz-Variable ( "3", 1, "wort", $referenz, ) # Leere Liste ()
Die einzelnen Array-Elemente werden durch ein Komma getrennt. Perl erlaubt ein Komma auch nach dem letzten Element. Man sollte dieses Feature nutzen, weil damit weniger Fehler entstehen, wenn man zu einem späteren Zeitpunkt weitere Elemente hinzufügt.
Auf Array-Elemente zugreifen Bis jetzt haben wir nur Listen in Form von Arrays angelegt, ohne auf einzelne Elemente des Arrays zuzugreifen. Das wollen wir jetzt nachholen. Wie in anderen Programmiersprachen auch sind eckige Klammern mit einem Index vorgesehen, um auf ein einzelnes Element des Arrays zuzugreifen: # Liste aus 3 Elementen ( "17", "hallo", 4, ) # Wir extrahieren das zweite Element "hallo" ( "17", "hallo", 4, )[ 1 ]
Mit der Zahl in eckigen Klammern gibt man die Indexnummer des Elements der Liste an, auf das man zugreifen möchte (in diesem Fall das zweite Element, da die Nummerierung bei 0 beginnt). Weitere Beispiele: ( # # #
stat( "/etc/passwd" ) )[ 7 ] liefert das 8. Element der Liste, die von der Funktion stat() zurückgegeben wird, das ist die Dateigrösse in Bytes
Listen
51
Der aufmerksame Leser wird bemerkt haben, dass um den Funktionsaufruf von stat() noch einmal runde Klammern gesetzt sind. Die Funktion gibt jedoch bereits selbst eine Liste zurück, daher könnte man meinen, dass auch folgender Code funktioniert: stat( "/etc/passwd" )[ 7 ]
Der Interpreter liefert hier jedoch einen Syntaxfehler, weil Perl einen Unterschied zwischen Listen und Array-Listen macht (auch Hashes sind ja Listen). Man muss um die Funktion Klammern setzen wie oben gezeigt, damit Perl weiß, dass es sich hier um eine Array-Liste handelt. Arrays können niemals den Pseudowert undef haben. Falls man einer Array-Variable gezielt den Wert undef zuweist, wird ein Array mit einem einzigen Element, das den Wert undef besitzt, angelegt, ohne dass der Interpreter eine Fehlermeldung ausgibt: my @array = undef; # @array hat folgenden Inhalt # ( undef, )
List-Kontext und skalarer Kontext Perl unterscheidet grundsätzlich zwischen skalarem Kontext und List-Kontext im Programm (es gibt auch noch den Void-Kontext, bei dem der Aufrufer einer Funktion keinen Rückgabewert erwartet, siehe hierzu das Beispiel unten). List-Kontext liegt immer bei Verwendung von Arrays oder Hashes vor, skalarer Kontext bei Verwendung von skalaren Variablen. Beispiele für einen skalaren Kontext: my $scalar = myFunc(); # Der Rückgabewert des Funktionsaufrufes wird einer # skalaren Variable zugewiesen, deshalb liegt hier ein # skalarer Kontext vor. my $line = <$fileHandle>; # Einlesen einer einzelnen Zeile # von einem Eingabemedium wie der Tastatur # in skalarem Kontext # Vorsicht: my ( $line ) = <$fileHandle>; # Das ist kein skalarer Kontext. Durch die runden # Klammern um die skalare Variable $line wird die # linke Seite der Zuweisung zu einer Liste! # Auswirkung: Es werden alle Zeilen des
52
2
Grundlagen
# Eingabemediums gelesen, die erste Zeile wird an # die Variable zugewiesen, alle weiteren werden # verworfen.
Beispiele für einen List-Kontext: my @array = myFunc(); # Der Rückgabewert des Funktionsaufrufes wird einer # Array-Variable zugewiesen, deshalb liegt hier # List-Kontext vor. my ( $scalar1, $scalar2 ) = myFunc1(); # Auch hier liegt List Kontext vor, obwohl links vom # Gleichheitszeichen nur skalare Variablen verwendet # werden. Durch die runden Klammern um die Variablen # wird ein List Kontext erzeugt. my ( $line ) = <$fileHandle>; # Auch hier liegt ein List Kontext vor. Dieses Beispiel # werden wir weiter unten noch vertiefen.
Die print()-Funktion von Perl erzeugt immer einen List-Kontext. Hier muss man aufpassen, weil manche Funktionen unterschiedlich reagieren, je nachdem, in welchem Programm-Kontext sie aufgerufen werden: print( localtime(), "\n" );
Das Code-Fragment könnte zum Beispiel folgende Ausgabe hervorrufen: 354681301020120
Diese scheinbar sinnlose Ausgabe stellt in Wirklichkeit eine Liste aus Sekunden, Minuten, Stunden, Tag des Monats, Monat, Jahr, Wochentag, Jahrestag und Sommerzeit dar. Wir werden es gleich besser verstehen, wenn wir dieselbe Funktion in skalarem Kontext aufrufen: my $date = localtime(); print( "$date\n" );
Nun gibt die print()-Funktion das Datum so aus: Sun Jan 13 08:46:35 2002
Das können wir schon besser lesen, obwohl es sich in beiden Fällen um dasselbe Datum handelt. Wenn wir jetzt noch die einzelnen Elemente der Rückgabeliste der Funktion localtime() besser ausgeben, kann man es auch erkennen: print( join( ", ", localtime() ), "\n" );
Listen
53
Nun werden die einzelnen Elemente der Rückgabeliste durch Kommata getrennt ausgegeben: 35, 46, 8, 13, 0, 102, 0, 12, 0
Bemerkenswert ist die 0 für den Monat, die 102 für das Jahr, die 0 für den Wochentag, sowie die 12 für den Jahrestag. Dazu müssen wir wissen, dass der erste Monat des Jahres die Nummer 0 hat, das Jahr als Offset des Jahres 1900 ausgegeben wird, Wochentage mit dem Index 0 begonnen werden (der erste Wochentag ist Sonntag, der letzte ist der Samstag), ebenso wie die Nummer des Jahrestages. Ich glaube, nach diesen Erläuterungen sind die beiden Ausgaben wirklich identisch. Mit Hilfe der Funktion scalar() kann man auch skalaren Kontext erzwingen: print( scalar( localtime() ), "\n" );
Nun wird das Datum wieder als String ausgegeben. my @date = localtime(); print( scalar( @date ), "\n" );
Jetzt wird nicht etwa das Datum als String ausgegeben, sondern die Zahl 9. Das liegt daran, dass die Funktion localtime() eine Liste und keine Array-Liste zurückliefert. Die Funktion scalar() gibt bei einem Array als Argument die Anzahl der im Array enthaltenen Elemente zurück (in unserem Fall sind es 9 Elemente). Weiter oben habe ich gesagt, dass sich die Funktion localtime() unterschiedlich verhält, je nachdem, in welchem Programm-Kontext sie aufgerufen wird. Wie erkennt sie, welcher Kontext vorliegt? Die Antwort heißt wantarray(). Das ist eine Perl-Funktion, die TRUE zurückgibt, wenn sich das Programm in List-Kontext befindet, und ansonsten FALSE. Wir werden dieses Thema noch ausführlicher behandeln. Zum Schluss noch ein Beispiel für den Void-Kontext: myFunc();
Wird eine Funktion aufgerufen, ohne dass deren Rückgabewert benutzt wird, dann haben wir den klassischen Fall von Void-Kontext.
2.4.2 Hashes Ein Hash (auch assoziatives Array genannt) ist wie ein Array eine Liste und wird ebenfalls in runde Klammern eingeschlossen. Im Gegensatz zu Arrays werden die Hash-Elemente nicht über einen numerischen Index, sondern über einen Schlüssel (englisch:
54
2
Grundlagen
»Key«) angesprochen, das ist immer ein String, der dem Element einen Namen gibt (deshalb auch assoziativ, weil man sich unter dem Schlüssel etwas vorstellen kann). Der Wert eines Hash-Elements wird auch »Value« genannt und muss ein skalarer Wert sein. Einen Hash kann man sich unter anderem als Register vorstellen, das aus Karten besteht. Auf den Reitern der Karten steht der Nachname, unten steht zum Beispiel der Vorname. Wenn wir nun die Karte für »Hans Dampf« brauchen, müssen wir in den Reitern nach dem String »Dampf« suchen. Diese Suche kann vor allem dann recht lange dauern, wenn man ein Chaot ist und die Karten nicht sortiert hat. Deshalb haben wir Menschen uns ein System für die Sortierung einfallen lassen, damit man bei der Suche möglichst schnell zum Ziel kommt. Der Algorithmus für die Speicherung von Hash-Elementen in Perl ist ganz anders. Hier sind die Elemente nicht sortiert. Stattdessen wird für jeden Schlüssel der Elemente ein Hashwert berechnet und dieser in einer Liste abgelegt. Wenn man nun durch die Angabe des Schlüssels auf ein bestimmtes Element zugreift, dann sucht Perl nicht den Schlüssel, sondern erst den Hashwert des Schlüssels in der Liste. Dieser Algorithmus ist wesentlich performanter als eine Suche in sortierten Elementen. Merke Hash-Elemente sind immer unsortiert! Dies ist das wesentliche Unterscheidungsmerkmal zu Arrays, bei denen die einzelnen Elemente ja durch den Index sequenziell aufsteigend sortiert abgelegt werden. Da ein Hash-Element immer einen Key und einen Value besitzt, ist die Anzahl der resultierenden List-Elemente immer ein Vielfaches von 2. Ein Hash wird also durch Key/Value-Paare gebildet. Ebenso wie Arrays können Hashes niemals den Pseudowert undef haben, da dieser nur bei Skalaren gültig ist. Allerdings erzeugt der Interpreter bei Hashes eine Fehlermeldung, wenn man versucht, einem Hash den Wert undef zuzuweisen, was bei Arrays nicht der Fall ist. Beispiel für ein Hash: ( "fn" => "Gundel", "ln" => "Gaukel", "age" => 50, )
Die Liste im Beispiel definiert ein Hash mit insgesamt 3 Elementen (gekennzeichnet durch die Keys »fn«, »ln«, »age«). Das resultierende assoziative Array hat insgesamt 6 Elemente, weil jedes Hash-Element aus einem Key/Value-Paar besteht. Aufgrund dieser Tatsache könnte man obiges Hash auch wie folgt angeben: ( "fn", "Gundel", "ln", "Gaukel", "age", 50, )
Listen
55
Jedoch wird von dieser Art abgeraten, da man hier den Unterschied zu einem normalen Array nicht erkennen kann. Wenn ich an die Vorteile von Perl denke, fallen mir als Erstes die Begiffe »Hash« und »reguläre Ausdrücke« ein. Zu regulären Ausdrücken werden wir weiter unten noch ausgiebig kommen. Hashes sind in Perl das Vehikel, um beliebige neue Datenstrukturen aufzubauen, was besonders in der Objektorientierten Programmierung ein zentraler Punkt ist. Das erste, was man in Perl verstanden haben muss, sind Hashes. Deshalb werde ich mein Bestes geben, damit Hashes für Sie keine böhmischen Dörfer bleiben. Seit Version 5.6 von Perl darf man Hash-Keys auch als Barewords ohne Quotes angeben, da der Interpreter Keys grundsätzlich in Strings umwandelt. Ich persönlich bevorzuge allerdings die explizite String-Darstellung: # erlaubt: ( fn => "Gundel", ln => "Gaukel", age => 50, ) # explizite Angabe: ( "fn" => "Gundel", "ln" => "Gaukel", "age" => 50, )
Sie werden sicherlich zu Recht fragen: Was ist ein Bareword? Wörtlich übersetzt bedeutet »Bareword« nichts anderes als »nacktes Wort«. Beim Parsen von Quelltext muss der Interpreter genau wissen, was er gerade vor sich hat, einen String, eine Zahl, eine Variable, eine Funktion etc. Trifft der Interpreter auf ein Wort, das nicht als String oder Zahl erkannt wird, dann ist es zunächst ein »Bareword«, und der Interpreter versucht nun, dieses Bareword zuzuordnen. Er sucht also in seiner Liste von Funktionen, Konstanten und sonstigen bekannten Dingen, ob er dort einen passenden Identifier (deutsch: »Bezeichner«) findet. Verläuft diese Suche erfolglos, kommt prompt eine Fehlermeldung wie im folgenden Beispiel: #!/usr/bin/perl -w use strict; my $a = a;
In diesem Beispiel haben wir vergessen, das Zeichen a als String zu kennzeichnen, es ist deshalb für den Interpreter ein Bareword. Wenn wir das Skript ausführen, erhalten wir folgende Fehlermeldung: Bareword "a" not allowed while "strict subs" in use at /tmp/tst.pl line 3. Execution of /tmp/tst.pl aborted due to compilation errors.
56
2
Grundlagen
2.5 Konstanten Üblicherweise dürfen in Perl keine Variablenidentifier (die deutsche Übersetzung von »Identifier« ist »Kennzeichner«) ohne Typkennzeichen verwendet werden, denn Barewords sind normalerweise für Funktionsnamen vorgesehen. Mit Hilfe der use-Direktive in Verbindung mit dem Package constant (Perl-Moduldatei constant.pm) können jedoch Barewords als Konstanten (englisch: »constants«) definiert werden: # Definition use constant use constant use constant
von Konstanten PI => 4 * atan2( 1, 1 ); FALSE => 0; TRUE => !FALSE;
# Benutzung der Konstanten als Bareword: print( "PI = ", PI, "\n" ); # Falsch wäre: print( "PI = PI\n" ); # da in diesem Fall das "PI" kein Bareword ist, # sondern Bestandteil des Strings, weil es # in Quotes steht, und somit vom # Interpreter nicht evaluiert wird. # Evaluierung bedeutet, dass der Interpreter # statt des Wortes PI den Wert der Konstanten "PI" # einsetzt. my $flag = TRUE; my $otherFlag = FALSE;
Bei der Deklaration von Konstanten wird der Operator => verwendet und nicht das Gleichheitszeichen =, das wäre nämlich eine Zuweisung mit linker und rechter Seite. »Moment mal«, werden Sie sich sagen, »den Operator kenne ich doch von Hashes.« Richtig, der Ausdruck: FALSE => 0
ist tatsächlich ein Hash mit einem Element, nämlich FALSE als Key und 0 als Value. Vorsicht bei Listen: Nehmen wir eine List-Konstante für Wochentage als Beispiel: use strict; use constant WDAYS => ( "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag" ); print( WDAYS[ 2 ], "\n" );
Zunächst haben wir die Konstante WDAYS als Liste definiert, die alle Wochentage als Elemente enthält (wie üblich, beginnt für Programmierer die Woche mit Sonntag). Mit der print()-Funktion versuchen wir, das dritte Element »Dienstag« auszugeben, die Kon-
Konstanten
57
stante WDAYS verwenden wir nun als Array. Prompt haben wir uns eine Fehlermeldung des Interpreters eingehandelt, die in etwa so aussieht: syntax error at - line 4, near "WDAYS[" Execution of - aborted due to compilation errors.
Wie man an diesem Beispiel sieht, sind Listen und Arrays in Perl nicht identisch. Erst wenn wir die Konstante explizit als Liste kennzeichnen, indem wir sie in runde Klammern einschließen, funktioniert die Sache: print( ( WDAYS )[ 2 ], "\n" );
Mit den runden Klammern teilen wir dem Interpreter mit: Die Konstante WDAYS ist eine Liste, und von dieser möchten wir jetzt bitteschön das dritte Element ausgeben. Natürlich gibt es wie immer eine Alternative: Wenn wir die Konstante WDAYS nicht als Liste, sondern als Array-Referenz (Erklärung kommt weiter unten) deklarieren: use constant WDAYS => [ "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag" ];
dann ist es möglich, WDAYS direkt als Array anzugeben: print( WDAYS->[ 2 ], "\n" );
Auch der noch etwas seltsam anmutende Ausdruck WDAYS->[ 2 ] wird weiter unten klar werden, hier sei nur angemerkt, dass damit das dritte Element des Arrays angesprochen wird. Hinweis für Vereinfachung: Alle Elemente der Liste im Beispiel sind Strings und müssen in Quotes angegeben werden. Wenn wir den Operator qw verwenden (den ich weiter unten noch erklären werde), können wir uns Schreibarbeit sparen: use constant WDAYS => qw( Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag ); # oder als Array-Referenz: use constant WDAYS => [ qw( Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag ) ];
Eine weitere Möglichkeit, Konstanten zu definieren, bieten Funktionen. Da wir Funktionen bisher noch nicht besprochen haben, werden Sie den folgenden Programmcode vielleicht erst nach Durchlesen des Abschnitts über Funktionen verstehen: # Konstante mit "use": use constant MYCONST => 1; # Konstante mit einer Funktion: sub MYCONST { return 1; }
58
2
Grundlagen
2.6 Variablen Das Leben als Programmierer wäre ziemlich langweilig, wenn man nur mit Konstanten arbeiten könnte. Deshalb haben die Entwickler aller Programmiersprachen Variablen erfunden, die das Programmiererdasein wesentlich interessanter machen. Eine Variable ist ein Speicherplatz mit einem Namen, den man im Englischen als »Identifier« bezeichnet. In Perl wird jede Variable mit einem Typkennzeichen versehen, das vor dem Namen der Variable steht. Durch dieses Typkennzeichen kann man sogar ansonsten reservierte Namen für Variablen verwenden, was in anderen Programmiersprachen unmöglich ist. Es ist auch erlaubt, für eine skalare Variable denselben Namen zu vergeben wie für eine Array- oder Hash-Variable, da sie aufgrund des Typkennzeichens unterschieden werden können. Perl hat keine streng typisierten Variablen, die nur einen bestimmten Datentyp aufnehmen können. Es wird nur zwischen Skalaren, Arrays und Hashes unterschieden, jedoch kann eine skalare Variable sowohl Zahlen und Strings als auch Referenzen enthalten. Für die drei unterschiedlichen Datentypen sind folgende Typkennzeichen reserviert: $ für skalare Variablen (einschließlich Referenzvariablen) @ für Array Variablen % für Hash Variablen
Beispiele für Variablen in Perl: $scalarVar # Skalare Variable namens "scalarVar" @arrayVar # Array-Variable namens "arrayVar" %hashVar # Hash-Variable namens "hashVar" # Da jeder Variablentyp ein Kennzeichen besitzt, # ist auch Folgendes möglich: $var @var %var # Obwohl die Namen der Variablen gleich sind, # handelt es sich hier um drei völlig verschiedene # Variablen, da sie durch das Typkennzeichen # eindeutig gekennzeichnet sind.
Variablen
59
2.6.1 Variablennamen (Identifier) Ein Variablenname muss mit einem Buchstaben oder einem Unterstrich beginnen und darf nur aus Buchstaben, Unterstrichen sowie Ziffern bestehen. Variablennamen dürfen nicht länger als 252 Zeichen sein. (Ich glaube, dieses Limit reicht für alle denkbaren Identifier aus, selbst in Finnland.) Man sollte sich bei der Namensgebung von Variablen angewöhnen, den ersten Buchstaben des Namens klein zu schreiben. Die Ausnahme von der Regel sind Variablen, die als Konstanten benutzt werden. Deren Namen sollten komplett in Großbuchstaben sein. Alle Namen von Variablen und sonstigen Identifiern in Perl sollten grundsätzlich englischsprachig gewählt werden, denn damit schlägt man zwei Fliegen mit einer Klappe: Es gibt keine deutschen Sonderzeichen in den Namen, und jeder Programmierer auf der Welt versteht, was mit einem Namen gemeint ist, weil Englisch die Muttersprache aller Programmierer ist. Perl ist case-sensitive, d.h. zwischen Groß- und Kleinschreibung wird ein Unterschied gemacht. Beispiele: $myVar $myvar # $myvar ist eine andere Variable als $myVar # Wenn ein Variablenname aus mehreren logischen # Einheiten besteht, dann schreibt man zu Beginn # eines neuen Teils einen Großbuchstaben: $_thisIsALongVariableNameButOK # Es geht zwar auch mit dem Unterstrich als Trenner für die # einzelnen logischen Einheiten, # er sollte aber nicht verwendet werden: $_this_is_a_long_variable_name_and_should_not_be_used $123 # Ungültiger Variablenname, weil er mit einer # Ziffer beginnt $var123 # Gültiger Variablenname, # weil er mit einem Buchstaben beginnt
Ein Variablenname kann auch in geschweifte Klammern gestellt werden. Dies ist in manchen Fällen erforderlich, um den Namen einer Variable von konstantem Text zu trennen:
60
2
Grundlagen
Es soll ein Dateiname verwendet werden, der sich aus dem Inhalt der Variable $prefix und dem konstanten String _test.txt ergibt. Die folgende Zuweisung führt zu einem Fehler: my $fileName = "$prefix_test.txt";
Da der Unterstrich (_) ein gültiges Zeichen für einen Variablennamen ist, versucht der Perl-Interpreter, auf den Inhalt der Variable $prefix_test zuzugreifen, gemeint war aber die Variable $prefix, gefolgt vom konstanten Text _test.txt. Mit geschweiften Klammern kann man dieses Problem lösen: my $fileName = "${ prefix }_test.txt";
Zwischen dem Variablennamen und den geschweiften Klammern dürfen Leerzeichen stehen (dies erhöht die Lesbarkeit). Natürlich geht auch: my $fileName = "${prefix}_test.txt";
Diese Notation ist beim Pattern Matching notwendig, da in diesem Fall die Leerzeichen mit in das Pattern (deutsch: »Muster«) eingehen. Über Pattern Matching werden wir uns noch recht ausführlich unterhalten. Beispiel für das Einrahmen von Variablennamen in geschweifte Klammern und Pattern Matching: /${ prefix }_test/
führt nicht immer zum gewünschten Ergebnis, aber: /${prefix}_test/
funktioniert immer.
2.6.2 Reservierte Wörter in Perl Hier habe ich eine sehr erfreuliche Nachricht für Sie: In Perl gibt es hinsichtlich der Namensgebung von Variablen keine reservierten Wörter. Das liegt ganz einfach daran, dass vor dem Namen jeder Variable ihr Typkennzeichen stehen muss. Deshalb dürfen Sie sich alle möglichen Namen für Ihre Variablen einfallen lassen, solange sie der oben genannten Syntax entsprechen. Reservierte Wörter existieren in Perl nur für Barewords, es sind jedoch so wenige, dass man sie sich leicht merken kann. Als ich zum ersten Mal ein Perl-Buch gelesen habe, wunderte ich mich anfangs darüber, dass es keine Liste von reservierten Wörtern in Perl gab. Heute weiß ich, warum.
Variablen
61
2.6.3 Geltungsbereich von Variablen Alle Identifier (Namen von Variablen und Funktionen) haben einen mehr oder weniger eingeschränkten Geltungsbereich (englisch: »scope«), innerhalb dessen Grenzen sie gültig sind. Grundsätzlich können in Perl Variablen dort definiert werden, wo sie gebraucht werden, sie müssen also nicht unbedingt am Beginn eines Programms definiert sein. Im Gegenteil, der Geltungsbereich von Variablen sollte so klein wie möglich gehalten werden, um Nebeneffekte zu vermeiden. Dieses Ziel erreicht man durch das Bareword my, das den Geltungsbereich einer Variable so einschränkt, dass sie nur innerhalb des umgebenden Programmblocks gültig ist. Ein Programmblock ist zum Beispiel das Hauptprogramm oder die Datei eines PerlModuls. Eine Funktion ist ebenfalls ein Programmblock (richtigerweise ist der Funktionsrumpf ein Block, aber dazu später). Ganz allgemein wird ein Programmblock durch geschweifte Klammern erzeugt. Man kann auch gezielt anonyme Programmblöcke einsetzen, um kurzlebige Variablen zu benutzen, die nur innerhalb des umgebenden Blocks, begrenzt durch die geschweiften Klammern, gültig sind, z.B.: # Hauptprogramm # Die Variable $var1 ist innerhalb # des Hauptprogrammes gültig. my $var1 = 1; ... # Beginn eines anonymen Blocks, er hat im Gegensatz # zu einer Funktion keinen Namen, deshalb anonym. { # Die Variable $var3 ist nur innerhalb des Blockes, # der durch geschweifte # Klammern begrenzt ist, gültig. my $var3 = 3; # # # #
Die Variable $var1 des Hauptprogramms ist hier gültig, man kann hier aber auch eine neue Variable $var1 definieren:
my $var1 = 2; # Jetzt existiert innerhalb dieses Blocks # die äussere Variable desselben
62
2
Grundlagen
# Namens nicht mehr. # Ausserhalb des Blocks ist dann wieder nur die # äussere Variable $var1 gültig. } # An dieser Stelle ist die ursprüngliche Variable # $var1 mit dem Wert 1 wieder gültig, $var2 existiert # nicht mehr. exit( 0 ); # Ende des Hauptprogramms # Funktion "myFunc" sub myFunc { # Die Variable $var1 aus dem Hauptprogramm hat # innerhalb der Funktion # keine Gültigkeit (auch wenn Perl in diesem Fall # sehr kulant ist und # den Gebrauch der Variable meist auch in einer # Funktion zulässt). # Die folgende Variable $var2 ist nur innerhalb der # Funktion myFunc gültig. my $var2 = 2; }
Will man eine Variable innerhalb der gesamten Datei global verfügbar machen, dann kann man statt einer my-Deklaration das Bareword our verwenden: # Hauptprogramm oder Modul (*.pm) ... our $globalVar = 1; # Die Variable $globalVar ist ab der Deklaration # innerhalb derselben Datei überall gültig, # ausserdem kann die Variable von anderen Dateien über # den Packagenamen angesprochen werden, # siehe weiter unten.
Deklariert man eine Variable innerhalb eines Blocks mit our, dann gilt sie nur in diesem Block. Hier eine typische und böse Falle für Anfänger: BEGIN { our $globalVar = 1; } # Hier ist die Variable $globalVar nicht mehr gültig.
Variablen
63
Anstelle einer Deklaration mit our kann man auch mit der Direktive use Variablen des Hauptprogramms in Funktionen innerhalb derselben Programmdatei zur Verfügung stellen: # Hauptprogramm use vars ( '$var1', ); # Mit dem speziellen Operator 'qw' (quote word) geht es # auch so: use vars qw ( $var1 ); my $var1 = 1; ... sub myFunc { # Die Variable $var1 ist nun auch in der Funktion # myFunc verfügbar. }
Mit der Direktive use wird eine Liste von Variablen angegeben (deswegen die runden Klammern), die den Zustand shared erhalten, d.h. sie sind auch in Funktionen innerhalb derselben Datei verfügbar. Die List-Elemente müssen in einfache Quotes gesetzt werden, da sonst der Interpreter den Wert der Variablen einsetzen würde. Das Sonderzeichen $ muss also mit einfachen Quotes entwertet werden. Beispiel: # Die folgende Zeile führt zu einem Fehler: use strict; use vars ( "$myVar" ); my $myVar = 1;
Wenn wir versuchen, den Programmcode auszuführen, dann passiert Folgendes (der Code wurde direkt über die Kommandozeile der Shell eingegeben): D:\>perl -w use strict; use vars ( "$myVar" ); Global symbol "$myVar" requires explicit package name at - line 2.
Wenn wir die zweite Zeile des Codes mit einem Zeilenvorschub abgeschickt haben, kommen wir gar nicht mehr dazu, den Rest einzutippen, weil der Interpreter sofort die Fehlermeldung ausgibt und das Programm beendet. Unser Fehler war, so meinen wir
64
2
Grundlagen
aufgrund der Fehlermeldung, dass wir die Variable $myVar vorher deklarieren müssen, also noch ein Versuch: D:\>perl -w use strict; my $myVar = 1; use vars ( "$myVar" ); Use of uninitialized value in string at - line 3. '' is not a valid variable name at - line 3 BEGIN failed--compilation aborted at - line 3. D:\>
Was ist das nun? Perl beschwert sich darüber, dass die Variable $myVar nicht initialisiert ist, aber wir haben ihr doch eine Zeile vorher einen definierten Wert gegeben! Auskunft gibt uns die letzte Zeile der Fehlermeldung: Die Direktive use wird anscheinend ausgeführt, bevor die Deklarationszeile darüber interpretiert wird (siehe auch »BEGIN-Block von Perl-Programmen«). Richtig, die use-Direktive wird zur Übersetzungszeit vom Interpreter abgearbeitet, während das Statement darüber erst im nächsten Schritt gelesen wird. Damit kann die Variable natürlich noch nicht initialisiert sein. Der eigentliche Fehler jedoch steckt in den doppelten Anführungszeichen der Direktive use. Damit versucht der Interpreter, den Wert der noch nicht vorhandenen Variable einzusetzen, was natürlich unmöglich ist. Also machen wir es jetzt richtig: D:\>perl -w use strict; use vars ( '$myVar' ); # Es geht auch mit dem Operator "qw": # use vars qw( $myVar ); my $myVar = 1; ^Z D:\>
So, jetzt ist unsere Welt wieder in Ordnung. Keine Fehlermeldung, kein Problem. Durch die einfachen Quotes wird vom Interpreter nicht versucht, den Variablenwert zu ermitteln, da das Dollarzeichen $ nun ein ganz normales Zeichen wie A oder x ist. Will man Variablen auch Programmcode zur Verfügung stellen, der nicht in derselben Datei steht (z.B. in Perl-Modulen), dann kann man diese nicht mit dem Bareword my deklarieren.
Variablen
65
Allerdings verhindert die Anweisung use strict;, dass man den Geltungsbereich der Variablen einfach weglässt. So liefert folgender Code eine Fehlermeldung des Interpreters: # Perl-Modul Util.pm package Util; use strict; # Diese Anweisung führt zu einer Fehlermeldung: $var = 5;
Im Beispielcode ist die Anweisung package enthalten, welche dazu führt, dass der Interpreter den darauf folgenden Code in einem eigenen Geltungsbereich, oft auch Namespace genannt, verwaltet. In unserem Beispiel hat der Geltungsbereich den Namen »Util«. Auch Hauptprogramme haben einen eigenen Namespace, ohne dass man diesen explizit angeben muss, da er vordefiniert ist und den Namen main trägt. Man kann dies auch explizit angeben: #!D:/Perl/bin/perl.exe -w package main; use strict; ... exit( 0 );
Will man nun Variablen deklarieren, die von Programmcode aus anderen Dateien heraus angesprochen werden sollen, muss man den voll qualifizierten Geltungsbereich für die Variablen angeben. Diesen erhält man, indem man zwischen das Typkennzeichen der Variable und den Namen der Variable den Namen des Packages schreibt und vom Variablennamen durch einen doppelten Doppelpunkt »::« trennt: # Perl-Modul (Datei Util.pm) package Util; use strict; # Variablendeklaration mit Angabe des # voll qualifizierten Geltungsbereichs $Util::var = 5; # Auch die Deklaration mit der our-Direktive # ist möglich: our $var1 = 10;
66
2
Grundlagen
Benutzt werden können so definierte Variablen von Programmcode in anderen Dateien wie folgt: # Hauptprogramm # Mit der folgenden Direktive lädt der Interpreter das # Modul Util: use Util; print( $Util::var, "\n" ); print( $Util::var1, "\n" );
Globale Variablen eines Skripts können natürlich auch im Hauptprogramm mit der voll qualifizierten Angabe des Geltungsbereichs verwendet werden: #!/usr/bin/perl -w use strict; $main::debugLevel = 1; ... print( "Debug level = $main::debugLevel\n" );
Allerdings lege ich Ihnen die Deklaration solcher globalen Variablen, die innerhalb der gesamten Datei gültig sind, mit dem Bareword our ans Herz, denn damit spart man sich lästige Schreibarbeit.
2.6.4 Skalare Variablen Skalare Variablen werden mit dem Typkennzeichen $ versehen, das vor dem Variablennamen steht. Perl wandelt unterschiedliche skalare Variablentypen um, falls dies möglich ist: my $var1 = 1; my $var2 = "3"; # Addition my $var3 = $var1 + $var2; # $var3 enthält 4 # Aneinanderhängen von Strings my $var4 = $var1 . $var2; # $var4 enthält "13";
Eine skalare Variable ist also nicht auf einen bestimmten skalaren Typ festgelegt, sondern kann zu verschiedenen Zeitpunkten alle mögliche Arten von skalaren Werten enthalten. Perl wandelt den Typ automatisch um. Allerdings liefert der Interpreter eine Fehlermeldung, wenn die implizite Umwandlung eines Datentyps aufgrund von Inkompatibilität unmöglich ist:
Variablen
67
my $var1 = 1; my $var2 = "a"; my $var3 = $var1 + $var2; # Fehlermeldung $var3 = $var1 . $var2; # Zulässig, da hier in String # umgewandelt wird
Aber folgendes Beispiel funktioniert: my $v1 = "1\n"; my $v2 = 5; print( $v1 + $v2 ); # Es wird "6" ausgegeben, da Zeilenende-Zeichen bei der # Konvertierung entfernt werden
Normalerweise werden Sonderzeichen in Strings, die durch einfache Quotes definiert werden, nicht als Sonderzeichen, sondern als literale Zeichen interpretiert. Ist ein einfaches Quote Bestandteil eines Strings, der durch doppelte Quotes definiert wurde, dann ist das einfache Quote kein Sonderzeichen mehr. Deswegen wird die Variable im folgenden Beispiel evaluiert (der gespeicherte Variablenwert wird eingesetzt): my $val = 50; my $string = "Inhalt der Variable var = '$var'"; # $string enthält "Inhalt der Variable var = '50'"
Macht man es umgekehrt: $string = 'Inhalt der Variable var = "$var"';
dann wird $var nicht evaluiert, sondern der String enthält exakt die Zeichenkette: Inhalt der Variable var = "$var"
Alle skalaren Variablen können den Pseudowert undef enthalten. In diesem Fall ist Vorsicht bei der Verarbeitung geboten. So können keine Variablen ausgegeben werden, die undef sind. Perl liefert hier eine Laufzeit-Warnung. Beispiel: my $var; # Die folgende Anweisung liefert eine Laufzeit-Warnung, # da die Variable $var # nicht initialisiert ist. print( "var = $var\n" );
68
2
Grundlagen
Besonders lästig sind Listen (in der Regel Arrays), die mit der print()-Funktion von Perl ausgegeben werden sollen. Enthält ein Element der Liste den Wert undef, dann wird eine Laufzeitmeldung ausgegeben: print( ( 1, 2, undef, 4, 5 ) ); # Beim Versuch, das dritte Element auszugeben, erzeugt # die print()-Funktion eine Warnungsmeldung.
Definition von Variablen Wie oben bereits erwähnt, sollte man Variablen immer mit dem kleinstmöglichen Geltungsbereich definieren, um unerwünschte Nebeneffekte zu vermeiden. In der Regel wird dafür das Bareword my verwendet. Meist werden Variablen bei der Deklaration gleich initialisiert: # Variable, die später mit einem brauchbaren Wert # belegt wird. Sie sollte zumindest mit "undef" # initialisiert werden. my $toBeFilledLater = undef; # Variable, die bei ihrer Deklaration selbst # bereits mit dem Rückgabewert einer Funktion # initialisiert wird. my $date = localtime();
Will man mehrere Variablen gemeinsam definieren und initialisieren, dann verwendet man den List-Operator in Form von runden Klammern: my ( $secs, $mins, $hours ) = localtime();
Diese Art, Variablen zu definieren, wird sehr häufig verwendet, vor allem auch in Funktionen, wie wir später sehen werden. Die Codezeile enthält mehr als nur eine Initialisierung mehrerer Variablen: Durch den List-Operator in Form von runden Klammern entsteht eine Zuweisung an eine Liste. Die Perl-Funktion localtime() wird also in List-Kontext aufgerufen und gibt somit keinen skalaren String, sondern eine Liste zurück (mehr Informationen über localtime() finden Sie in Anhang C). Diese Liste rechts vom Gleichheitszeichen wird nun in die links vom Gleichheitszeichen stehende Liste kopiert. $secs erhält das erste Element der Rückgabeliste, $mins das zweite, und $hours das dritte Element. Alle weiteren Rückgabeelemente werden verworfen. Hätten wir die Zuweisung so geschrieben: my $secs, $mins, $hours = localtime();
dann würden wir sofort eine Fehlermeldung vom Interpreter »ernten«, weil der ListOperator fehlt.
Variablen
69
Wichtig ist auch, dass Sie sich Folgendes einprägen: # Aufruf der Funktion und Zuweisung an $secs # in List-Kontext: # Die Funktion liefert eine Liste zurück. # Deren erstes Element (die Sekunden) # wird in das erste Element der links vom # Gleichheitszeichen stehenden Liste kopiert, # also in $secs abgespeichert. my ( $secs ) = localtime(); # Dasselbe in skalarem Kontext: # Die Funktion liefert einen skalaren String, # der direkt in der Variable abgespeichert wird. my $secs = localtime(); # Mit der Perl-Funktion "scalar()" wird ein # skalarer Kontext für den Funktionsaufruf # von localtime() erzwungen, sie gibt also # einen skalaren String zurück. # Dieser wird an eine Liste bestehend aus einem # Element (unsere zu definierende Variable) # zugewiesen. Der Perl-Interpreter wandelt # hier implizit den skalaren Rückgabewert # in eine Liste um und kopiert das erste # Element in die Variable $secs. my ( $secs ) = scalar( localtime() );
Wir werden dieses Konstrukt der Variablendefinition noch sehr häufig antreffen, merken Sie sich also bitte: Eine Variablendefinition mit dem List-Operator stellt immer List-Kontext her.
2.6.5 Array-Variablen Array-Variablen werden mit dem Typkennzeichen @ versehen, das vor den Variablennamen gestellt wird. Die Elemente eines Arrays müssen wiederum Skalare sein. Im Gegensatz zu skalaren Variablen können Array-Variablen niemals undef sein: Ein Beispiel, wie man es nicht machen sollte: my @array = undef; # @array ist dennoch definiert, es enthält 1 Element, # das den Wert undef hat: ( undef, ) # Der Interpreter liefert keine Fehlermeldung!
70
2
Grundlagen
Initialisiert werden Array-Variablen mit dem List-Operator: # leeres Array my @array = (); # Array mit 3 Elementen my @array1 = ( 1, "drei", 5, );
Auf einzelne Elemente des Arrays kann man durch Angabe des numerischen Index in eckigen Klammern zugreifen: $array1[ 0 ] # greift auf den skalaren Wert des ersten # Elements zu, das die Zahl 1 enthält $array1[ 2 ] = 1; # Das 3. Element des Arrays erhält den # Wert 1
Wir haben gelernt, dass ein Dollarzeichen $ als Typkennzeichen vor dem Variablennamen auf eine skalare Variable hindeutet. Die Variable array1 ist jedoch eine ArrayVariable. Ist das ein Widerspruch? Mitnichten. Mit dem Ausdruck $array1[ 0 ]
greifen wir ja nicht auf das Array als solches zu, sondern vielmehr auf ein Element des Arrays. Da Elemente von Arrays grundsätzlich Skalare sein müssen, stimmt die Sache wieder. Dass die Variable array1 wirklich eine Array-Variable ist, erkennt man daran, dass hinter dem Variablennamen eine eckige Klammer steht. Natürlich kann man auch den Index in einer Variable speichern und diese anstelle einer konstanten Nummer verwenden: my $ind = 2; $array1[ $ind ] # # # #
Greift auf das Element mit dem Index zu, der durch die Variable $ind angezeigt wird (hier also auf das 3. Element).
Mehrere Array-Elemente können gezielt durch eine kommaseparierte Liste von Indizes extrahiert werden (beachte das @-Zeichen anstelle von $): my @extract = @array1[ 0,2 ]; # @extract enthält ( 1, 5 ) $array[ 0,2 ] ist falsch, @array[ 0,2 ] ist richtig!
Wir wollen ja nicht ein skalares Element extrahieren, sondern eine Teilmenge des Arrays. Diese ist zwangsläufig wiederum eine Array-Liste.
Variablen
71
Beispiel für den Auszug einiger Werte von localtime(): # Zuweisung des Monats, der Stunde und der Minute # an entsprechende Variablen über eine # List-Definition mit Zuweisung aus einer # Teilliste: my ( $mon, $hours, $minutes ) = ( localtime() )[ 4, 2, 1 ];
Perl bietet im Zusammenhang mit Arrays eine spezielle Variable an, die den Index des letzten Elements des Arrays enthält. Der Name dieser (skalaren) Variable wird gebildet aus $# und dem Namen der Array-Variable: # Leeres Array initialisieren my @array = (); # Index des letzten Elements lesen my $lastIndex = $#array; # $lastIndex enthält -1, da das Array leer ist. # Array neu initialisieren @array = ( 1, 3, 4 ); # $#array enthält nun 2 (Index des letzten Elements)
Man kann auch ein Array mit einer bestimmten Anzahl von Elementen initialisieren: # Leeres Array initialisieren my @array = (); # Index des letzten Elements auf die Zahl 999 setzen $#array = 999; # Es wird Speicher für 1000 Elemente "reserviert". # Die einzelenen Elemente sind allerdings alle "undef".
Mit der Funktion scalar() kann man die Anzahl der Elemente eines Arrays erhalten (die Funktion wird im Anhang C ausführlich beschrieben): my @array = ( 1, 10, "hallo", ); my $elementCount = scalar( @array ); # $elementCount enthält den Wert 3
Um zu prüfen, ob ein Array leer ist, hat man folgende Möglichkeiten (die jetzt noch nicht bekannten Begriffe unless und if werden später erklärt): # Alle folgenden Ausdrücke liefern TRUE, # wenn das Array leer ist, ansonsten liefern # sie FALSE. unless ( @arrayVar ) { ... } if ( $#arrayVar < 0 ) { ... } unless ( scalar( @arrayVar ) ) { ... } if ( scalar( @arrayVar ) == 0 ) { ... }
72
2
Grundlagen
Man kann eine Array-Variable mit mehreren Arrays initialisieren: my @a1 = ( 1, 2, ); my @a2 = ( 3, 4, ); my @a3 = (0, @a1, @a2, 5, ); # @a3 enthält ( 0, 1, 2, 3, 4, 5, ) # Alle Elemente von @a1 und @a2 werden in @a3 kopiert
Mit den Funktionen unshift() und push() kann man neue Elemente hinzufügen (die Funktionen unshift() und push() sind in Anhang C ausführlich beschrieben): my @array = ( 1, 2, 3, ); # Neues Element am Beginn eines Arrays einfügen unshift( @array, 0, ); # @array enthält jetzt ( 0, 1, 2, 3, ) # Neues Element am Ende des Arrays anhängen push( @array, 4 ); # @array enthält jetzt ( 0, 1, 2, 3, 4, ) # Beide Funktionen arbeiten auch mit Listen: push( @array, ( 5, 6, ) ); # @array enthält jetzt ( 0, 1, 2, 3, 4, 5, 6, ) # Dasselbe am Anfang, statt Liste in runden Klammern # werden die einzelnen neuen Elemente als variable # Argumente an unshift() übergeben: unshift( @array, -2, -1 ); # @array enthält jetzt ( -2, -1, 0, 1, 2, 3, 4, 5, 6, )
Mit den Funktionen shift() und pop() kann man Elemente am Beginn bzw. Ende des Arrays entfernen (die Funktionen shift() und pop() sind in Anhang C ausführlich beschrieben): my @array = ( 0, 1, 2, 3, 4, 5, ); # Erstes Element entfernen my $ele = shift( @array ); # @array enthält jetzt ( 1, 2, 3, 4, 5, ) # $ele enthält 0 # Letztes Element entfernen $ele = pop( @array ); # @array enthält jetzt ( 1, 2, 3, 4, ) # $ele enthält 5
Variablen
73
Mit der Funktion splice() kann man sowohl beliebige Elemente an beliebiger Stelle hinzufügen als auch entfernen (die Funktion splice() ist in Anhang C ausführlich beschrieben): my @array = ( 1, 2, 3, 4, ); # Zweites Element entfernen my $ele = splice( @array, 1, 1 ); # @array enthält jetzt ( 1, 3, 4, ) # $ele enthält 2 # Vorletztes Element entfernen $ele = splice( @array, -2, 1 ); # @array enthält jetzt ( 1, 4, ) # $ele enthält 3 # Elemente ab Index 1 einfügen splice( @array, 1, 0, 2, 3 ); # @array enthält jetzt ( 1, 2, 3, 4, ) # Zweites und drittes Element entfernen und 3 neue # Elemente einfügen my @removed = splice( @array, 1, 2, "zwei", "drei", "dreieinhalb" ); # @array enthält jetzt ( 1, "zwei", "drei", "dreieinhalb", 4, ) # @removed enthält ( 2, 3, )
In skalarem Kontext liefert die Funktion splice() das letzte entfernte Element zurück (oder undef, falls kein Element entfernt wurde), in List-Kontext werden alle entfernten Elemente zurückgeliefert. Die Funktion splice() wird, ebenso wie alle anderen verwendeten Funktionen, in Anhang C noch ausführlich behandelt. Mit der Funktion join() kann man ein Array in einen Skalar umwandeln. Jedoch ist Vorsicht geboten, wenn einzelne Elemente des Arrays den Pseudowert undef haben: my @array = ( 1, 2, 3, 4, ); my $scalar = join( "", @array ); # $scalar enthält "1234" $scalar = join( ", ", @array ); # $scalar enthält "1, 2, 3, 4" # Vorsicht bei undef-Elementen my @array1 = ( 1, undef, 2 ); # Die folgende Anweisung ergibt eine Laufzeitwarnung: print( join( ", ", @array ), "\n" );
74
2
Grundlagen
Mit der Funktion split() kann man eine skalare Variable in ein Array umwandeln: my $scalar = 12345; my @array = split( "", $scalar ); # @array enthält ( 1, 2, 3, 4, 5, ) $scalar = "Das ist ein Satz"; @array = split( " ", $scalar ); # @array enthält ( "Das", "ist", "ein", "Satz", ) # Gleiches Beispiel, nur wird nun das Leerzeichen mit in # das Array übernommen @array = split( "( )", $scalar ); # @array enthält ( "Das", " ", "ist", " ", "ein", " ", "Satz", )
Mit der Funktion reverse() kann man die Elemente eines Arrays in der Reihenfolge vertauschen: my @array = ( 1, 2, 3, 4, ); @array = reverse( @array ); # @array enthält jetzt ( 4, 3, 2, 1, )
Mit der Funktion sort() kann man Arrays sortieren: my @array = ( 1, 17, 25, 3, -1 ); my @sortedArray = sort( @array ); # @sortedArray enthält ( -1, 1, 17, 25, 3, )
Wie man sieht, wurde das Array nicht numerisch, sondern lexikalisch sortiert. Das ist die Standardeinstellung von sort(). Will man numerisch sortieren, muss man den Operator <=> verwenden (keine Angst, auch diese Funktion wird noch mit einigen Beispielen erklärt). @sortedArray = sort( { $a <=> $b } @array ); # @sortedArray enthält ( -1, 1, 3, 17, 25, )
Zwischen den geschweiften Klammern und dem zu sortierenden Array darf kein Komma stehen. Dreht man die innerhalb der geschweiften Klammern vordefinierten Variablen $a und $b um, kann man umgekehrt numerisch sortieren: @sortedArray = sort( { $b, <=> $a } @array ); # @sortedArray enthält ( 25, 17, 3, 1, -1, )
Variablen
75
Mit dem Operator .. und der Funktion rand() lassen sich auf einfache Weise zufällige Zeichenfolgen erzeugen: ... # Programmcode, der einen Zufallsstring # mit 10 Zeichen Länge erzeugt und ausgibt # Hinweis: Die Variablen @cs und $rs sollten # eigentlich @characters und $randomString # heißen, wurden jedoch aus Platzgründen # abgekürzt. my @cs = ( "A" .. "Z", "a" .. "z", "0" .. "9" ); my $rs = ""; for ( my $i = 1; $i <= 10; $i++ ) { $rs .= $cs[ int( rand( scalar( @cs ) ) ) ]; } print( "zufällige Zeichenfolge = '$rs'\n" );
Diese Anweisungen werden schon noch klar werden, doch an dieser Stelle möchte ich Ihnen zeigen, was man mit Arrays alles anfangen kann, das Wie kommt später. Nicht vorenthalten möchte ich Ihnen hier aber wichtige Informationen über Arrays: my @cs = ( "A" .. "Z", "a" .. "z", "0" .. "9" );
Bei der Initialisierung der Array-Variable @cs habe ich den speziellen Operator .. verwendet, der sich immer bei monoton aufsteigenden Reihen anbietet. "A" .. "Z" ist die Kurzfassung für "A", "B", "C" etc., "Z". Gleiches gilt für die Kleinbuchstaben und die Ziffern. Der Ausdruck int( rand( scalar( @cs ) ) )
liefert eine ganze Zufallszahl zwischen 0 und $#cs und damit einen gültigen Index innerhalb der Arraygrenzen. Der Operator .= hängt den Ausdruck rechts des Operators an die Variable links vom Operator an, das sei hier vorweggenommen. Hinweis für Wissbegierige: Anstelle von int() könnte man auch POSIX::floor() verwenden. In diesem Fall muss das Package POSIX mit der Anweisung use POSIX; geladen werden.
Mehrdimensionale Arrays Die meisten Dinge im Leben sind mehrdimensional und damit kompliziert. Bisher haben wir uns nur um einfache, eindimensionale Arrays gekümmert, doch das soll
76
2
Grundlagen
sich ab sofort ändern. Die Frage ist: Wie kann man in Perl mehrdimensionale Arrays erstellen und benutzen? Die Antwort werden wir weiter unten sehen, wenn Sie wissen, was Referenzvariablen sind.
2.6.6 Hash-Variablen Hash-Variablen werden mit dem Typkennzeichen % vor dem Variablennamen versehen. Initialisiert werden Hash-Variablen mit dem bereits von Arrays bekannten List-Operator bestehend aus zwei runden Klammern. Key und Value jedes einzelnen Hash-Elements werden durch den Operator => voneinander getrennt. Die Values eines Hashs müssen Skalare sein. Beispiele für die Initialisierung von Hashes: # Leeres Hash initialisieren my %hash = (); # Hash mit 2 Elementen initialisieren my %hash1 = ( "fn" => "Hugo", "ln" => "Hofmannsthal", ); # Hinweis: Die folgende Initialisierung führt zu # einer Fehlermeldung, da Hash-Variablen niemals # undef sein können. my %hash = undef;
Auch bei Hash-Initialisierungen gilt, wie schon von Arrays bekannt: Nach dem letzten Element darf (und sollte) ebenfalls ein Komma stehen. Auf einzelne Elemente eines Hashs greift man immer über den Key zu, der in geschweiften Klammern angegeben wird: my $firstname = $hash{ "fn" }; # $firstname enthält den Value des Elements, # das durch den Key "fn" # identifiziert wird, hier also "Hugo". # Vergleiche die Analogie zu Arrays: # $array[ $index ] # $hash{ $key }
Wir haben gelernt, dass ein Dollarzeichen $ als Typkennzeichen vor dem Variablennamen auf eine skalare Variable hindeutet. Die Variable hash ist jedoch eine Hash-Variable. Ist das ein Widerspruch?
Variablen
77
Mitnichten. Es gilt dasselbe wie schon vorher bei Arrays. Mit dem Ausdruck $hash{ "fn" }
greifen wir ja nicht auf das Hash als solches zu, sondern vielmehr auf ein Element des Hash. Da Elemente von Hashes grundsätzlich Skalare sein müssen, stimmt die Sache wieder. Dass die Variable hash wirklich eine Hash-Variable ist, erkennt man daran, dass nach dem Variablennamen eine geschweifte Klammer steht. Ein Hash-Element wird angelegt, indem man eine Zuweisung über den Key durchführt: my %hash = (); $hash{ "fn" } = "Egon"; # Hinweis: Auch die folgende Zuweisung ist möglich: $hash{ fn } = "Egon"; # Perl interpretiert Hash-Keys immer als Strings, # deshalb wird das Bareword "fn" implizit in einen # String umgewandelt.
Wenn bereits ein Element mit demselben Key existiert, dann überschreibt man den vorher gespeicherten Value des Hash-Elements, da jeder Key eindeutig ist, also nur einmal vorkommen kann. Beispiel: my %h = ( "fn" => "Hugo", ); $h{ "fn" } = "Egon"; # Der ursprüngliche Wert "Hugo" des Elements, das durch # den Key "fn" identifiziert wird, enthält nun "Egon".
Löschen kann man ein Hash-Element mit der delete()-Funktion: Die delete()-Funktion sowie alle weiteren hier besprochenen Funktionen werden weiter unten sowie in Anhang C noch ausführlich besprochen. delete( $hash{ "fn" } ); # Löscht das Hash-Element (Key und Value) mit dem Key # "fn"
Mit der Funktion exists() kann abgefragt werden, ob ein Hash-Element mit dem angegebenen Key existiert oder nicht: if ( exists( $hash{ "age" } ) ) { # Es existiert ein Element mit dem Key "age". # Hinweis: Es wird nicht auf den Value zugegriffen, # sondern nur auf den Key. } else { # Es gibt kein Element mit dem Key "age". }
78
2
Grundlagen
Will man den Zustand des Wertes anstelle des Schlüssels prüfen, muss man zum Beispiel folgende Abfrage machen: if ( $hash{ "age" } ) { # Es existiert ein Element mit dem Key "age" und der # Value ist logisch TRUE # (er hat also nicht den Wert "0", 0, "" oder undef). }
Mit der Funktion defined() ist es möglich, festzustellen, ob der Value des Hash-Elements mit dem angegebenen Key einen definierten Wert hat oder nicht: if ( defined( $hash{ "age" } ) ) { # Es existiert ein Hash-Element mit dem Key "age" # und der Wert des Elements ist definiert # (kann also auch die Werte # "0", 0 oder "" enthalten). }
Ähnlich wie bei Arrays kann man in einer Schleife nacheinander auf alle Hash-Elemente zugreifen, allerdings nicht geordnet nach aufsteigenden Indizes, Stattdessen erhält man eine unsortierte Liste der Hash-Keys mit der Perl-Funktion keys(): my %hash = "key1" "key2" "3" => );
( => 1, => 2, "value3"
foreach my $key ( keys( %hash ) ) { my $val = $hash{ $key }; print( "key = $key, value = $val\n" ); } # Man kann die Liste der Keys auch in einer Array # Variable zwischenspeichern. # Damit kann man eine Laufvariable verwenden, um # direkt auf die Keys des Arrays zuzugreifen. my @keys = keys( %hash ); for ( my $i = 0; $i <= $#keys; $i++ ) { my $val = $hash{ $keys[ $i ] }; print( "key = $keys[ $i ], value = $val\n" ); }
Die noch unbekannten Begriffe for und foreach folgen demnächst. Da die Keys eines Hashs unsortiert abgelegt sind, kann man natürlich nicht erwarten, von der Funktion keys() eine sortierte Liste zurückzubekommen. In unserem obigen Beispiel könnte die Ausgabe also zum Beispiel so aussehen:
Variablen
79
key = key1, value = 1 key = key2, value = 2 key = 3, value = value3
Es könnte aber auch erscheinen: key = key2, value= 2 key = 3, value = value3 key = key1, value = 1
Mit der sort()-Funktion kann man die Liste der zurückgelieferten Keys beliebig sortieren: my %hash = "key1" "key2" "3" => );
( => 1, => 2, "value3"
# Alphabetische Sortierung der Keys foreach my $key ( sort( keys( %hash ) ) ) { my $val = $hash{ $key }; print( "key = $key, value = $val\n" ); } # Umgekehrte alphabetische Sortierung der Keys foreach my $key ( reverse( sort( keys( %hash ) ) ) ) { my $val = $hash{ $key }; print( "key = $key, value = $val\n" ); }
Eine weitere Möglichkeit, auf Hash-Elemente nacheinander zuzugreifen, bietet die each()-Funktion, mit der man in einem Schleifendurchlauf sowohl den Key als auch den zugehörigen Value bekommt: my %hash = "key1" "key2" "3" => );
( => 1, => 2, "value3"
while ( my ( $key, $val ) = each( %hash ) ) { print( "key = $key, value = $val\n" ); }
Mit der Funktion values() kann man eine Liste der Values eines Hashs erzeugen, ohne dass Keys direkt involviert sind. Wie die zurückgegebenen List-Elemente sortiert werden, kann nicht beeinflusst werden. Man kann nicht davon ausgehen, dass die zurückgegebenen List-Elemente in irgendeiner Weise sortiert sind:
80
2
Grundlagen
my %hash = ( "key1" => 1, "key2" => 2, "3" => "value3" ); my @values = values( %hash ); # @values enthält # ( 2, 1, "value3", ) # oder ( 2, "value3", 1, ) # oder ( "value3", 2, 1, ) # oder ( "value3", 1, 2, ) # oder ( 1, 2, "value3", ) # oder ( 1, "value3", 2, )
2.6.7 Referenzvariablen Während alle bisherigen Variablentypen einem bestimmten Datentyp zugeordnet waren, können Referenzvariablen alle denkbaren Typen enthalten (obwohl die Referenzvariable selbst natürlich immer eine skalare Variable ist). In einer Referenzvariable wird die Adresse eines Wertes oder einer anderen Variable oder auch einer Funktion bzw. eines anonymen Codeblocks gespeichert. Sie sind also ähnlich aufzufassen wie Pointer-Variablen aus der Programmiersprache C. Da Referenzvariablen Skalare sind, werden sie mit dem Typkennzeichen $ versehen. Ich möchte Referenzvariablen anhand des folgenden Schaubildes verdeutlichen:
Abbildung 2.1: Referenzvariablen
Erläuterungen: In der Symboltabelle speichert der Perl-Interpreter alle bekannten Identifier (das sind die Namen von Variablen und Funktionen) eines Programms mit ihrer Adresse im Hauptspeicher ab.
Variablen
81
Angenommen, die erste freie Adresse im Hauptspeicher beginnt bei 0 (hex 0x00), und wir besitzen (immer noch) einen 32-Bit-Rechner, dann enthält die Symboltabelle des folgenden Programms: #!/usr/bin/perl -w use strict; my $var1 = 7; my $ref1 = \$var1;
in vereinfachter Form folgende Einträge: Symbol
Adresse
$var1
0x00
$ref1
0x04
Die skalare Variable $var1 wird in der Länge 4 Byte im Hauptspeicher unter der Adresse 0x00 gespeichert, der Wert dieser Speicheradresse ist 7. Die ebenfalls skalare Variable $ref1 wird in der Länge 4 Byte im Hauptspeicher unter der nächsten freien Adresse gespeichert, also unter 0x04. Da die Variable keinen normalen Wert, sondern die Adresse von $var1 enthält, ist im Hauptspeicher für die Referenzvariable der Wert 0x00 gespeichert, die Referenzvariable »zeigt« also auf die Adresse der Variable $var1.
Definition von Referenzvariablen Wenn Referenzvariablen als Zeiger auf andere Variablen oder auf Funktionen definiert werden, stellt man einen Backslash »\« vor die Variable, auf welche die Referenzvariable zeigen soll. Bei Funktionsreferenzen muss nach dem Backslash zusätzlich ein Kaufmännisches Und »&« angegeben werden, um dem Interpreter mitzuteilen, dass der darauf folgende Identifier der Name einer Funktion ist: # Hier ein paar Definitionen von normalen Variablen my $scalar = 5; my @array = ( 1, 2, ); my %hash = ( "firstname" => "Hugo", "age" => 39, ); # Und nun die Definition einer Funktion mit dem # Namen "myFunc". Sie tut nichts anderes, als alle # Argumente der Funktion auszugeben. sub myFunc { my ( $arg ) = @_;
82
2
Grundlagen
print( "$arg\n" ); } # Definition einer Referenzvariable auf $scalar my $scalarRef = \$scalar; # Die Variable "$scalarRef" enthält die Speicheradresse # der Variable "$scalar". Deren Wert kann nun sowohl # über $scalar = 5; # als auch über $$scalarRef = 5; # verändert werden. Machen Sie sich keine Gedanken # über das komische "$$", die Erklärung kommt noch. # Definition einer Referenzvariable auf @array my $arrayRef = \@array; # Die Variable "$arrayRef" enthält die Speicheradresse # der Variable "@array". Man kann nun auf die Elemente # des Arrays entweder über @array oder über $arrayRef # zugreifen. Beispiele: # Das 4. Element des Arrays bekommt den Wert 5 $array[ 3 ] = 5; $arrayRef->[ 3 ] = 5; # Die Bedeutung von "->" wird später noch erklärt. # Definition einer Referenzvariable auf %hash my $hashRef = \%hash; # Die Variable "$hashRef" enthält die Speicheradresse # der Variable "%hash". Man kann nun auf die Elemente # des Hashs entweder über %hash oder über $hashRef # zugreifen. Beispiele: $hash{ "firstname" } = "Egon"; $hashRef->{ "firstname" } = "Egon"; # Die Bedeutung von "->" wird später noch erklärt. # Definition einer Referenzvariable auf die Funktion # myFunc my $funcRef = \&myFunc; # Beachte: nach dem Backslash muss ein Kaufmänisches Und "&" # stehen, damit Perl weiß, dass es sich um eine # Funktion handelt. # Normaler Aufruf der Funktion: myFunc( "bla" ); # Aufruf der Funktion über die Referenzvariable: &{ $funcRef }( "bla" );
Man kann auch anonyme Referenzen mit Hilfe von Referenzvariablen definieren, das sind Referenzen, die nicht auf andere Variablen oder Funktionen zeigen, sondern direkt auf die Daten bzw. den Programmcode:
Variablen
83
# Direkte Referenzvariable auf ein Array: # Anstelle der bei Listen üblichen runden Klammern # werden hier eckige Klammern verwendet. my $refArray = [ 1, 2, 3, ]; # Direkte Referenzvariable auf ein Hash: # Anstelle der bei Listen üblichen runden Klammern # werden hier geschweifte Klammern verwendet. my $refHash = { "key1" => "value1", "key2" => "value2", }; # Direkte Referenzvariable auf Programmcode, # in diesem Fall auf eine anonyme Funktion ohne Namen: # Beachte: Nach der schliessenden geschweiften Klammer # muss ein Strichpunkt stehen, da es sich hier nicht um # eine Funktionsdeklaration, sondern um eine Zuweisung # einer anonymen Funktion an eine Variable handelt. # Die Funktion ist anonym, weil sie keinen Namen # hat. Sie kann nur über die Referenzvariable # $refFunc aufgerufen werden. # Damit kann man wunderschöne private Funktionen # schreiben, die nur innerhalb derselben Datei # bekannt sind! Mehr hierzu bei Objektorientierter # Programmierung. my $refFunc = sub { my ( $arg ) = @_; print( "$arg\n" ); }; # Aufruf der Funktion über die Referenzvariable: &{ $refFunc }( "bla" ); # # # # #
Da die Variable "$refFunc" mit der Deklaration durch 'my' nur im umgebenden Codeblock bekannt ist, kann sie auch nur dort verwendet werden. Damit ist die Funktion, auf die "$refFunc" zeigt, privat gemacht und nach außen unbekannt.
Dereferenzierung von Referenzvariablen Unter Dereferenzierung versteht man den Zugriff auf den Wert derjenigen Variablen, auf die eine Referenzvariable zeigt. Für die unterschiedlichen Dereferenzierungsarten gelten folgende Regeln:
Dereferenzierung von skalaren Variablen Die Dereferenzierung von skalaren Referenzvariablen erfolgt durch ein Voranstellen von $ vor die Referenzvariable (einschließlich des Typkennzeichens).
84
2
Grundlagen
Im folgenden Programmcode versuchen wir, den Wert einer Referenzvariablen direkt auszugeben: # Definition einer skalaren Variable my $scalar = 5; # Definition einer Referenzvariable auf "$scalar" my $scalarRef = \$scalar; # Während über $scalar direkt der Wert der Variable # angesprochen wird, # erhalten wir über $scalarRef zunächst die # Hauptspeicheradresse von $scalar. print( "scalar = $scalar\n" ); print( "scalarRef = $scalarRef\n" );
Wenn wir den Code ausführen, dann erhalten wir folgende Ausgabe: scalar = 5 scalarRef = SCALAR(0x1a72f04)
Wie wir sehen, wird für die normale skalare Variable direkt der im Hauptspeicher abgelegte Wert ausgegeben, während bei der Referenzvariable die Adresse erscheint, auf welche die Variable zeigt (zusätzlich zur Information, dass es sich um eine Referenzvariable auf eine skalare Variable handelt). Der Wert 5 unserer normalen Variable $var1 ist also im Hauptspeicher unter der Adresse 0x1a72f04 abgelegt. Damit wir auf den Wert des Wertes der Referenzvariable zugreifen können, müssen wir ein zusätzliches Dollarzeichen $ für die Dereferenzierung angeben. Dabei gehört es nicht nur zum guten Ton, dass man um das Dereferenzierungszeichen und die Variable geschweifte Klammern setzt, vielmehr wird der Programmcode damit leichter lesbar: print( "Inhalt der dereferenzierten Variable scalarRef", " = ", ${ $scalarRef }, "\n" );
Die Ausgabe ist nun: Inhalt der dereferenzierten Variable scalarRef = 5
Man kann die geschweiften Klammern um $scalarRef weglassen und den Code wie folgt ändern: ${ $scalarRef } # ausführliche Form $$scalarRef # vereinfachte Form
Ich empfehle allerdings die ausführliche Form mit geschweiften Klammern, denn sie ist einfach besser lesbar.
Variablen
85
Die Abhängigkeiten zwischen Variablen und Referenzvariablen gelten sowohl für lesenden als auch für schreibenden Zugriff: $scalar = 1; # Da die Variable $scalarRef auf dieselbe # Speicheradresse zeigt, hat sich auch der Wert # von ${ $scalarRef } geändert: $val = ${ $scalarRef }; # $val enthält 1 # Nun ändern wir den Wert über die Referenzvariable: ${ $scalarRef } = -17; # Die Originalvariable $scalar enthält ebenfalls -17
Dereferenzierung von Array-Variablen Array-Referenzvariablen werden dereferenziert, indem ein @ vor die Referenzvariable (einschließlich des Typkennzeichens $) gestellt wird. Auf Array-Elemente wird über die Referenzvariable zugegriffen, indem der Operator -> zwischen den Namen der Referenzvariable und die öffnende eckige Klammer gesetzt wird: my @array = ( 1, 2, 3, ); # Definition der Referenzvariablen durch Voranstellen # eines Backslashs: my $arrayRef = \@array; # Zugriff auf einzelne Elemente des Arrays über die # Originalvariable: my $ele = $array[ 1 ]; # $ele enthält 2 # Zugriff auf einzelne Elemente des Arrays über die # Referenzvariable: $ele = $arrayRef->[ 1 ]; # Beachte: Hier muss der Dereferenzierungsoperator # -> verwendet werden. # $ele enthält ebenfalls 2 # Vergisst man den Dereferenzierungsoperator "->", # bekommt man als Dank eine schöne Fehlermeldung. # Beispiel: $ele = $arrayRef[ 1 ]; # Perl sucht in diesem Fall nach einer Array-Variable # @arrayRef, die es natürlich nicht gibt, wir haben # ja nur die skalare Referenzvariable $arrayRef # definiert. # Eine Fehlermeldung ist die Folge. # Anzahl der Array-Elemente # über die Array-Variable:
86
2 my $eleCount = scalar( @array ); # $eleCount enthält 3 # Dasselbe über die Referenzvariable: $eleCount = scalar( @{ $arrayRef } ); # Wir müssen dem Interpreter ausdrücklich sagen, # dass sich hinter der Variable "$arrayRef" in # Wirklichkeit ein Array verbirgt # $eleCount enthält 3 # Index des letzten Elements # über die Array-Variable: my $lastIndex = $#array; # $lastIndex enthält 2 # Dasselbe über die Referenzvariable: $lastIndex = $#{ $arrayRef }; # $lastIndex enthält 2 # Neues Element ans Ende des Array stellen # über die Array-Variable: push( @array, 7 ); # @array enthält jetzt ( 1, 2, 3, 7, ) # Dasselbe über die Referenzvariable: push( @{ $arrayRef }, 8 ); # @array enthält jetzt ( 1, 2, 3, 7, 8, ) # Liste leeren # über die Array-Variable @array = (); # @array enthält jetzt eine leere Liste # Dasselbe über die Referenzvariable: @{ $arrayRef } = (); # @array enthält jetzt eine leere Liste # # VORSICHT # # Die folgende Zeile weist der Variable # "$arrayRef" ein anonymes Array zu. $arrayRef = [ 7, 4, ]; # @array enthält immer noch eine leere Liste, # während $arrayRef nun zu einer Referenz auf ein # anonymes Array geworden ist, das über keine andere # Variable mehr erreichbar ist. # Die ursprüngliche Referenz auf @array wurde mit dieser # neuen Zuweisung an $arrayRef aufgehoben.
Grundlagen
Variablen
87
Dereferenzierung von Hash-Variablen Hash-Referenzvariablen werden dereferenziert, indem ein % vor die Referenzvariable (einschließlich Typkennzeichen $) gestellt wird. Auf Hash-Elemente wird über die Referenzvariable zugegriffen, indem der Operator -> zwischen den Namen der Referenzvariablen und die öffnende geschweifte Klammer gesetzt wird: # Definition und Initialisierung # einer normalen Hash-Variable my %hash = ( "age" => 35, "gender" => "w", ); # Definition der Referenzvariable, die # auf %hash zeigt. # (Die Referenzvariable wird mit der Adresse # von "%hash" initialisiert.) my $hashRef = \%hash; # Zugriff auf einzelne Hash-Elemente # über die Hash-Variable: my $value = $hash{ "age" }; # $value enthält 35 # Über die Referenzvariable: $value = $hashRef->{ "age" }; # $value enthält 35 # Setzen eines neuen Elements in %hash # über die Hash-Variable: $hash{ "firstname" } = "Egon"; # Dasselbe über die Referenzvariable: $hashRef->{ "firstname" } = "Egon"; # Schleife über alle Hash-Elemente # über die Hash-Variable: foreach my $key ( keys( %hash ) ) { my $val = $hash{ $key }; print( "key = $key, value = $val\n" ); } # Dasselbe über die Referenzvariable: foreach my $key ( keys( %{ $hashRef } ) ) { my $val = $hashRef->{ $key }; print( "key = $key, value = $val\n" ); } # Hash leeren # über die Hash-Variable: %hash = (); # %hash ist nun leer
88
2
Grundlagen
# Dasselbe über die Referenzvariable: %{ $hashRef } = (); # %hash ist nun leer # Abfrage auf Original-Hash-Variable: if ( %hash ) { print( "Hash ist nicht leer\n" ); } # Gleiche Abfrage über Referenzvariable: if ( %{ $hashRef } ) { print( "Hash ist nicht leer\n" ); } # VORSICHT: # Die folgende Abfrage if ( $hashRef ) # prüft, ob der Inhalt der Referenzvariable # logisch TRUE ist. # Der Inhalt der Referenzvariable zeigt aber auf eine # Speicheradresse # und nicht auf den Inhalt der Speicheradresse. # Adressen sind aber normalerweise ungleich 0x00 # (was logisch FALSE wäre), # d.h. die Abfrage liefert in der Regel immer TRUE. # Noch mal VORSICHT: $hashRef = { "a" => 1, "b" => 2, }; # Diese Zuweisung legt ein anonymes Hash an, # da nur noch über die Referenzvariable "$hashRef" # angesprochen werden kann. $hashRef hat nun nichts # mehr mit der Hash-Variable "%hash" zu tun, # diese bleibt unverändert!
Dereferenzierung von Referenzvariablen auf Funktionen Referenzvariablen auf Funktionen werden dereferenziert, indem man das Zeichen & vor die Referenzvariable (einschließlich Typkennzeichen $) stellt. Beispiel: # Definition einer Funktion "myFunc" # Sie gibt das erste Argument aus. sub myFunc { my ( $arg ) = @_; print( "$arg\n" ); } # Definition einer Referenzvariable, die auf # die Funktion "myFunc" zeigt. my $funcRef = \&myFunc; # Normaler Aufruf der Funktion
Variablen
89
myFunc( "hallo" ); # Aufruf über die Referenzvariable &{ $funcRef }( "Welt" ); # In diesem Fall ginge auch &$funcRef( "Welt" ), # aber das wollen wir uns erst gar nicht # angewöhnen. # Völlig anonyme Funktion, da sie gar keinen Namen # hat. Der Funktionsrumpf wird als Ganzes der # Referenzvariable zugewiesen. # Hinweis: Da es sich um eine Zuweisung handelt, # muss nach der schließenden geschweiften Klammer # unbedingt ein Semikolon stehen! # Aufruf nur über die Referenzvariable möglich. my $anonymousFunc = sub { my ( $arg ) = @_; print( "$arg\n" ); }; # Aufruf der Funktion &{ $anonymousFunc }( "Hi" );
Anonyme Funktionen mit Hilfe von Referenzvariablen werden meist in Zusammenhang mit Objektorientierter Programmierung verwendet, um private Funktionen zu definieren, die nur von Programmcode innerhalb derselben Datei aufgerufen werden können. Hinweis: Die Definition von Referenzvariablen für anonyme Funktionen muss vor dem Aufruf stehen, sonst erscheint eine Fehlermeldung. Beispiel: # So ist es richtig: erst definieren, dann benutzen. my $ref = sub { print( "hallo\n" ); }; &{ $ref }(); # So handelt man sich Ärger ein: &{ $ref }(); ... my $ref = sub { print( "hallo\n" ); };
Prüfen des Typs einer Referenzvariable Man kann mit Hilfe der Funktion ref() feststellen, ob es sich bei der angegebenen Variable um eine normale Variable oder aber um eine Referenzvariable handelt. Beispiel: # Definition einer normalen Array-Variable my @array = ( 1, 2, ); # Definition einer Referenzvariable auf @array
90
2
Grundlagen
my $aref = \@array; # Definition einer normalen Hash-Variable my %hash = ( "k1" => 1, "k2" => 2, ); # Definition einer Referenzvariable auf %hash my $href = \%hash; # Definition einer Referenzvariable auf $href my $refRef = \$href; # Definition einer Referenzvariable auf # eine anonyme Funktion my $func = sub { print( "hallo\n" ); }; # Für jede der oben stehenden Variablen wird nun # die Funktion ref() benutzt, um deren Typ # auszugeben: print( 'ref( @array ) = ', ref( @array ), "\n" ); print( 'ref( $aref ) = ', ref( $aref ), "\n" ); print( 'ref( %hash ) = ', ref( %hash ), "\n" ); print( 'ref( $href ) = ', ref( $href ), "\n" ); print( 'ref( $refRef ) = ', ref( $refRef ), "\n" ); print( 'ref( $func ) = ', ref( $func ), "\n" );
Die Ausgabe des Perl-Codes ist: ref( ref( ref( ref( ref( ref(
@array ) = $aref ) = ARRAY %hash ) = $href ) = HASH $refRef ) = REF $func ) = CODE
Wie man bereits durch Überlegen richtig vermuten kann, sind die Variablen @array und %hash keine Referenzvariablen. Für die Referenzvariable auf @array wird der String »ARRAY« zurückgeliefert, für die Hash-Referenz der String »HASH«. Bildet man eine Referenz auf eine Variable, die wiederum eine Referenz ist, wird der String »REF« von der Funktion ref() zurückgegeben. Handelt es sich bei der Variable um eine Referenz auf eine Funktion, dann gibt die Funktion den String »CODE« zurück. Damit lassen wir es fürs Erste gut sein. Später, wenn wir die Objektorientierte Programmierung kennen lernen, werden wir sehen, dass die Funktion ref() auch noch andere Rückgabewerte haben kann. Hier wollen wir uns erst einmal dem Thema »mehrdimensionale Arrays« widmen:
Variablen
91
Mehrdimensionale Arrays Mit Hilfe von Referenzen lassen sich in Perl auf effiziente Art und Weise mehrdimensionale Arrays aufbauen. Sehen wir uns einmal ein Beispiel für ein zweidimensionales Array an: # Direkte Definition einer zweidimensionalen # Array-Variable mit Hilfe von Referenzen my @ar2d = ( [ 1, 2, ], [ "a", 7, "hallo", ], );
Das erste Element des Arrays @ar2d, das mit $ar2d[ 0 ] angesprochen wird, enthält eine Referenz auf ein anonymes Array, das aus zwei Elementen besteht. Das zweite Element von @ar2d ist ebenfalls eine Referenz auf ein anonymes Array, diesmal mit drei Elementen. Wie man sieht, darf die Länge der anonymen Arrays in der zweiten Dimension getrost unterschiedlich sein. Wenn wir uns die Anzahl der Elemente von @ar2d mit print( scalar( @ar2d ), "\n" ); # Alternativ geht natürlich auch: print( $#ar2d + 1, "\n" );
ausgeben lassen, erhalten wir, wie bereits vermutet: 2
Mit folgendem Code sehen wir uns an, was sich in den einzelnen Elementen von @ar2d verbirgt: for ( my $i = 0; $i <= $#ar2d; $i++ ) { print( "ar2d[ $i ] = '$ar2d[ $i ]'\n" ); }
Dann bekommen wir in etwa diese Ausgabe: ar2d[ 0 ] = 'ARRAY(0x1a7efcc)' ar2d[ 1 ] = 'ARRAY(0x1a7f1dc)'
Die kryptisch anmutenden Hexzahlen sind die Speicheradressen, in denen Perl die Elemente abgelegt hat. Wie man bereits ahnen kann, deutet das Wörtchen ARRAY darauf hin, dass es sich jeweils um eine Referenz auf ein Array handelt.
92
2
Grundlagen
Wir gehen jetzt noch einen Schritt weiter und definieren auch die Array-Variable @ar2d als Referenz: # Zweidimensionales Array nur mit Referenzen # Anstelle einer Array-Variable verwenden wir jetzt # eine skalare Referenzvariable. my $ar2d = [ [ 1, 2, ], [ "a", 7, "hallo", ], ];
Natürlich müssen wir nun den Dereferenzierungsoperator -> benutzen, um an die Elemente des Arrays heranzukommen: # Ausgabe der Anzahl von Elementen: print( scalar( @{ $ar2d } ), "\n" ); # oder print( $#{ $ar2d } + 1, "\n" ); # Ausgabe der Elemente: for ( my $i = 0; $i <= $#{ $ar2d }; $i++ ) { print( "ar2d[ $i ] = '$ar2d->[ $i ]'\n" ); }
Nun wollen wir zum Beispiel auf das letzte Element von $ar2d->[ 0 ] zugreifen (das ist das erste Element des übergeordneten Arrays @ar2d): # Hier ist der Index für das letzte Element des # untergeordneten Arrays in der zweiten Dimension # fest verdrahtet mit 1 angegeben. print( $ar2d->[ 0 ]->[ 1 ], "\n" ); # Und nun machen wir deutlich, dass wir das letzte # Element wollen, egal, wie viele Elemente im # untergeordneten Array der zweiten Dimension # vorhanden sind: print( $ar2d->[ 0 ]->[ $#{ $ar2d->[ 0 ] } ], "\n" );
Alles klar? Wenn nicht, dann wollen wir die Sache schrittweise vereinfachen: In Worten wollen wir Folgendes: den Inhalt des letzten Elements vom ersten untergeordneten Array der zweiten Dimension. Verwenden wir Hilfsvariablen zur Vereinfachung, dann wird alles schon ein bisschen klarer: # Unser Array in Form von Referenzen my $ar2d = [ [ 1, 2, ], [ "a", 7, "hallo", ], ]; # Hilfsvariable für die Referenz auf das erste Element # in der ersten Dimension my $aref = $ar2d->[ 0 ];
Variablen
93
# Hilfsvariable für den Index des letzten Elements # im untergeordneten Array der zweiten Dimension my $lastInd = $#{ $aref }; # Und nun noch eine Hilfsvariable für den Wert # des so ermittelten Elements my $ele = $aref->[ $lastInd ]; # Nun sieht der Code doch schon ganz lesbar aus, oder? print( "ele[ 0 ][ $lastInd ] = '$ele'\n" );
Noch ein Wort zum Dereferenzierungs Operator ->:
Die Pfeilregel für mehrdimensionale Arrays Perl erlaubt, dass man bei mehreren hintereinander stehenden ->[]->[] in mehrdimensionalen Arrays nur den ersten hinschreibt. Damit sieht ein mehrdimensionales Array auch so aus: # Ausführliche Angabe aller Operatoren zur # Dereferenzierung: my $lastInd = $#{ $ar2d->[ 0 ] }; print( $ar2d->[ 0 ]->[ $lastInd ], "\n" ); # Erlaubte Abkürzung: print( $ar2d->[ 0 ][ $lastInd ], "\n" );
Wenn wir für das Array in der ersten Dimension keine Referenz, sondern eine normale Array-Variable verwenden, dann wird die Sache noch einfacher, denn in diesem Fall benötigt man gar keine Operatoren für die Dereferenzierung: # Direkte Deklaration einer zweidimensionalen # Array-Variable mit Hilfe von Referenzen my @ar2d = ( [ 1, 2, ], [ "a", 7, "hallo", ], ); # Verwendung der Pfeilregel zur Abkürzung my $lastInd = $#{ $ar2d[ 0 ] }; print( $ar2d[ 0 ][ $lastInd ], "\n" );
Ich habe sowohl für die Array-Variable @ar2d als auch für die Referenzvariable $ar2d denselben Namen gewählt (ar2d), um Ihnen ein Problem zu zeigen, das Novizen in Perl oft haben: In den Ausdrücken $ar2d[ 0 ][ $lastInd ] # und $ar2d->[ 0 ][ $lastInd ]
handelt es sich bei $ar2d um zwei völlig verschiedene Variablen!
94
2
Grundlagen
Man erkennt es allerdings nur am ersten Dereferenzierungsoperator -> nach dem Variablennamen, der auf eine skalare Referenzvariable $ar2d hindeutet, während der obere Ausdruck eine Array-Variable @ar2d darstellt. Leider erkennt man das nicht am Typkennzeichen, das vor dem Variablennamen steht, denn dieses ist in beiden Fällen das Dollarzeichen $, und dieses kennzeichnet, wie wir nun wissen, eine skalare Variable. Wir wollen ja schließlich auf ein Element eines Arrays zugreifen. Dieses ist immer, gleichgültig, ob das Array über eine normale Array-Variable oder über eine Referenzvariable angesprochen wird, ein Skalar.
n-dimensionale Arrays n-dimensionale Arrays funktionieren im Prinzip genauso wie zweidimensionale. Man muss einfach nur mehr schreiben: # Beispiel für ein dreidimensionales Array # mit Referenzen my $ar3d = [ # erste Dimension [ # zweite Dimension [ 0, 1, 2, ], # dritte Dimension ], [ # zweite Dimension [ 3, 4, 5, ], # dritte Dimension ], ]; # Anhängen eines Elements push( @{ $ar3d->[ 0 ][ 0 ] }, "2.5" );
Oft werden mehrdimensionale Arrays nicht mit allen Werten in der Deklaration einer Variable initialisiert, sondern wachsen zur Laufzeit des Programms (zum Beispiel, wenn Tabellen aus Dateien eingelesen und verarbeitet werden). Zuerst deklariert man eine Array-Variable (oder auch eine Referenzvariable) und initialisiert diese mit einer leeren Liste: my @array = (); # oder als Referenz my $aref = [];
In einer Schleife werden nun Daten eingelesen. Als Beispiel wollen wir das zeilenweise Einlesen einer Datei nehmen. Jede Zeile der Datei soll ein neues Array werden, das wiederum alle Zeichen der eingelesenen Zeile als Elemente besitzt (keine Sorge, das Einlesen von Dateien werden wir weiter unten ausführlich kennen lernen). Wir bauen also ein zweidimensionales Array zur Laufzeit auf:
Variablen 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 }
95
my @array = (); # Zeilenweises Einlesen von der Standardeingabe # (meist ist das die Tastatur) while ( defined( my $line = <STDIN> ) ) { # Zeilenende-Zeichen entfernen (dieses ist # nach dem Einlesen der Zeile # Bestandteil von $line) chomp( $line ); # Unser oberstes Array, das die Zeilen enthalten # soll, um ein Element erweitern $#array++; # Hinweis: es wurde nur das Array um ein # Element erweitert, das ist jedoch noch nicht # initialisiert. Wir müssen nun dafür sorgen, # dass aus diesem Element eine Array Referenz # wird, die alle eingelesenen Zeichen der Zeile # als Elemente besitzt $array[ $#array ] = [ split( "", $line ) ];
Die interessanten Zeilen des Quellcodes sind Zeile 12 und Zeile 19. In Zeile 12 wird die Anzahl des Arrays @array um ein Element erhöht (mit Hilfe des Autoincrement-Operators, den wir später noch kennen lernen werden). Zu diesem Zeitpunkt erfolgt aber noch keine Initialisierung des neu hinzugekommenen Elements, es ist also noch undef. Es wird also zunächst nur ein Element am Ende des Arrays hinzugefügt. Erst in Zeile 19 erfolgt die Initialisierung. Den Ausdruck $array[ $#array ]
müssten wir bereits kennen, er greift auf das letzte Element des Arrays @array zu. Mit dem Funktionsaufruf split( "", $line )
wandeln wir die Zeichenkette der eingelesenen Zeichen in eine Liste um, deren Elemente die Zeichen der Zeile sind. Dadurch, dass wir den Funktionsaufruf in eckige Klammern setzen, teilen wir dem Interpreter mit: »Mach bitte aus der Liste eine Array-Referenz!«: [ split( "", $line ) ]
Wenn wir keine eckigen Klammern setzen, erhalten wir, wie könnte es anders sein, natürlich eine Fehlermeldung: $array[ $#array ] = split( "", $line );
96
2
Grundlagen
Die Fehlermeldung sieht in etwa so aus: Use of implicit split to @_ is deprecated at - line 19.
Im Moment können wir mit dieser Fehlermeldung noch nicht viel anfangen, weil wir nicht wissen, was für eine Variable @_ ist (keine Sorge, die Erklärung kommt noch). Die Funktion split() liefert, wie gesagt, eine Liste zurück. Wir aber weisen diese Liste einer skalaren Variable zu, nämlich dem letzten Element des Arrays @array. Das kann nur schief gehen. Erst durch die eckigen Klammern wird die Liste zu einer skalaren Referenz gemacht.
2.7 Operatoren 2.7.1 Was sind Operatoren? Ich will Ihnen den Begriff »Operator« anhand einiger einfacher Beispiele näher bringen: 5 + 3 $i++; my $diff = $op1 - $op2;
Ein Operator dient der Verknüpfung von Variablen oder Ausdrücken (englisch: »expressions«). Ein Ausdruck kann ein beliebiger Wert oder eine Verknüpfung von Werten und weiteren Ausdrücken (inklusive Funktionsaufrufen) sein. Das erste Beispiel zeigt den Operator + für die arithmetische Addition. Im zweiten Beispiel wird der aktuelle Zahlenwert der Variable $i um eins erhöht; der dafür vorgesehene Operator ++ wird Autoincrement-Operator genannt. Im letzten Beispiel sehen wir gleich zwei Operatoren: Zunächst wird die Differenz zweier numerischer Variablen mit Hilfe des Operators - gebildet. Das Ergebnis dieser Aktion wird dann über den Zuweisungsoperator = an die Variable $diff übergeben. Die Variablen bzw. die Ausdrücke, welche durch den Operator miteinander verknüpft werden, nennt man »Operanden«. Das letzte Beispiel hieße also in Worten: »Bilde zunächst die Differenz, indem der Operand $op1 und der Operand $op2 durch den Operator - verknüpft werden, und weise das Ergebnis dieser Operation mit Hilfe des Operators - der Variable $diff zu«.
Operatoren
97
Evaluierung Nahezu jeder Operator liefert einen Wert zurück, auf neudeutsch sagt man, er »evaluiert« die Operanden zu einem Wert. So evaluiert der Operator + im ersten Beispiel zum Zahlenwert 8, während der Operator ++ des zweiten Beispiels zu dem um 1 erhöhten aktuellen Zahlenwert der Variable $i evaluiert.
Binäre Operatoren Binäre Operatoren kennzeichnen sich dadurch aus, dass sie zwei Operanden besitzen, einen linken und einen rechten, die vom Operator miteinander verknüpft werden und einen neuen Wert ergeben (evaluieren).
Unäre Operatoren Ein unärer Operator besitzt nur einen einzigen Operanden. So ist zum Beispiel der Operator ++ unär. Je nach Art von Operator kann dabei der Operand entweder links oder rechts vom Operator stehen.
Rangfolge (Prioritäten) von Operatoren Bei der Abarbeitung mehrerer Operatoren gilt eine festgelegte Rangfolge, die bestimmt, in welcher Reihenfolge die Operatoren bearbeitet werden. Haben zwei Operatoren dieselbe Priorität, dann arbeitet der Interpreter diese von links nach rechts ab. Man kann die Reihenfolge der Abarbeitung auch gezielt durch Setzen von runden Klammern verändern. Beispiele: # Alle Operatoren haben dieselbe Priorität # Sie werden von links nach rechts abgearbeitet 1 + 2 - 3 + 4 # Der Operator "*" hat eine höhere Priorität als "+", # "/" hat eine höhere Priorität als "-" 2 * 3 + 4 / 2 - 4 # Ergebnis: 4 # Dasselbe mit expliziten Klammern ( 2 * 3 ) + ( 4 / 2 ) - 4 # Verändern der Reihenfolge mit Klammern 2 * ( 3 + 4 ) / ( 2 - 4 ) # Ergebnis: -7
Übersicht der Operatorprioritäten. In der linken Spalte ist der Operator angegeben, in der rechten Spalte der Assoziationstyp (d.h., ob der links vom Operator stehende Operand evaluiert wird oder der rechts stehende). Die Tabelle ist so sortiert, dass die Operatoren mit der höchsten Priorität oben stehen, die mit der geringsten Priorität unten.
98
2
Grundlagen
Operator
Assoziationstyp
Variablen, Quote-Operatoren, Ausdrücke in runden Klammern, Funktionsaufrufe mit runden Klammern, List-Operatoren nach links gesehen
links
->
links
++ --
./.
**
rechts
!, ~, \, unäres +, unäres -
rechts
=~, !~
links
*, /, %, x
links
+, -, .
links
<<, >>
links
-f, -d etc.
./.
<, >, <=, >=, lt, gt, le, ge
./.
==, !=, <=>, eq, ne, cmp
./.
&
links
|, ^
links
&&
links
||
links
.., ...
./.
?:
rechts
Komma-Operator, =>
links
List-Operatoren (nach rechts gesehen)
./.
not
rechts
and
links
or, xor
links
Tabelle 2.2: Übersicht der Operatorprioritäten
Wie man bereits an der Tabelle sieht, ist es bei der Fülle von Operatoren und Prioritäten nicht leicht, sich das Ganze zu merken. Wir sollten uns deshalb an den Leitsatz halten: »Setze immer Klammern, um die Rangfolge zu zeigen, die du meinst, wenn mehrere Operatoren mit unterschiedlichen Prioritäten verwendet werden!«. Das verbessert die Lesbarkeit von Programmen ganz ungemein. In Perl ist nicht immer genau definiert, wann etwas ein Operator oder eine Funktion ist. Diese Tatsache spielt allerdings in der Praxis kaum eine Rolle.
Operatoren
99
Perl stellt für alle möglichen Anforderungen die verschiedensten Operatoren zur Verfügung, die wir nun in einzelne Gruppen einteilen wollen.
2.7.2 Arithmetische Operatoren Für die Verarbeitung von Zahlen stellt Perl die folgenden Operatoren zur Verfügung:
Operator + Mit diesem Operator wird die Summe zweier Operanden gebildet. Beispiel: my $o1 = 1; my $o2 = 5; my $sum = $o1 + $o2; # $sum enthält 6 # Auch Zahlen als Strings mit "white space" am Ende # oder am Anfang # (white space ist ein Blank, ein Tab, "\n" und "\r") # funktionieren: my $o1 = "1\n"; my $o2 = " 2 "; my $sum = $o1 + $o2
Enthält ein Operand keine Zahl, erfolgt eine Fehlermeldung: my my my my my
$i = 5; $k = "10"; $sum = $i + $k; $m = "a"; $diff = $m - $i;
Die letzte Zeile liefert: Argument "a" isn't numeric in subtraction (-) at - line 5.
Die Summenbildung von $i und $k geht jedoch in Ordnung, obwohl $k als String deklariert wurde, weil der Interpreter den Wert 10 in eine gültige Zahl umwandeln kann. Der Fehler ist in unserem Beispiel zwar leicht erkennbar, weil dort die Variablenwerte als Konstanten angegeben sind. In der Praxis jedoch werden die Variablen zur Laufzeit des Programms dynamisch mit Werten versorgt (z.B. durch das Einlesen von Daten aus einer Datenbank). Dann ist der Fehler aus dem Quellcode nicht mehr direkt ersichtlich. Beispiel: # Einlesen der Variablenwerte von der Standardeingabe # (Tastatur) my $i = <STDIN>;
100
2
Grundlagen
my $k = <STDIN>; my $sum = $i + $k;
Wenn wir den Beispielcode mit folgenden Eingaben ausführen: D:\>perl -w my $i = <STDIN>; my $k = <STDIN>; print( $i + $k ); ^Z 1 2 3 D:\>
dann erhalten wir, was schon zu vermuten war: die Ausgabe der Zahl 3 (das ist die letzte Zeile, alle vorhergehenden Zeilen haben wir über die Tastatur eingegeben). Das ist insofern bemerkenswert, als durch das Einlesen einer Zeile zunächst ein String entsteht, der das Zeilenende-Zeichen enthält. (Die Variable $i hat also den Wert 1\n. Wie wir sehen, entfernt der Interpreter implizit alle Zeichen, die man »white space« nennt. Dazu gehören das Blank, das TAB-Zeichen sowie \n und \r.) Erst wenn wir eine falsche Eingabe machen, sehen wir, was wirklich an die Variablen übergeben wurde: D:\>perl -w my $i = <STDIN>; my $k = <STDIN>; print( $i + $k ); ^Z 1 a Argument "a\n" isn't numeric in addition (+) at - line 3, <STDIN> line 2. 1
Operator Dieser Operator bildet die Differenz zweier Operanden. Beispiel: my $o1 = 1; my $o2 = 5; my $diff = $o1 - $o2; # $diff enthält -4
Enthält ein Operand keine Zahl, erfolgt eine Fehlermeldung (siehe hierzu den Operator +).
Operatoren
101
Als unärer Operator mit nur einem Operanden liefert er den Wert mit umgekehrtem Vorzeichen: my $o = -1; my $result = -$o1; # $result enthält 1
Operator * Dieser Operator bildet das Produkt zweier Operanden. Beispiel: my $o1 = 1; my $o2 = 5; my $prod = $o1 * $o2; # $prod enthält 5
Enthält ein Operand keine Zahl, erfolgt eine Fehlermeldung.
Operator / Dieser Operand bildet den Quotienten zweier Operanden. Das Ergebnis ist eine Gleitkommazahl. my $o1 = 1; my $o2 = 5; my $quot = $o1 / $o2; # $quot enthält 0.2
Enthält ein Operand keine Zahl, erfolgt eine Fehlermeldung. Dasselbe passiert natürlich, wenn der Nenner (das ist der rechte Operand von »/«) die Zahl 0 enthält (Division durch Null).
Operator % Dieser Operator liefert den Rest einer Division (modulo Division). Das Ergebnis ist eine Integerzahl, die zwischen 0 und rechter operand - 1 liegt. Beispiel: my $o1 = 5; my $o2 = 3; my $remainder = $o1 % $o2; # $remainder enthält 2, denn: # 5 / 3 = 1 Rest 2
Enthält ein Operand keine Zahl, erfolgt eine Fehlermeldung.
102
2
Grundlagen
Operator ** Dieser Operator führt eine Exponentialfunktion durch. Beispiel: my $o1 = 2; my $o2 = 3; my $result = $o1 ** $o2; # $result enthält 8 (2^3)
Enthält ein Operand keine Zahl, erfolgt eine Fehlermeldung. Vorsicht: -2 ** 4 # # # ( -2 ) **
ist nicht etwa 16, sondern -16, da der binäre Operator ** stärker bindet als der unäre Operator 4 # liefert 16
2.7.3 String-Operatoren Operator . (Punkt-Operator) Der Punkt-Operator wird verwendet, um mehrere Strings aneinander zu hängen. Beispiel: my $s1 = " balla"; my $result = "abc" . $s1 . " hallo"; # $result enthält "abc balla hallo"
Operator X (Vervielfältigungsoperator) Mit dem Vervielfältigungsoperator lässt sich ein beliebiger String (das kann auch ein einzelnes Zeichen sein) vervielfältigen. Beispiel: # Rechtsbündige Ausgabe mit fester Zeilenbreite # Auszugebender String my $output = "Hi there"; # Feste Zeilenbreite (hier 80 Zeichen) my $colCount = 80; # Länge des Strings bestimmen, der aus Leerzeichen # besteht und eine Füllfunktion hat my $fillerLen = $colCount - length( $output ); # Der String " " wird so oft vervielfältigt, # dass sich genau 80 Zeichen je Zeile ergeben print( " " x $fillerLen, "$output\n" );
Operatoren
103
Ist der rechte Operand (die Anzahl für die Vervielfältigung) negativ, dann wird vom Operator ein leerer String ausgegeben: "a" x 0 # ergibt ""
2.7.4 Zuweisungsoperatoren Operator = Der einfachste Zuweisungsoperator ist das Gleichheitszeichen, bei dem der Ausdruck rechts vom Operator dem linken Operanden zugewiesen wird. Er kann auch mit vielen anderen Operatoren verknüpft werden, so dass sich abkürzende Schreibweisen ergeben. Beispiele: my $result = 7 + 4; # $result um 10 erhöhen $result += 10; # Dasselbe in Langform: $result = $result + 10; # $result mit 10 multiplizieren $result *= 10 # $result durch 2 teilen $result /= 2; # 5 von $result abziehen $result -= 5;
Neben den gezeigten Kombinationen lässt sich der Zuweisungsoperator mit folgenden Operatoren verknüpfen (der Zuweisungsoperator = steht immer rechts vom verknüpften Operator): 왘 ** (Exponentialfunktion) 왘 . (String Verkettung) 왘 x (Vervielfältigung) 왘 & (Bitweises UND) 왘 | (Bitweises ODER) 왘 ^ (Bitweises EXOR) 왘 << (Bitweises Linksschieben)
104
2
Grundlagen
왘 >> (Bitweises Rechtsschieben) 왘 && (logisches UND) 왘 || (logisches ODER)
2.7.5 Autoincrement- und Autodecrement-Operatoren Mit den Operatoren ++ sowie -- lassen sich Variablen nach Gebrauch bzw. vor Gebrauch automatisch um eins hochzählen (inkrementieren) oder verringern (dekrementieren). Der Autoincrement-Operator weist zudem die Besonderheit auf, dass er auch bei Strings funktioniert. Beispiele: my $var1 = 1; my $var2 = $var1++ + 1; # Zuerst wird der aktuelle Wert von "$var1" mit 1 # addiert, und das Ergebnis wird "$var2" zugewiesen. # $var2 enthält also 2. # $var1 enthält nach der Operation den Wert 2. my $var3 = ++$var1 + 1; # Diesmal wird zuerst der Wert von "$var1" um 1 # erhöht, erst danach wird die Addition durchgeführt. # $var3 enthält also 4. # Besonderheit bei Strings, die nur für den # ++ Operator gilt! my $str = "A"; $str++; print( "$str\n" ); # Es wird "B" ausgegeben. $str = "Z"; $str++; print( "$str\n" ); # Es wird "AA" ausgegeben. # Der Operator -- hingegen funktioniert nicht # bei Strings. $str = "Z"; $str--; print( "$str\n" ); # Es wird -1 ausgegeben.
Operatoren
105
2.7.6 Logische Operatoren Mit logischen Operatoren lassen sich Verknüpfungen der booleschen Algebra durchführen. Die booleschen Verknüpfungen sind: 왘 UND Zwei logische Werte werden mit der UND-Funktion miteinander verknüpft. Das Ergebnis ist nur dann TRUE, wenn beide Operanden TRUE sind, in allen anderen Fällen ist das Ergebnis FALSE. Dazu ein kleines Beispiel aus dem Alltagsleben von Kinogängern: »Wir gehen nur dann ins Kino, wenn noch Plätze frei sind UND das Wetter schlecht ist.« 왘 ODER Zwei logische Werte werden mit der ODER-Funktion miteinander verknüpft. Das Ergebnis ist nur dann FALSE, wenn beide Operanden FALSE sind, in allen anderen Fällen ist das Ergebnis TRUE. Auch hier ein kleines Beispiel: »Wenn ich heute einkaufe ODER meine offenen Rechnungen bezahle, dann habe ich morgen kein Geld mehr.« (Vor allem seit der Einführung des Euro ist besonders der erste Fall sehr wahrscheinlich.) 왘 NICHT Mit diesem unären Operator wird der aktuelle logische Wert des Operanden umgekehrt (negiert). Aus TRUE wird FALSE und umgekehrt. 왘 EXOR Diese wohl beliebteste Verknüpfung der booleschen Algebra liefert TRUE, wenn die beiden Operanden ungleich sind, FALSE bei Gleichheit der Operanden. Die UND-Funktion hat eine höhere Priorität als die ODER-Funktion. Werden beide Funktionen in einem Ausdruck verwendet, sollte man der besseren Lesbarkeit und Verständlichkeit halber Klammern setzen: a # ( #
UND b ODER c UND d ist dasselbe wie a UND b ) ODER ( c UND d ) aber mit Klammern wird es besser ersichtlich
# Sollen zuerst b und c verknüpft werden, dann # muss man sowieso Klammern setzen: a UND ( b ODER c ) UND d
Die Operanden werden von links nach rechts evaluiert. Das bedeutet im Falle der UND-Funktion, dass der zweite Operand überhaupt nicht evaluiert wird, wenn der erste Operand bereits FALSE ist.
106
2
Grundlagen
Umgekehrt wird bei der ODER-Funktion der zweite Operand ebenfalls nicht evaluiert, wenn der erste Operand bereits TRUE ist. Diese Tatsache kann man beim Kodieren von hoch performantem Code berücksichtigen, indem man die Reihenfolge der Operanden je nach Lage der Dinge vertauscht.
Die Operatoren && und and Sowohl && als auch and führen eine logische UND-Verknüpfung zweier Operanden durch. and hat jedoch niedrigere Priorität. Beispiele: my $flag1 = 0; my $flag2 = 1; my $result = $flag1 && $flag2; # Beachte: Hier wird $flag2 nicht evaluiert, # weil $flag1 bereits logisch FALSE ist. Der Interpreter # arbeitet die Operation von links nach rechts ab # und hört in dem Moment auf, in dem sich ein FALSE-Wert # ergibt. # Das Ergebnis ist hier FALSE. # Vorsicht: Hier wird zuerst die Zuweisung von # $flag1 an $result durchgeführt # und dieses erst mit $flag2 # "verundet", da der Operator "and" # eine niedrigere Priorität besitzt als # der Zuweisungsoperator. $result = $flag1 and $flag2; # So funktioniert es wie erwartet: $result = ( $flag1 and $flag2 );
Die Operatoren || und or Sowohl || als auch or führen eine logische ODER-Verknüpfung zweier Operanden durch. or hat jedoch niedrigere Priorität. Beispiele: my $flag1 = "yes"; my $flag2 = ""; # Beachte: Hier wird $flag2 nicht evaluiert, # weil $flag1 bereits logisch TRUE ist. # Das Ergebnis ist TRUE.
Operatoren
107
my $result = $flag1 || $flag2; # Vorsicht: Hier wird zuerst die Zuweisung von # $flag1 an $result durchgeführt und dieses # erst anschließend mit $flag2 "verodert", da der # Operator "or" # eine niedrigere Priorität besitzt als # der Zuweisungsoperator. $result = $flag1 or $flag2; # So funktioniert es wie erwartet: $result = ( $flag1 or $flag2 );
Die Operatoren ! und not Sowohl der Operator ! als auch not führen eine logische Negation des Operanden durch. not hat jedoch eine niedrigere Priorität. Beispiele: my $flag = !1; $flag = not 1; # $flag enthält nach der Zuweisung den leeren String ''. # Jeder beliebige Wert, der im Sinne von Perl zu TRUE # evaluiert, ergibt in negierter Form den leeren String. $flag = !0; $flag = not 0; # $flag enthält nach der Zuweisung die Zahl "1". # Jeder beliebige Wert, der im Sinne von Perl zu FALSE # evaluiert, ergibt in negierter Form die Zahl "1".
Operator xor Der xor-Operator führt eine logische Exklusiv-Oder-Verknüpfung der beiden Operanden durch. Beispiele: my $flag1 = "yes"; my $flag2 = ""; # Vorsicht: Hier wird zuerst die Zuweisung von # $flag1 an $result durchgeführt, # anschließend dieses mit $flag2 # exklusiv-oder verknüpft, da der Operator "xor" # eine niedrigere Priorität besitzt als der # Zuweisungsoperator. $result = $flag1 xor $flag2;
108
2
Grundlagen
# So funktioniert es wie erwartet: $result = ( $flag1 xor $flag2 ); # $result ist TRUE, da $flag1 einen anderen # logischen Zustand hat als $flag2 # (die beiden Operanden sind ungleich).
2.7.7 Vergleichsoperatoren Mit Vergleichsoperatoren lassen sich zwei Operanden miteinander vergleichen. Das Ergebnis des Vergleichs ist ein logischer Wert. Perl bietet für Zahlen andere Vergleichsoperatoren an als für Strings. Während Zahlen numerisch verglichen werden, findet bei Strings ein lexikalischer Vergleich statt. Dies hat insbesondere dann oft unerwünschte Auswirkungen, wenn Zahlen mit Vergleichsoperatoren für Strings verglichen werden. Beispiele: my $v1 = 10; my $v2 = 5;
Numerisch gesehen kommt »5« vor »10«. Lexikalisch jedoch kommt »10« vor »5«.
2.7.8 Vergleichsoperatoren für Zahlen Operator == Mit dem ==-Operator wird die Gleichheit zweier numerischer Operanden geprüft. Das Ergebnis ist TRUE, wenn beide Operanden denselben Wert haben, sonst evaluiert der Operator zu FALSE. Ist einer der Operanden keine gültige Zahl, erfolgt eine Fehlermeldung. Beispiele: my $op1 = 5; my $op2 = -17.4; my $equals = ( $op1 == $op2 ); # Hinweis: Ich habe der besseren Lesbarkeit halber # Klammern gesetzt. Da der Zuweisungsoperator "=" # eine geringere Priorität besitzt als "==", könnte # man die Klammern hier auch weglassen: $equals = $op1 == $op2; # liefert also dasselbe Ergebnis, würde ich aber # nicht empfehlen. # $equals ist FALSE. $op1 = "a"; $equals = ( $op1 == $op2 );
Operatoren
109
# Dies führt zu einer Fehlermeldung, # da $op1 keine Zahl enthält. # Dasselbe, aber nicht direkt ersichtlich: # Einlesen zweier Werte von der Tastatur my $op10 = <STDIN>; my $op11 = <STDIN>; $equals = ( $op10 == $op11 ); # Der Code führt dann zu einer Fehlermeldung, # wenn entweder $op10 oder $op11 keine gültige Zahl ist.
Operator != Der != Operator evaluiert zu TRUE, wenn beide Operanden ungleich sind, andernfalls liefert er FALSE. Ist einer der Operanden nicht numerisch, erfolgt eine Fehlermeldung. Beispiele: my $op1 = 5; my $op2 = -17.4; my $notEquals = ( $op1 != $op2 ); # Hinweis: Ich habe der besseren Lesbarkeit halber # Klammern gesetzt. Da der Zuweisungsoperator "=" # eine geringere Priorität besitzt als "!=", könnte # man die Klammern hier auch weglassen: $equals = $op1 != $op2; # $notEquals ist TRUE. $op1 = "a"; $notEquals = ( $op1 != $op2 ); # Dies führt zu einer Fehlermeldung, da $op1 keine Zahl # enthält. # Dasselbe, aber nicht direkt ersichtlich: # Einlesen zweier Werte von der Tastatur my $op10 = <STDIN>; my $op11 = <STDIN>; $notEquals = ( $op10 != $op11 ); # Der Code führt dann zu einer Fehlermeldung, wenn # entweder $op10 oder $op11 keine gültige Zahl ist.
Operator > Der >-Operator evaluiert zu TRUE, wenn der linke Operand größer ist als der rechte Operand, andernfalls liefert er FALSE. Ist einer der Operanden nicht numerisch, erfolgt eine Fehlermeldung.
110
2
Grundlagen
Beispiele: my $op1 = 5; my $op2 = -17.4; my $greater = ( $op1 > $op2 ); # $greater ist TRUE, da $op2 zwar zahlenmässig grösser, # aber negativ ist. # Hinweis: Ich habe der besseren Lesbarkeit halber # Klammern gesetzt. Da der Zuweisungsoperator "=" # eine geringere Priorität besitzt als ">", könnte # man die Klammern hier auch weglassen: $equals = $op1 > $op2; $op1 = "a"; $greater = ( $op1 > $op2 ); # Dies führt zu einer Fehlermeldung, da $op1 # keine Zahl enthält. # Dasselbe, aber nicht direkt ersichtlich: # Einlesen zweier Werte von der Tastatur my $op10 = <STDIN>; my $op11 = <STDIN>; $greater = ( $op10 > $op11 ); # Der Code führt dann zu einer Fehlermeldung, wenn # entweder $op10 oder $op11 keine gültige Zahl ist.
Operator < Der <-Operator evaluiert zu TRUE, wenn der linke Operand kleiner ist als der rechte Operand, andernfalls liefert er FALSE. Ist einer der Operanden nicht numerisch, erfolgt eine Fehlermeldung. Beispiele: my $op1 = 5; my $op2 = -17.4; my $lower = ( $op1 < $op2 ); # $lower ist FALSE, da $op1 zwar zahlenmässig kleiner, # aber positiv ist. # Hinweis: Ich habe der besseren Lesbarkeit halber # Klammern gesetzt. Da der Zuweisungsoperator "=" # eine geringere Priorität besitzt als "<", könnte # man die Klammern hier auch weglassen: $equals = $op1 < $op2; $op1 = "a"; $lower = ( $op1 < $op2 ); # Dies führt zu einer Fehlermeldung, # da $op1 keine Zahl enthält.
Operatoren
111
# Dasselbe, aber nicht direkt ersichtlich: # Einlesen zweier Werte von der Tastatur my $op10 = <STDIN>; my $op11 = <STDIN>; $lower = ( $op10 < $op11 ); # Der Code führt dann zu einer Fehlermeldung, wenn # entweder $op10 oder $op11 keine gültige Zahl ist.
Die Operatoren >= und <= Diese Operatoren arbeiten wie > und <, jedoch evaluieren sie auch dann zu TRUE, wenn die Operanden gleich sind.
Operator <=> Der <=> Operator vergleicht beide Operanden numerisch. Ist einer der Operanden keine Zahl, erfolgt eine Fehlermeldung. Je nach Ergebnis liefert der <=>-Operator einen von 3 möglichen Werten. 왘 3 <=> 4 liefert -1 (3 ist kleiner als 4) 왘 3 <=> 3 liefert 0 (Gleichheit) 왘 3 <=> -4 liefert 1 (3 ist größer als -4) Vorsicht: Wenn man nicht sicher ist, dass die Operanden numerisch sind, kann es wie bei allen anderen numerischen Vergleichsoperatoren zu Fehlermeldungen kommen. Beispiel (die Statements werden direkt über die Kommandozeile in einer DOS-Box eingegeben): D:\>perl -w use strict; # Einlesen zweier Werte von der Tastatur my $v1 = <STDIN>; my $v2 = <STDIN>; print( "v1 <=> v2 = ", $v1 <=> $v2, "\n" ); ^Z 3 a Argument "a\n" isn't numeric in numeric comparison (<=>) at - line 6, <STDIN> line 2. v1 <=> v2 = 1
Der <=>-Operator wird häufig in Sortierfunktionen verwendet, um numerisch zu sortieren. Per Default verwendet die sort()-Funktion die lexikalische Sortierung. Wir werden später noch intensiv auf die Verwendung der Funktion zu sprechen kommen.
112
2
Grundlagen
2.7.9 Vergleichsoperatoren für Strings Alle Vergleichsoperatoren für Strings können auch auf Zahlen und numerische Werte in Variablen benutzt werden. Die Zahlenwerte werden vor dem Vergleich implizit in Strings umgewandelt. Allerdings ist zu beachten, dass alle Stringvergleiche lexikalisch und case-sensitive sortieren (»10« kommt lexikalisch vor »5«). Deutsche Umlaute werden per Default bei einer Sortierung als Sonderzeichen behandelt, die vom Zeichencode her hinter allen normalen Zeichen kommen. Das hat folgende Auswirkung: D:\>perl -w use strict; my $s1 = "außen"; my $s2 = "äußern"; print( join( ", ", sort( $s1, $s2 ) ), "\n" ); ^Z außen, äußern
Eigentlich müsste das Wort »äußern« vor »außen« kommen. Das Problem kann man mit der Direktive use locale;
jedoch beheben: D:\>perl -w use strict; use locale; my $s1 = "außen"; my $s2 = "äußern"; print( join( ", ", sort( $s1, $s2 ) ), "\n" ); ^Z äußern, außen
Nun stimmt die Welt wieder. Eine genaue Beschreibung für die Benutzung von Locales erhält man mit dem Aufruf: perldoc perllocale
Operator eq Der eq-Operator entspricht dem ==-Operator für Zahlen, jedoch werden die Operanden lexikalisch verglichen. Im Gegensatz zum ==-Operator funktioniert der eq-Operator durch die automatische Umwandlung sowohl für Zahlen als auch für Strings. Der lexikalische Vergleich ist case-sensitive.
Operatoren
113
Beispiele für die Benutzung des eq-Operators: # Normaler Vergleich zweier Strings if ( $stringVar eq "hallo" ) # Vergleich mit einer Zahl als String if ( $stringVar eq "1" ) # eq kann man auch für numerische Variablen benutzen, # diese werden automatisch in Strings umgewandelt. if ( $numericVar eq 17 )
# Einlesen der Werte für $v1 und $v2 von der Tastatur my $v1 = <STDIN>; my $v2 = <STDIN>; my $equalFlag = ( $v1 eq $v2 ); # Hier kommt keine Fehlermeldung wie beim numerischen # Vergleich mit "==", wenn man keine Zahlen eingibt.
Operator ne Der ne-Operator entspricht dem !=-Operator für Zahlen, jedoch werden die Operanden lexikalisch verglichen. Im Gegensatz zum !=-Operator funktioniert der ne-Operator sowohl für Zahlen als auch für Strings. Der lexikalische Vergleich ist case-sensitive. Beispiel: # Einlesen der Werte für $v1 und $v2 von der Tastatur my $v1 = <STDIN>; my $v2 = <STDIN>; my $notEqualFlag = ( $v1 ne $v2 ); # Hier kommt keine Fehlermeldung wie beim numerischen # Vergleich mit "!=", wenn man keine Zahlen eingibt.
Operator gt Der gt-Operator entspricht dem >-Operator für Zahlen, jedoch werden die Operanden lexikalisch verglichen. Der lexikalische Vergleich ist case-sensitive. Beispiel: "hallo" gt "Hallo" # # "300" gt "4" # # 300 gt 4 # #
Evaluiert zu true, weil 'hallo' nach 'Hallo' kommt. Evaluiert zu false, da 3 im Alphabet vor 4 kommt. Dito, Zahlen werden automatisch in Strings konvertiert.
114
2
Grundlagen
Vorsicht ist geboten, wenn man Zahlen mit dem gt-Operator vergleichen möchte: D:\>perl -w use strict; # Einlesen zweier Werte von der Tastatur my $v1 = <STDIN>; my $v2 = <STDIN>; if ( $v1 gt $v2 ) { print( "$v1 groesser als $v2\n" ); } else { print( "$v2 kleiner als $v2\n" ); } ^Z 3 10 3 groesser als 10 D:\>
Wie wir sehen, hat da irgendjemand einen Fehler gemacht. Wie so häufig muss man diesen beim Programmierer suchen und nicht den Interpreter beschimpfen. Die Ausgabe ist lexikalisch sortiert richtig, numerisch sortiert aber falsch, also haben wir im Programmcode einen Denkfehler gemacht. Abhilfe für das Problem schafft folgender Code, der zum einen auch Zahlen richtig vergleicht, auf der anderen Seite aber beliebige Strings vergleicht (natürlich nur bis zur angegebenen maximalen Länge): my $v1 = <STDIN>; my $v2 = <STDIN>; $v1 = sprintf( "%10d", $v1 ); $v2 = sprintf( "%10d", $v2 );
Im Moment müssen wir die Funktion sprintf() noch nicht verstehen, es reicht zu wissen, dass die Variablen mit führenden Nullen aufgefüllt werden, so dass sie immer eine Länge von 10 Zeichen haben.
Operator lt Der lt-Operator entspricht dem <-Operator für Zahlen, jedoch werden die Operanden lexikalisch verglichen. Der lexikalische Vergleich ist case-sensitive. Beispiel: "hallo" lt "Hallo" # Evaluiert zu false, weil 'hallo' # nach 'Hallo' kommt. "300" lt "4" # Evaluiert zu true, da 3 im Alphabet
Operatoren
300 lt 4
115 # vor 4 kommt. # Dito, Zahlen werden automatisch in # Strings konvertiert.
Vorsicht ist geboten, wenn man Zahlen mit dem lt-Operator vergleichen möchte: D:\>perl -w use strict; # Einlesen zweier Werte von der Tastatur my $v1 = <STDIN>; my $v2 = <STDIN>; if ( $v1 lt $v2 ) { print( "$v1 kleiner als $v2\n" ); } else { print( "$v2 groesser als $v2\n" ); } ^Z 3 10 3 groesser als 10 D:\>
Es tritt wie schon beim gt-Operator ein Fehler auf, da die Ausgabe lexikalisch sortiert richtig ist, numerisch sortiert aber falsch. Auch hier kann man mit der sprintf()-Funktion Abhilfe schaffen: my $v1 = <STDIN>; my $v2 = <STDIN>; $v1 = sprintf( "%10d", $v1 ); $v2 = sprintf( "%10d", $v2 );
Damit werden alle Eingaben mit führenden Nullen aufgefüllt, und die Sache ist im Lot.
Operator ge Der ge-Operator entspricht dem >=-Operator für Zahlen, jedoch werden die Operanden lexikalisch verglichen. Der lexikalische Vergleich ist case-sensitive. Man kann bei Zahlen in die gleiche Falle wie bei gt und lt stolpern.
Operator le Der le-Operator entspricht dem <=-Operator für Zahlen, jedoch werden die Operanden lexikalisch verglichen. Der lexikalische Vergleich ist case-sensitive. Auch hier ist bei Zahlenvergleichen Vorsicht geboten.
116
2
Grundlagen
Operator cmp Der cmp-Operator vergleicht beide Operanden lexikalisch und evaluiert je nach Ergebnis zu einem von 3 möglichen Werten. Der lexikalische Vergleich ist case-sensitive. Beispiele: "10" cmp 9 evaluiert zu -1 ("10" kommt lexikalisch vor 9) "10" cmp 10 evaluiert zu 0 ("10" ist lexikalisch gleich 10) "10" cmp "09" evaluiert zu 1 ("10" kommt lexikalisch nach "09") 0 cmp -3 evaluiert zu 1 (Das Zeichen "-" kommt lexikalisch vor den Ziffern)
Der cmp-Operator wird in der sort()-Funktion verwendet, um die Sortierung anzupassen, damit ein lexikalischer Vergleich durchgeführt wird (dies ist die Default-Einstellung von sort()). Siehe hierzu auch das Gegenstück, den Operator <=>, der für einen numerischen Vergleich sorgt. Auch bei der Beschreibung der sort()-Funktion in Anhang C werden wir den Operator noch einmal näher betrachten. Deutsche Sonderzeichen (das sind die Umlaute und das scharfe »ß« kommen in der Codetabelle der Zeichen und damit bei einer Sortierung nach allen Buchstaben, was beim sortierten Ausgeben von deutschen Texten zu Problemen führt. Beispiel: use strict; my $w1 = "augen"; my $w2 = "äugen"; if ( ( $w1 cmp $w2 ) > 0 ) { print( "$w1 kommt nach $w2\n" ); } else { print( "$w1 kommt vor $w2\n" ); }
Wenn wir den gezeigten Programmcode ausführen, erhalten wir die Ausgabe: augen kommt vor äugen
Das ist natürlich falsch, da das Wort »äugen« lexikalisch wie das Wort »aeugen« behandelt werden und damit lexikalisch vor »augen« kommen muss. Die Direktive use locale; löst das Problem, da nun deutsche Umlaute richtig behandelt werden (wir werden noch öfter auf dieses Problem zu sprechen kommen):
Operatoren
117
use strict; use locale; my $w1 = "augen"; my $w2 = "äugen"; if ( ( $w1 cmp $w2 ) > 0 ) { print( "$w1 kommt nach $w2\n" ); } else { print( "$w1 kommt vor $w2\n" ); }
Nun gibt unser Programmcode richtigerweise aus: augen kommt nach äugen
Die Direktive use locale; in Verbindung mit deutschen Umlauten funktioniert natürlich nur, wenn der Computer auf deutsche Sprache eingestellt ist! Andernfalls nimmt Perl an, es handelt sich um amerikanische Computer, und die können nun mal keine deutschen Umlaute (obwohl sie mindestens 5 Sprachen sprechen: Englisch, Amerikanisch, Kanadisch, Hawaiianisch, Australisch).
2.7.10 Bit-Operatoren Bisher haben wir Operatoren kennen gelernt, die sich mit Strings, Zahlen und booleschen Werten beschäftigen. Nun wollen wir ans Eingemachte gehen und Operatoren besprechen, die ihren Wirkungsbereich mehr in den Registern der CPU haben, die so genannten Bit-Operatoren. Dort existieren nur »Nullen« und »Einsen«. A propos, da wir gerade von CPU-Registern sprechen, sei ein kleiner Hinweis erlaubt: Alle Bit-Operatoren sind auf die Breite der CPU-Register beschränkt, die im Moment auf nahezu allen Rechnern noch 32 Bit beträgt. Das bedeutet, dass Sie keine Zahlen oder Strings (mit Strings macht man in der Regel jedoch sowieso keine Bit-Operationen) für BitOperatoren verwenden sollten, die länger als 32 Bit sind. In naher Zukunft, wenn wir in der 64-Bit-Welt leben werden, haben wir immerhin die doppelte Länge (was spätestens 2038 der Fall sein wird, weil dann auf 32-Bit-Rechnern kein Datum mehr funktioniert).
Operator & Mit dem &-Operator wird eine bitweise UND-Verknüpfung beider Operanden durchgeführt. Beispiel: my $op1 = 0x7f; my $op2 = 0x3; my $result = $op1 & $op2;
118
2
Grundlagen
# $result enthält 0x03 0b0111 1111 (0x7f) 0b0000 0011 (0x03) ----------0b0000 0011 (0x03)
Operator | Mit dem |-Operator wird eine bitweise ODER Verknüpfung beider Operanden durchgeführt. Beispiel: my $op1 = 0x7f; my $op2 = 0x3; my $result = $op1 | $op2; # $result enthält 0x7f 0b0111 1111 (0x7f) 0b0000 0011 (0x03) ----------0b0111 1111 (0x7f)
Operator ^ Mit dem ^-Operator wird eine bitweise EXOR Verknüpfung beider Operanden durchgeführt. Beispiel: my $op1 = 0x7f; my $op2 = 0x3; my $result = $op1 ^ $op2; # $result enthält 0x7c 0b0111 1111 (0x7f) 0b0000 0011 (0x03) ----------0b0111 1100 (0x7c)
Operator << Der <<-Operator verschiebt die Bits des linken Operanden um so viele Bits nach links, wie der rechte Operand angibt. Der Operator darf nur auf Integerwerte angewandt werden. Außerdem ist der Zahlenbereich von der Breite der CPU-Register abhängig.
Operatoren
119
Beispiel: my $i = 3; $i = $i << 4; # oder so $i <<= 4; # $i enthält 48, denn: 0b0000 0011 << 4 ----------0b0011 0000 = 0x30 = 48
Operator >> Der >>-Operator verschiebt die Bits des linken Operanden um so viele Bits nach rechts, wie der rechte Operand angibt. Der Operator darf nur auf Integerwerte angewandt werden. Außerdem ist der Zahlenbereich von der Breite der CPU-Register abhängig. Das Vorzeichen wird nicht mitgeschoben. Beispiel: my $i = 18; $i = $i >> 4; # oder so $i >>= 4; # $i enthält 1 0b0001 0010 << 4 ----------0b0000 0001 = 0x1 = 1
Dereferenzierungsoperator -> Der Dereferenzierungsoperator -> dient dazu, auf ein Hash- oder Array-Element über eine Referenzvariable zuzugreifen. Bei Objekten wird er verwendet, um Objektmethoden aufzurufen oder auf Objektattribute zuzugreifen. Damit werden wir uns im Kapitel über Objektorientierte Programmierung noch ausführlich befassen. Auch normale Funktionen von Perl-Modulen können über diesen Operator aufgerufen werden. Beispiele: my @array = ( 1, 2, 3, ); # Array-Referenzvariable: my $arrayRef = \@array; my %hash = ( "type" => "tree", "subtype" => "palm", ); # Hash-Referenzvariable: my $hashRef = \%hash;
120
2
Grundlagen
# Array-Referenz auf anonymes Array: my $refArray = [ 4, 5, 6, ]; # Hash-Referenz auf anonymes Hash my $refHash = { "gender" => "f", "age" = 17, }; $arrayRef->[ 2 ]
# 3. Element von @array über # Referenzvariable
$hashRef->{ "type" } # Element mit dem Key "type" von # %hash über Referenzvariable $refArray->[ 1 ]
# 2. Element des anonymen Arrays # $refArray
$refHash->{ "age" }
# Element mit dem Key "age" des # anonymen Hash $refHash
Verwendung bei Objekten: use IO::Handle; # Aufruf der Methode autoflush() des Objekts # IO::Handle::STDOUT STDOUT->autoflush( 1 ); # Prozedurale Verwendung bei Perl-Modulen # nicht-objektorientierte Programmierung: my $dbh = DBI->connect(...);
Adressoperator \ Der Adressoperator \ wird verwendet, um die Speicheradresse eines Arrays, eines Hashs oder einer Funktion einer Referenzvariable zuzuweisen. Beispiele siehe Dereferenzierungsoperator. # Referenz auf eine Funktion, die das erste Argument # ausgibt. sub myFunc { my $arg = shift; $arg = "undef" unless ( defined( $arg ) ); print( "$arg\n" ); } my $funcRef = \&myFunc; # Aufruf der Funktion über die Referenzvariable &{ $funcRef }( "hello" );
Operatoren
121
I/O-Operator <> Der <>-Operator liest aus einer Datei zeilenweise Daten ein. Wenn kein FileHandle angegeben ist, dann wird das vordefinierte FileHandle STDIN verwendet. Der Operator arbeitet unterschiedlich, je nachdem, ob er im skalaren oder im List-Kontext verwendet wird. Zeilenende-Zeichen sind Bestandteil der eingelesenen Zeilen. An dieser Stelle sollte ich ein paar Sätze zu FileHandles sagen: Unter einem FileHandle versteht man eine Systemressource, die das Betriebssystem zur Verfügung stellt, damit man unter anderem auf Dateien im Filesystem der Festplatte zugreifen kann (Lesen, Schreiben, Ändern, Erzeugen). Die Verwaltung solcher FileHandles erfolgt durch das so genannte »I/O-Subsystem« (ins Deutsche übersetzt: »Eingabe/Ausgabesystem«) des Betriebssystemkerns, der neudeutsch auch »Kernel« genannt wird. Zu den Daten, die über FileHandles des I/O-Systems verarbeitet werden können, gehören auch Tastatureingaben, Bildschirmausgaben sowie Datenströme über so genannte »Pipes« oder Netzwerkverbindungen in Form von »Sockets«. Unter einer »Pipe« muss man sich eine Datenverbindung zwischen zwei Prozessen vorstellen, die wie ein Wasserrohr funktioniert: Der Prozess am linken Rohrende schiebt Daten durch das Rohr auf die rechte Seite, wo ein anderer Prozess die ankommenden Daten in Empfang nimmt, um sie weiterzuverarbeiten. Das Betriebssystem stellt jedem Prozess drei vordefinierte FileHandles zur Verfügung: 왘 STDIN (Standard Eingabe) 왘 STDOUT (Standard Ausgabe) 왘 STDERR (Standard Fehlerausgabe) Die Daten von STDIN werden in der Regel von der Tastatur eingelesen (das muss aber nicht immer der Fall sein, zum Beispiel dann nicht, wenn man die Standardeingabe mit Hilfe des <-Zeichens beim Aufruf des Programms umlenkt; mehr dazu unten). Die Daten nach STDOUT werden in der Regel am Bildschrim ausgegeben (falls nicht mit Hilfe des >-Zeichens die Standard Ausgabe umgelenkt wurde, siehe weiter unten). Die Daten nach STDERR gehen in der Regel auf das gleiche Ausgabemedium wie diejenigen nach STDOUT. Man kann jedoch auch die Standardfehlerausgabe umlenken. Mehr zu FileHandles finden Sie im Kapitel über Ein-/Ausgabe. Beispiel für das zeilenweise Einlesen von Daten über das vordefinierte FileHandle STDIN: # Einlesen von Daten mit "<>" in skalarem Kontext # und Standard-FileHandle STDIN while ( defined( my $line = <STDIN> ) ) {
122
2
Grundlagen
# Zeilenende-Zeichen entfernen chomp( $line ); print( "$line\n" ); }
Der Operator <> erlaubt, dass man das FileHandle weglässt: while ( defined( my $line = <> ) ) { # Zeilenende-Zeichen entfernen chomp( $line ); print( "$line\n" ); }
Ich bevorzuge jedoch die erste Variante, bei der das FileHandle explizit angegeben ist, auch wenn es entfallen darf, weil es die Standardeinstellung ist. Das Programm wird dadurch verständlicher. Es folgt ein Beispiel für das Einlesen aller Zeilen einer Datei. Hier wird der Operator <> nicht im skalaren Kontext, sondern im List-Kontext verwendet, weil die eingelesenen Zeilen einer Array-Variable zugewiesen werden: # Wir verwenden das Modul "FileHandle", das in der # Standard-Distribution von Perl enthalten ist. use FileHandle; # Öffnen der Datei "C:\temp\data.txt" für lesenden # Zugriff my $fh = new FileHandle( "C:/temp/data.txt" ); # Prüfung, ob die Datei zum Lesen geöffnet werden # konnte unless ( $fh ) { print( STDERR "Fehler\n" ); exit( 1 ); } # Einlesen aller Zeilen der Datei # Hinweis: Wenn die Datei kein Zeilenende-Zeichen # enthält, dann steht der gesamte Datei-Inhalt im # ersten Element von @lines (z.B. bei Binärdateien). my @lines = <$fh>; # @lines enthält alle Zeilen der Datei als Elemente # (inklusive Zeilenende-Zeichen). # NICHT vergessen: Die Datei muss wieder geschlossen # werden, damit die damit verbundene Systemressource # im Betriebssystem freigegeben wird. undef( $fh );
Operatoren
123
Beispiel für das Einlesen des gesamten Datei-Inhalts in eine skalare Variable: # Wir verwenden das Modul "FileHandle", das in der # Standard-Distribution von Perl enthalten ist. use FileHandle; # Öffnen der Datei "C:\temp\data.txt" für lesenden # Zugriff my $fh = new FileHandle( "C:/temp/data.txt" ); # Prüfung, ob die Datei zum Lesen geöffnet werden # konnte unless ( $fh ) { print( STDERR "Fehler\n" ); exit( 1 ); } # Einlesen des gesamten Inhalts der Datei my $data = join( "", <$fh> ); # $data enthält den Inhalt der gesamten Datei als String # NIEMALS vergessen: FileHandles müssen sofort nach # Gebrauch wieder geschlossen werden. undef( $fh );
Bereichsoperator .. Der Bereichsoperator .. definiert einen aufsteigenden Bereich von Zahlen oder Buchstaben bzw. Ziffern (und sogar Strings) und gibt eine Liste zurück. Wir haben ihn schon bei der Vorstellung von Arrays gesehen. Er ist deshalb sehr praktisch, weil man sich damit eine Menge Schreibarbeit sparen kann. Beispiele: # Ausgabe der Zahlen 1 bis 100 foreach my $i ( 1 .. 100 ) { print( "i = $i\n" ); } my @hexDigits = ( "0" .. "9", "a" .. "f" ); # @hexDigits enthält # ( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f )
Wichtig beim Bereichsoperator ist, dass alle Zeichen im angegebenen Bereich monoton aufsteigend sein müssen. Beispiele: my @chars = ( "A" .. "z" ); # Liefert nicht etwa ( "A" bis "Z" und "a" bis "z" ), # sondern nur "A" bis "Z", weil "z" nicht in der
124 # # # # #
2
Grundlagen
aufsteigenden Zeichenklasse der Großbuchstaben enthalten ist. Es wird die gesamte Zeichenklasse, in der "A" enthalten ist, beginnend bei "A" in das Array gestellt.
my @list = ( "y" .. "Z" ); # Liefert nur ( "y", "z" ), # weil "Z" nicht in der aufsteigenden Zeichenklasse # der Kleinbuchstaben enthalten ist. # Es wird, beginnend bei "y", der Rest der Zeichenklasse # der Kleinbuchstaben in das Array gestellt. # Das Ganze funktioniert auch bei Strings, die # Zahlen mit führenden Nullen enthalten: my @mdayNums = ( "01" .. "31" ); # @mdayNums enthält die Nummern der Monatstage # in zweistelligem Format mit führender Null.
Außerdem kann der Bereichsoperator noch bei Arrays verwendet werden, um Teile aus dem Array zu erhalten: my @array = ( 1, 2, 3, 4, 5, 6, 7, ); my @array1 = @array[ 1 .. 4 ]; # @array1 enthält ( 2, 3, 4, 5 )
Um Bereiche aus dem Array @array anzusprechen, muss als Typkennzeichen das AtZeichen »@« vor dem Variablennamen array stehen, nicht das Dollarzeichen $. Der Bereichsoperator hat im skalaren Kontext eine andere Bedeutung als im List-Kontext und wirkt dann wie ein Flipflop. In diesem Kontext kann man zusätzlich den Operator mit 3 statt 2 Punkten verwenden. Mehr Informationen hierzu erhalten Sie mit: perldoc perlop
Bedingungsoperator ?: Der Bedingungsoperator ?: kann als Abkürzung einer if/else-Abfrage verwendet werden (if und else werden weiter unten erläutert). Beispiel: my $v1 = 5; my $v2 = 3; my $flag = undef;
Operatoren
125
if ( $v1 > $v2 ) { $flag = 1; } else { $flag = 0; } # Abkürzung mit dem ?:-Operator my $flag = ( $v1 > $v2 ) ? 1 : 0;
Zunächst wird der Ausdruck links vom Fragezeichen ? evaluiert. Ist das Ergebnis TRUE, dann wird der Ausdruck links vom Doppelpunkt evaluiert, ansonsten der Ausdruck rechts vom Doppelpunkt. Ich setze grundsätzlich Klammern um den Ausdruck vor dem Fragezeichen, damit wird der Code besser lesbar und verständlicher, weil die Prioritäten der Operatoren dann keine Rolle spielen.
Operator <<; Dieser Operator ist eine gute Hilfe, wenn man einen längeren Text entweder ausgeben oder in einer Variable speichern möchte. Am besten, Sie sehen sich die Arbeitsweise des Operators anhand eines Beispiels an: # Ein etwas längerer Text für eine skalare Variable: my $html = "\n" . "\t\n" . "\t\t
Titel\n" . "\t\n" . "\t\n" . "\t\tDas ist der Body\n" . "\t\n" . ""; # Mit dem "<<;"-Operator geht es einfacher: my $html = <<EOT;
Titel Das ist der Body EOT
126
2
Grundlagen
Wie wir deutlich sehen, ist der <<;-Operator eine echte Hilfe, weil man die Zeilenvorschübe und Tabulatorzeichen direkt eingeben kann. In der Variable wird einfach der gesamte Text gespeichert, der vom Strichpunkt des Operators und dem String EOT umrahmt wird. Das Zeilenende-Zeichen nach dem Strichpunkt des Operators ist nicht Bestandteil des Textes. Noch ein Wort zu EOT: Zwischen < und dem Strichpunkt des Operators muss ein Identifier stehen (case-sensitive). Das ist einfach eine beliebige Zeichenkette, die als Kennzeichen für das Textende benutzt wird. Um vom Interpreter auch wirklich als Textende erkannt zu werden, muss es am Beginn einer Zeile (ohne Leerzeichen) stehen. Auch darf nach dem Kennzeichen nichts in dieser Zeile stehen (auch kein Kommentar). Man hätte genauso gut wrzlbrmpfd als Kennzeichen verwenden können, aber ich glaube, EOT (End-Of-Text) passt besser. Beispiel für die Verwendung des <<;-Operators in Verbindung mit der print()-Funktion: print( <<EOT ); Content-Type: text/html EOT
Bei diesem Beispiel sehen wir eine Besonderheit, die nicht in Erscheinung tritt, wenn man wie so viele Perl-Programmierer zu faul ist, um Klammern zu schreiben, dann sieht der Code nämlich so aus: print <<EOT; Content-Type: text/html EOT
Perl zwingt Sie nicht dazu, Klammern bei Funktionsaufrufen zu setzen. Damit ist leider Tür und Tor für Missverständnisse beim Lesen der Programme geöffnet. Ich habe versucht, dieses Buch so zu schreiben, dass den meisten Lesern dieser gutmütige Charakterzug des Interpreters gar nicht auffällt. Vielmehr möchte ich Ihnen vorgaukeln, dass Klammern bei Funktionsaufrufen zwingend vorgeschrieben sind (zu Funktionen komme ich noch ausführlich). Leider hakt das konsequente Setzen von Klammern an manchen Stellen, denn im obigen Beispiel würde man vermuten, dass der Code mit Klammern so aussehen müsste: use strict; print( <<EOT; Content-Type: text/html EOT );
Operatoren
127
Zwischen dem String << und dem Semikolon des Operators steht der Identifier für das Textende (EOT), im ersten Beispiel jedoch haben wir zwischen dem String EOT und dem Semikolon die schließende Klammer des print()-Aufrufes. Machen wir es im letzten Beispiel aber vermeintlich richtig, dann setzt es prompt eine Fehlermeldung vom Interpreter: syntax error at - line 5, near "EOT ;" (Might be a runaway multi-line << string starting on line 2) Execution of - aborted due to compilation errors.
Im Text, der zwischen Operator und Textende-Kennzeichen steht, führt der Interpreter wie gewohnt die Evaluierung von Variablen durch. Funktionsaufrufe sind jedoch nicht erlaubt (oder sagen wir besser, sie werden nicht evaluiert, sondern sind einfach nur Bestandteil des Textes): sub myFunc { print( "bla" ); } my $title = "Hello World"; print( <<EOT ); $title Wie ist das Wetter heute? myFunc() EOT
Wenn wir den Code ausführen, bekommen wir folgende Ausgabe: Hello World Wie ist das Wetter heute? myFunc()
Ich glaube, diese Ausgabe ist nicht wirklich im Sinne des Erfinders. Während der Wert der Variable $title wie erwartet eingesetzt wird ($title wird evaluiert), erfolgt kein Aufruf der Funktion myFunc(), sondern der String myFunc() ist einfach nur Text. Auch wenn eine Variable innerhalb von einfachen Quotes steht, evaluiert der Interpreter die Variable, weil die einfachen Quotes keine Sonderbedeutung haben. Stellen Sie sich den Bereich zwischen dem Operator und dem Endekennzeichen einfach als String in doppelten Quotes vor: my $var = "Egon"; print( <<EOT ); Hallo '$var' EOT
128
2
Grundlagen
Die Ausgabe ist: Hallo 'Egon'
Operator => Dieser Operator wird als Trenner zwischen Key und Value eines Hash-Elements verwendet. Beispiel: my %hash = ( "age" => 20, "fn" => "Gundel", );
Mehr Beispiele hierzu finden Sie in der Beschreibung von Hashes und Hash-Variablen weiter oben.
Operator qw Dieser Quoting-Operator wird häufig der Einfachheit halber verwendet, wenn mehrere Elemente einer Liste in Quotes gesetzt sein müssen, zum Beispiel: # Importieren eines Moduls use myModule ( 'func1', 'func2', 'func3' ); use vars ( '$var1', '%var2', '@var3' ); my @array = ( "a", "b", "c", ); # Einfacher ist: use myModule qw( func1 func2 func3 ); use vars qw( $var1 %var2 @var3 ); my @array = qw( a b c );
Mit dem Operator qw entfallen nicht nur die Quotes, sondern auch die Kommata zwischen den einzelnen List-Elementen. Diese werden durch »white space« voneinander getrennt (»white space« sind Blanks, Tabulator- und Zeilenende-Zeichen). Das nächste Zeichen nach dem Operator qw, das kein white space ist, wird als Begrenzungszeichen der Liste verwendet. Man darf also auch durchaus andere Zeichen als die runde Klammer für die Liste benutzen, zum Beispiel: my @array = qw| a b c |; use vars qw[ $var1 %var2 @var3 ];
Dieses Feature kann man ausnutzen, wenn die runde Klammer zum Beispiel selbst Bestandteil der Liste ist: my @array = qw| a ( b |; # Folgender Code würde eine Fehlermeldung bringen: my @array = qw( a ( b );
Operatoren
129
# Will man auf die Klammern als List-Begrenzer nicht # verzichten, muss man den Backslash zur Entwertung # der Sonderbedeutung verwenden: my @array = qw( a \( b );
An dieser Stelle sei erwähnt, dass es zusätzlich noch weitere Operatoren für das Quoting gibt. Ich möchte Sie hierfür auf die Online-Dokumentation von Perl verweisen: D:\>perldoc perlop
Dieser Befehl gibt z.B. die Online Dokumentation für Operatoren aus.
Matchingoperator m Der Matchingoperator m wird bei der Angabe von Pattern in Such- oder Ersetzungsoperationen verwendet, wenn statt des Standard-Trennzeichens / für das Pattern Matching ein anderes Zeichen verwendet werden soll. Das zu verwendende Trennzeichen steht direkt hinter dem Operator und kann ein beliebiges einzelnes Zeichen mit Ausnahme von Leerzeichen, Tab und Zeilenende-Zeichen sein. Wer jetzt mit den Begriffen »Matching« oder »Pattern« hadert: Ich habe diesen ein eigenes Kapitel gewidmet. Beispiel für die Verwendung des Operators m: # Das Suchmuster (Search Pattern) ist "/tmp" # und enthält das Standard-Trennzeichen für # Pattern Matching. # Dieses muss deshalb mit einem Backslash # entwertet werden, wenn man standardmässiges # Pattern Matching benutzt. if ( $str =~ /\/tmp/ ) { ... } # Mit dem Matching-Operator "m" kann ein anderes # Trennzeichen verwendet werden, # dieses sollte im Pattern nicht vorkommen # (z.B. wäre das Zeichen "~" eine gute Wahl). if ( $str =~ m~/~ ) { ... }
Substitutionsoperator s Der Substitutionoperator s wird genauso verwendet wie der Matchingoperator m, aber für Ersetzungsoperationen eingesetzt. Auch hier gilt, wie schon beim Matchingoperator: Falls Sie mit den Begriffen »Matching« oder »Pattern« noch nichts anfangen können, lesen Sie vorher das Kapitel »Pattern Matching«.
130
2
Grundlagen
Beispiele: my $str = "abc abdads dsskdf sdfsd ff"; # Ersetze alle Leerzeichen durch Linefeed-Zeichen. # Der Slash ist Standard-Trennzeichen # für den Substitutionsausdruck, # der aus drei Teilen besteht: # - Suchpattern # - Ersetzungspattern # - Optionen $str =~ s/ /\n/g; # Gleiche Operation, jedoch anderes Trennzeichen $str =~ s~ ~\n~g;
Binding-Operator =~ Der Binding-Operator =~ wird für Pattern Matching verwendet. Der rechte Operand ist ein Pattern und wird auf den linken Operanden angewandt, der meist ein String ist. Bei einer Suche nach Zeichenketten steht im rechten Operanden also der Suchstring, nach dem im linken Operanden gesucht wird. Die Verwendung des Binding-Operators wird ausführlich im Kapitel »Pattern Matching« beschrieben. Beispiel: my $string = "Das ist das Haus vom Nikolaus"; # Mit der folgenden Anweisung wird nach dem Pattern # "Haus" in $string gesucht: if ( $string =~ /Haus/ ) { # Die Variable $string enthält # die Zeichenkette "Haus". }
Binding-Operator !~ Der Binding-Operator !~ ist das genaue Gegenteil des Operators =~. Beispiel: my $string = "Das ist das Haus vom Nikolaus"; # Mit der folgenden Anweisung wird geprüft, ob der # String "Haus" in $string vorkommt if ( $string !~ /Haus/ ) { # Die Zeichenkette "Haus" kommt nicht in $string vor. }
Operatoren
131
# Man kann es natürlich auch so codieren: unless ( $string =~ /Haus/ ) { # Die Zeichenkette "Haus" kommt nicht in $string vor. }
Backtick-Operator ` Mit Hilfe des Backtick-Operators ` kann man aus einem Perl-Skript heraus externe Betriebssystemkommandos aufrufen (ähnlich wie mit der system()-Funktion). Im Gegensatz zur system()-Funktion öffnet der Perl-Interpreter zwischen aufgerufenem Programm und Perl Skript eine Pipe, d.h., Daten, die vom aufgerufenen Programm nach STDOUT oder STDERR ausgegeben werden, können vom Perl-Skript verarbeitet werden. Der Exit-Status des aufgerufenen Programms wird in der vordefinierten Variable $? gespeichert. Da dieser Status auch Zusatzinformationen in den höherwertigen 8 Bits enthält, muss man den Wert von $? durch 256 teilen (dies entspricht einer Bitverschiebung um 8 Bit nach rechts), um den eigentlichen Exit-Status des Programms zu erhalten. Der Perl-Interpreter wartet auf die Beendigung des aufgerufenen Programms, bevor er das nächste Perl-Statement ausführt. Enthält die Ausgabe des aufgerufenen Programms mehr als eine Zeile, dann wird dem Perl-Skript ein Array übergeben, das jede Ausgabezeile als einzelnes Array-Element enthält. Da dieser Operator nicht portabel ist, sollte man ihn nur dann einsetzen, wenn man sicher ist, dass das Perl-Skript nur auf einer bestimmten Plattform laufen soll. Beispiele: # UNIX-Kommando ls my $cmd = "ls -l" # Gesamte Ausgabe des ls Kommandos in die skalare # Variable $output übernehmen # (Zeilenende-Zeichen sind Bestandteil des Strings) my $output = join( "", `$cmd` ); # Oder auch: my @output = `$cmd`; # Die Zeilenende-Zeichen werden als Trenner für die # einzelnen Array-Elemente verwendet # d.h., jedes Array-Element enthält eine Zeile (inkl. # Zeilenende-Zeichen) der Ausgabe. # Exit-Status des aufgerufenen Programms abfragen my $returnCode = ( $? >> 8 );
132
2
Grundlagen
Zeilenvorschübe in der Ausgabe des aufgerufenen Programms sind Bestandteil der Variable, in welcher die Ausgabe abgespeichert wird. Speziell enthält jedes ArrayElement als letztes Zeichen das Zeilenende-Zeichen. Der Inhalt von $? wird mit jedem neuen Aufruf eines externen Kommandos überschrieben, man sollte den Exit-Status des aufgerufenen Programms also direkt nach dessen Ausführung lesen.
2.8 Statements Statements (zu deutsch »Anweisungen«) sind Programmcode, der vom Interpreter in einem Stück abgearbeitet wird. Man unterscheidet einfache Statements (englisch: »simple statements«) und komplexe Statements (englisch »compound statements«). Ein einfaches Statement könnte z.B. so aussehen: # Zuweisung my $i = 5; # Funktions-Aufruf print( "$i\n" ); # Sehr einfaches Statement, das nur zum logischen Wert # TRUE evaluiert # In Perl-Modulen ist es immer die letzte Zeile des # Perl-Codes. 1;
Jedes einfache Statement wird in Perl mit einem Strichpunkt abgeschlossen. Komplexe Statements werden in geschweifte Klammern gestellt und bestehen wiederum aus einfachen Statements. Die Klammern dienen dazu, die einzelnen Anweisungen zu gruppieren und somit zu einer Einheit (dem »compound statement«) zusammenzufassen: Beispiele für ein »compound statement«: # Anonymer Codeblock { my $i = 1; my $j = $i + 1; } # # # #
Funktion Bei Funktionen stellt der Funktionsrumpf (das sind die Anweisungen in der Funktion) ein "compound statement" dar
Statements
133
sub myFunc { my $i = shift; print( "$i\n" ); } # if-Abfrage if ( $i > $j ) { print( "i > j\n" ); $i--; $j++; } else { print( i < j\n" ); $i--; $j++; }
Zur Auffrischung: Was ist in Perl TRUE, und was ist FALSE? Für den Interpreter ist alles FALSE, was undef ist, was die Zahl »0« (oder einen String, der nur aus der Ziffer "0" besteht) oder den leeren String ("") enthält.
2.8.1 Statement if Syntax: if ( Bedingung ) { statement } if ( Bedingung ) { statement } else { statement } if ( Bedingung ) elsif ( Bedingung elsif ( Bedingung ... else { statement
{ statement } ) { statement } ) { statement } }
Bedingung ist ein Ausdruck (englisch »expression«), der einen logischen Zustand evaluiert. statement muss ein komplexes Statement sein, auch wenn es sich nur um eine einzige Anweisung handelt. Es sind also zwingend geschweifte Klammern um die Einzelanweisung(en) zu setzen.
134
2
Grundlagen
Die Definition eines komplexen Statements enthält eigentlich bereits die geschweiften Klammern, d.h., normalerweise ist der Ausdruck compound statement
gleichbedeutend mit { simple statement simple statement ... }
In der Syntaxbeschreibung der if-Anweisung (und aller weiteren Statements, die noch folgen) habe ich aber die geschweiften Klammern explizit angegeben, weil sie zwingend vorgeschrieben sind. Der folgende Code führt also zu einem Fehler: ... if ( $i > 1 ) print( "i ist größer als 1" );
Natürlich müssen Sie nur einmal die geschweiften Klammern setzen. Ich hoffe, niemand kommt auf die Idee, eine »if«-Abfrage so zu machen: ... if ( $i > 1 ) { { print( "i ist größer als 1" ); } }
Die erste Variante der if-Abfrage führt das nachfolgende Statement nur dann aus, wenn der Ausdruck in den runden Klammern den logischen Zustand TRUE liefert. In der zweiten Variante wird entweder das Statement nach if ausgeführt (wenn die Bedingung TRUE ist) oder das Statement nach else (wenn die Bedingung FALSE ist). In der dritten Variante werden geschachtelte if-Abfragen mit dem Schlüssel-wort elsif verwendet (Vorsicht: elseif ist falsch, es muss elsif heißen). Diese Variante entspricht der switch-Anweisung anderer Programmiersprachen. Anmerkung meinerseits: In Perl existiert keine switch-Anweisung. Das ist kein Beinbruch, da man sowieso falsch programmiert, wenn man diese Anweisung häufig benötigt. Beispiele für das if-Statement: # Einlesen von der Tastatur my $line = <STDIN>; # Entfernen des Zeilenende-Zeichens chomp( $line );
Statements
135
if ( line eq "" ) { print( "Sie haben nichts eingegeben\n" ); } else { print( "Ihre Eingabe war: '$line'\n" ); }
Beispiel mit elsif: my $action = <STDIN>; chomp( $action ); if ( $action eq "a" ) { # führe Aktion append durch } elsif ( $action eq "d" ) { # führe Aktion delete durch } elsif ( $action eq "c" ) { # führe Aktion change durch } else { # keine der vorherigen Abfragen war TRUE print( STDERR "ungültige Aktion\n" ); }
Abkürzende Schreibweise bei nur einem Statement Wenn der if-Block nur ein einziges Statement enthält, kann man die Reihenfolge vertauschen: # Ausführliche Schreibweise if ( $flag ) { print( "flag ist TRUE\n" ); } # Abkürzende Schreibweise print( "flag ist TRUE\n" ) if ( $flag );
Diese Abkürzung ist verführerisch, weil man sich die geschweiften Klammern sparen kann. Ich habe allerdings bereits sehr oft die Erfahrung gemacht, dass man im Laufe der Entwicklung später Statements hinzufügt. Der if-Block besteht dann nicht mehr aus nur einem Statement und darf somit nicht mehr in der abgekürzten Version geschrieben werden. Dann muss man die if-Abfrage wieder umformatieren. Deshalb benutze ich persönlich die abkürzende Schreibweise relativ selten.
136
2
Grundlagen
2.8.2 Statement unless Das unless-Statement ist die Umkehrung von if (NOT if) und wird häufig verwendet, wenn eine if-Abfrage zu kompliziert ist. Hierzu ein typisches Beispiel, das häufig für Abfragen von HTML-Formularfeldern benutzt wird: # Komplizierte Abfrage mit if if ( ( ! $var1 ) or ( ! $var2 ) or ( ! $var3 ) ) { print( "keine der Variablen ist true\n" ); } # Dasselbe mit unless, nur wesentlich kürzer unless ( $var1 and $var2 and $var3) # Hier wurde ein boolesches Gesetz angewandt: ( not a ) or ( not b ) = not ( a and b )
Wie bei der if-Abfrage können auch bei unless else- sowie elsif-Zweige verwendet werden: unless ( $v ) { # $v hat den logischen Wert FALSE } else { # $v hat den logischen Wert TRUE }
Ebenso wie bei der if-Abfrage erlaubt Perl, auch das unless-Statement abzukürzen, wenn der unless-Block nur aus einem einzigen Statement besteht. Siehe hierzu das Beispiel der if-Abfrage.
2.8.3 Schleifen Schleifen werden häufig gebraucht, um bestimmte Programmteile mehrfach auszuführen. Je nach Programmlogik kann man unterscheiden zwischen Schleifen, bei denen vorher bekannt ist, wie oft die Schleife durchlaufen werden soll (for-Schleife), solchen, bei denen man die Anzahl der Schleifendurchläufe vorher nicht kennt (while-Schleifen, foreach-Schleifen), und gewollten Endlosschleifen (for- oder while-Schleifen), die aus der Schleife heraus explizit beendet werden. Grundsätzlich hat jede Schleife einen Schleifenkopf und einen Schleifenrumpf, der durch geschweifte Klammern gekennzeichnet ist und in dem die mehrfach auszuführenden Programmteile stehen. Alle Schleifen können mit einem Label versehen werden, weiter unten werden wir noch näher darauf zu sprechen kommen.
Statements
137
for-Schleife Syntax (Alle Angaben in eckigen Klammern sind optional und können auch entfallen): [ label ]for ( [Init]; [Bedingung]; [Nachbearb.] ) Rumpf # Definition von Rumpf: { statement }
Der Schleifenkopf einer for-Schleife besteht aus dem Bareword for und drei durch Strichpunkte getrennten Kopfteilen, die in runde Klammern gesetzt sind. Anschließend folgt der Schleifenrumpf, der in geschweifte Klammern gesetzt sein muss und ein oder mehrere Statements enthalten kann. Der erste Kopfteil dient der Initialisierung von Schleifenvariablen, meist Schleifenzählern. Es können mehrere Variablen durch Kommata getrennt initialisiert werden. Dieser Kopfteil wird nur ein einziges Mal zu Beginn durchlaufen. Der zweite Kopfteil enthält einen Ausdruck, der einen logischen Zustand (FALSE oder TRUE) liefern muss. Evaluiert der Ausdruck zu TRUE, dann wird der Schleifenrumpf durchlaufen, ansonsten wird die Schleife beendet, er enthält also die Abbruchbedingung für die Schleife (genauer gesagt, die Ausführungsbedingung für den Schleifenrumpf). Dieser Kopfteil wird vor jedem Schleifendurchlauf geprüft. Der dritte Kopfteil enthält Anweisungen, die nach jedem einzelnen Schleifendurchlauf ausgeführt werden, bevor der Bedingungsteil erneut evaluiert wird. Man kann auch hier mehrere Anweisungen durch Kommata getrennt angeben. Alle drei Kopfteile können leer sein, in diesem Fall hat man die klassische Endlosschleife vor sich (siehe Beispiele). Wenn Sie in Funktionen mit der for-Schleife die Elemente der Aufrufparameter ändern, dann wirkt sich die Änderung auf die Originale des aufrufenden Programmcodes aus. Detailinformationen erhalten Sie dazu, wenn ich auf Funktionen zu sprechen komme. Beispiele für die for-Schleife: # Einfache for-Schleife # Beachte: Es wird eine Schleifenvariable im Kopfteil # mit "my" deklariert und initialisiert, # damit gilt diese Variable nur innerhalb der Schleife. for ( my $i = 1; $i <= 10; $i++ ) { print( "i = $i\n" ); }
138
2
Grundlagen
# Typische Endlosschleife for ( ; ; ) { ... } # Schleife mit mehreren Variablen # Beachte: "my" darf im Initialisierungsteil nur vor der # ersten Variable stehen. for ( my $i = 0, $j = 1; ( $i < 10 ) and ( $j <= 100 ); $i++, $j++ ) { ... } # Schleife ohne Nachbearbeitung for ( my $i = 100; $i; ) { ... $i--; } # Schleife, in der nach jedem Schleifendurchgang # die Zählervariable um 3 hochgezählt wird for ( my $i = 0; $i < 10; i += 3 ) { ... } # Die Schleife wird also insgesamt 4-mal durchlaufen
foreach-Schleife Die foreach-Schleife wird meist in Zusammenhang mit Arrays verwendet, wenn man nacheinander alle Array-Elemente abarbeiten möchte, ohne dass man direkt über den Index auf einzelne Elemente zugreift. Syntax (alle Angaben in eckigen Klammern sind optional und können auch entfallen): [ label ]foreach variable ( list ) { statement }[ continue { Block } ]
variable sollte eine Variablendefinition mit my sein, list ist ein Ausdruck, der eine Liste liefert. Im einfachsten Fall ist dies eine Array-Variable oder eine konstante Liste, es kann aber auch ein beliebig komplexer Ausdruck sein; auch Funktionsaufrufe dürfen hier vorkommen.
Statements
139
Enthält die foreach-Schleife einen continue-Block, dann wird dieser nach dem Abarbeiten des letzten Statements im Schleifenrumpf durchlaufen, ähnlich wie der Nachbearbeitungsteil im Schleifenkopf der for-Schleife. Wenn Sie in Funktionen mit der foreach-Schleife die Elemente der Aufrufparameter ändern, dann wirkt sich die Änderung auf die Originale des aufrufenden Programmcodes aus, da keine Kopie der Liste, sondern eine Referenz auf die Original-Liste übergeben wird. Detailinformationen erhalten Sie dazu, wenn ich auf Funktionen zu sprechen komme. Beispiele für die foreach-Schleife: my @array = ( 1, 2, 3, ); my %hash = ( "type" => "file", "path" = "/tmp/b.txt", ); foreach my $ele ( @array ) { print( "element = '$ele'\n" ); } # Unsortierte Ausgabe der Hash-Elemente foreach my $key ( keys( %hash ) ) { my $val = $hash{ $key }; print( "Hash Element $key = '$val'\n" ); } # Dasselbe sortiert # Beachte: die Sortierung erfolgt lexikalisch foreach my $key ( sort( keys( %hash ) ) ) { my $val = $hash{ $key }; print( "Hash Element $key = '$val'\n" ); } # Beispiel für einen continue-Block my $count = 0; foreach my $ele ( @array ) { print( "$ele\n" ); } continue { $count++; } print( "array enthält $count Elemente\n" );
while-Schleife Die while-Schleife wird meist verwendet, wenn die Anzahl der Schleifendurchläufe nicht von vornherein bekannt ist. Die Abbruchbedingung wird immer vor dem Eintritt in den Schleifenrumpf geprüft.
140
2
Grundlagen
Syntax (alle Angaben in eckigen Klammern sind optional und können auch entfallen): [ label ]while ( Bedingung ) { statement }[ continue { Block } ]
Beispiele für die while-Schleife: # Endlosschleife while ( 1 ) { ... } # Eingabe-Schleife while ( defined( my $line = <STDIN> ) ) { chomp( $line ); ... } # Beachte: Obwohl die Variable "$line" innerhalb # des Funktionsaufrufs von defined() mit "my" # definiert wird, ist sie im gesamten Schleifenrumpf # gültig.
Wenn ein continue-Block angegeben ist, hat er dieselbe Bedeutung wie bei der foreachSchleife und wird nach dem letzten Statement des Schleifenrumpfs ausgeführt, bevor der nächste Schleifendurchlauf beginnt: my $i = 0; while ( $i <= 5 ) { print( "$i\n" ); } continue { $i++; }
do-Schleife Die do-Schleife ist der while-Schleife ähnlich, jedoch wird hier der Schleifenrumpf mindestens einmal ausgeführt, da die Abbruchbedingung am Ende des Schleifenrumpfes steht und nicht im Schleifenkopf. Syntax (alle Angaben in eckigen Klammern sind optional und können auch entfallen): [ label ]do { statement } while ( Bedingung ); do { statement } until ( Bedingung );
Statements
141
Der Unterschied beider Varianten ist, dass die do-Schleife mit while so lange ausgeführt wird, wie Bedingung TRUE ist, während die do-Schleife mit until so lange ausgeführt wird, wie die Bedingung FALSE ist. Beispiele für die do-Schleife: my $i = 0; do { print( "i = $i\n" ); $i++; } while ( $i < 10 ); do { print( "i = $i\n" ); $i++; } until ( $i == 20 ); do { print( "i = $i\n" ); $i--; } while ( $i );
next/last Syntax: next; last; next( "label" ); last( "label" );
label wird weiter unten beschrieben. Häufig ergibt sich die Abbruchbedingung für eine Schleife innerhalb des Schleifenrumpfes aufgrund der Programmlogik. Perl bietet mit den Statements next sowie last die Möglichkeit, den nächsten Schleifendurchgang sofort bedingungslos zu beginnen (next-Statement) bzw. die Schleife sofort bedingungslos zu beenden (last-Statement). Wenn mehrere Schleifen geschachtelt sind, wirken sich next und last nur auf diejenige Schleife aus, in deren Schleifenrumpf die next- bzw. last-Anweisung steht. Beispiel: # Zwei geschachtelte Schleifen for ( my $i = 1; $i <= 10; i++ ) { while ( defined( my $line = <STDIN> ) ) { # Zeilenende-Zeichen entfernen chomp( $line ); if ( $line eq "q" ) {
142
2
Grundlagen
last; } if ( $line eq "n" ) { next; } print( "$line\n" ); } }
Was macht der Programmcode? Nun, eigentlich nichts wirklich Sinnvolles. Mir geht es nur darum, Ihnen zu zeigen, wie man Schleifen beendet oder den nächsten Schleifendurchlauf erzwingt. Und nebenbei, auf was man dabei achten muss, wenn Schleifen geschachtelt werden. Die äußere Schleife wird insgesamt 10-mal durchlaufen. In der inneren Schleife wird mit jedem Schleifendurchlauf eine Zeile von der Standardeingabe eingelesen. Gibt man das Zeichen »q« ein, dann soll das Programm beendet werden, ohne dass die eingegebene Zeile noch einmal ausgegeben wird. Gibt man »n« ein, soll die nächste Zeile eingelesen werden (ebenfalls keine Ausgabe der Zeile). Ansonsten wird das, was man eingegeben hat, noch einmal ausgegeben. Nehmen wir jetzt einmal an, wir geben ständig »q« ein. Damit wird die Eingabe nicht noch einmal angezeigt, weil die innere Schleife beendet wird. Es wird leider nur die innere Schleife beendet, nicht aber die äußere (was wir eigentlich vorhatten). Das bedeutet, dass wir insgesamt 10-mal »q« eingeben müssen, bis das Programm endlich damit aufhört, Eingaben von uns zu erwarten. Da liegt wohl ein Fehler vor. Im nächsten Abschnitt werden wir aber sehen, wie man dieses Fehlverhalten abstellt.
Labels Labels werden bei geschachtelten Schleifen benutzt, um bestimmte Schleifen der geschachtelten Struktur entweder gezielt mit last zu beenden oder mit next den nächsten Schleifendurchlauf zu beginnen. Syntax: labelName:
Beispiele für Labels: # Bildung einer Prüfsumme my $chksum = 0; # Geschachtelte Schleife loop1: while ( defined( my $line = <STDIN> ) ) {
Statements
143
# Zeilenende-Zeichen entfernen chomp( $line ); foreach my $c ( split( "", $line ) ) { if ( $c eq "Q" ) { last( "loop1" ); } $chksum ^= ord( $c ); } } printf( "Prüfsumme = '0x%02x'\n", $chksum );
Bei der Anweisung last( "loop1" );
habe ich das Label in doppelte Anführungszeichen gesetzt, weil dies vor Version 5.6.1 mit use strict; notwendig war. Seit Version 5.6.1 von Perl kann das Label auch als Bareword angegeben werden: last( loop1 );
Wir haben hier ein Beispiel zur Bildung einer Prüfsumme mit zwei geschachtelten Schleifen vor uns. Die äußere Schleife hat über ein Label den Namen »loop1« bekommen und dient dazu, von der Standardeingabe zeilenweise Zeichen einzulesen. Sie soll dann beendet werden, wenn im Zeichenstrom ein »Q« enthalten ist. Die innere Schleife hat keinen Namen (braucht sie auch nicht; die innersten Schleifen dürfen ungestraft ohne Namen sein, weil es keinen Sinn hat, aus einer äußeren Schleife eine innere zu beenden, sondern nur umgekehrt). In dieser Schleife über alle Zeichen der eingegebenen Zeile wird eine Prüfsumme gebildet. Wenn das Zeichen »Q« erkannt wird, soll die äußere Schleife beendet werden. Würde man beim last-Statement den Namen der äußeren Schleife weglassen, dann endet nur die innere Schleife, d.h., die Verarbeitung der aktuellen Zeile wird abgebrochen. Wir wollen in diesem Fall aber das Einlesen der Daten beenden und müssen somit aus der inneren Schleife heraus die äußere beendet, deshalb müssen wir deren Namen beim last-Statement angeben.
Statement redo Syntax (alle Angaben in eckigen Klammern sind optional und können auch entfallen): redo([ Label ]);
144
2
Grundlagen
In allen Schleifen kann ein redo-Statement enthalten sein, das einen sofortigen Neudurchlauf des Schleifenrumpfs bewirkt, ohne dass bei for-Schleifen der Nachbearbeitungsteil, bei foreach- oder while-Schleifen der continue-Block ausgeführt wird. Dieses Statement ist mit Vorsicht zu genießen, da man damit herrliche (und natürlich ungewollte) Endlosschleifen bauen kann. Es wird sehr selten benötigt, deshalb möchte ich die Wissbegierigen verweisen auf: D:\>perldoc perlsyn
2.8.4 Statement return Das return-Statement wird in Funktionen verwendet, um den Funktionsrumpf sofort bedingungslos zu beenden. Optional können ein oder mehrere Rückgabewerte Werte als Liste angegeben sein. Syntax (alle Angaben in eckigen Klammern sind optional und können auch entfallen): return[ expression ]; return[ (list) ];
Hinweis: expression bzw. list kann auch ein Funktionsaufruf sein, der wiederum einen skalaren Wert oder eine Liste zurückliefert. Es können nicht mehrere verschiedene Listen zurückgegeben werden, denn diese werden dem Aufrufer der Funktion als eine einzige Liste übergeben (siehe Beispiele unten). Beispiele für das return-Statement: sub myFunc { my ( $arg1, $arg2 ) = @_; ... if ( $arg1 < 0 ) { return undef; } return $arg1 * $arg2; }
Wie man sieht, kann man in einer Funktion durchaus unterschiedliche Werte mit dem return-Statement zurückgeben. Dazu mehr, wenn ich zu Funktionen komme. Nun ein Beispiel, wie man es nicht machen sollte: 01 # Beispiel, das nicht funktioniert 02 # Definition der Funktion "wrongFunc": 03 sub wrongFunc { 04 ...
Statements
145
05 my %hash = ( "a" => 1, "b" => 2, ); 06 my @arr = ( 1, 2, 3, ); 07 08 return ( %hash, @arr ); 09 } 10 11 # Aufruf der Funktion: 12 my ( %h, @array ) = wrongFunc();
Beim return-Statement in Zeile 08 passiert Folgendes: Der Interpreter packt beide Variablen %hash und @arr in eine einzige Liste, die an den Aufrufer der Funktion zurückgegeben wird. Die Liste enthält folgende Elemente: ( "a", 1, "b", 2, 1, 2, 3 )
Wenn wir nun in Zeile 12 versuchen, diese Liste in die zwei Variablen %h und @array zu packen, passiert Folgendes: Der Interpreter versucht, die gesamte zurückgelieferte Liste in der Hash-Variable %h zu speichern. Das kann natürlich nicht funktionieren, weil die Liste eine ungerade Anzahl von Elementen enthält, Hash-Variablen aber immer eine gerade Anzahl von List Elementen besitzen müssen, da jedes Hash-Element aus zwei Teilen, nämlich Key und Value besteht. Wir haben uns also eine Fehlermeldung eingehandelt. Noch schlimmer ist es, wenn man das Array @arr in der Funktion mit einer geraden Anzahl von Elementen erstellt: sub wrongFunc { ... my %hash = ( "a" => 1, "b" => 2, ); # @arr hat diesmal 4 Elemente my @arr = ( 1, 2, 3, 4 ); return ( %hash, @arr ); }
Jetzt erhält der Aufrufer nach der Zeile my ( %h, @array ) = wrongFunc();
ein Hash %h, das folgende Elemente enthält: ( "a" => 1, "b" => 2, "1" => 2, "3" => 4 )
146
2
Grundlagen
Außerdem liefert der Interpreter in diesem Fall noch nicht einmal eine Fehlermeldung, weil ja die Anzahl der List-Elemente geradzahlig ist. Die Variable @array hingegen enthält überhaupt keine Elemente, weil die gesamte Liste, die von der Funktion zurückgeliefert wurde, in das Hash gepackt wurde. Fazit: Will man mehrere unterschiedliche Listen aus einer Funktion heraus an den Aufrufer zurückgeben, dann muss man Referenzvariablen benutzen. Nähere Informationen zu diesem Thema werden wir bei der Besprechung von Funktionen erhalten.
2.9 Funktionen Funktionen, historisch bedingt auch Unterfunktionen genannt, werden verwendet, um ein Programm übersichtlicher zu gestalten und mehrfach benutzten Programmcode zu kapseln. Man muss strikt unterscheiden zwischen der Funktionsdeklaration bzw. der Funktionsdefinition auf der einen Seite und dem Funktionsaufruf andererseits. Wenn man eine Funktion deklariert bzw. definiert, dann führt der Interpreter keinerlei Aktionen außer einer Syntaxprüfung durch. Eine Funktionsdefinition bzw. Funktionsdeklaration erkennt man am Bareword sub. Funktionsaufrufe hingegen sind dadurch gekennzeichnet, dass hinter dem Funktionsnamen ein Paar runder Klammern folgt. Beispiel für die Deklaration bzw. Definition einer Funktion: # Funktionsdefinition # Wenn der Perl Interpreter beim Parsen der Sourcedatei # die folgenden Programmzeilen findet, # prüft er nur die Syntax. # Der Code wird aber nicht ausgeführt. sub myFunc { my $arg = shift; ... } # Funktionsdeklaration # Sie wird auch als "forward declaration" bezeichnet. # Damit weiß der Interpreter, dass die angegebene # Funktion benutzt wird, bevor sie definiert ist. sub myFunc;
Trifft der Interpreter beim Parsen des Sourcecodes auf einen Funktionsaufruf, dann führt er den Programmcode der Funktion aus, und der Programmfluss zweigt aus dem Hauptprogramm ab in den Code der aufgerufenen Funktion (der auch in einer
Funktionen
147
anderen Datei stehen kann als das Hauptprogramm). Der Programmfluss kehrt nach Beendigung der Funktion wieder in das Hauptprogramm zurück.
Abbildung 2.2: Programmfluss bei Funktionsaufrufen
Aufruf der Funktion myFunc() (erkennbar an den runden Klammern hinter dem Funktionsnamen): myFunc( "hallo" );
Eine Funktion wird entweder explizit durch das return-Statement beendet oder implizit, wenn das letzte Statement des Funktionsrumpfs abgearbeitet worden ist. In diesem Fall liefert die Funktion im skalaren Kontext den Wert undef zurück, im List-Kontext eine leere Liste.
2.9.1 Funktionsdefinition Die Deklaration bzw. Definition einer Funktion wird durch das Bareword sub eingeleitet, dem der Name der Funktion folgen muss. Bei anonymen Funktionen enfällt der Funktionsname. Dabei wird der Code des Funktionsrumpfs einer Referenzvariablen zugewiesen (Beispiel folgt). Der Funktionsname darf nur mit einem Buchstaben oder einem Unterstrich beginnen und im Weiteren nur aus Buchstaben, Ziffern und Unterstrichen bestehen. Der Funktionsname kann maximal 252 Zeichen lang sein.
148
2
Grundlagen
Nach dem Funktionsnamen folgt der eigentliche Programmcode der Funktion, eingeschlossen in geschweifte Klammern. Übrigens: Wenn ich von »Deklaration« spreche, bedeutet dies fast ausschließlich »Definition«, in Perl ist dies nahezu identisch. Syntax einer Funktionsdeklaration: sub name { Code } refVar = sub { Code };
Der Ausdruck sub name
ist der Funktionskopf, { Code }
ist der Funktionsrumpf, der in geschweiften Klammern stehen muss und den Code mit den Statements enthält, die beim Aufruf der Funktion ausgeführt werden. In der Regel sollten Funktionsnamen mit einem Kleinbuchstaben beginnen. Enthält der Funktionsname mehrere logische Wortteile, dann benutzt man Großbuchstaben, um die einzelnen Teile sichtbar zu unterscheiden. Unterstriche als Trenner für die Wortteile sollten Sie sich nicht angewöhnen, es sei denn, sie implementieren eine private Funktion, die nur innerhalb eines Moduls verwendet wird. In diesem Fall sollte der Funktionsname mit einem Unterstrich beginnen. Beispiele: # Normaler Funktionsname sub pow { ... } # Längerer Funktionsname sub myFunctionWithLogicalParts { ... } # Schlecht wäre sub my_function_with_logical_parts { ... } # Private Funktion; sie wird dadurch gekennzeichnet, # dass der Name mit einem Unterstrich beginnt sub _privateFunction { ... }
Funktionen
149
Funktionsnamen sind »Barewords«, d.h., sie sind nicht in Anführungszeichen eingeschlossen wie z.B. Strings und beginnen nicht mit einem Typkennzeichen wie Variablen. Wo immer der Interpreter im Sourcecode auf Barewords stößt, versucht er diese als Namen von Funktionen zu interpretieren. Im Allgemeinen dürfen nur Funktionsnamen Barewords sein, nicht aber Variablennamen. Allerdings gibt es auch Ausnahmen wie zum Beispiel Konstanten oder FileHandles in Großbuchstaben. Perl unterstützt (in einer etwas seltsamen Art und Weise) das Prototyping von Funktionen, bei dem bereits in der Funktionsdefinition die Anzahl und die Typen der Funktionsparameter angegeben werden können. Die Angabe von Anzahl und Typen der Parameter von Funktionen nennt man auch »formale Funktionsparameter«. Sie ist bei den meisten Programmiersprachen vorgeschrieben, in Perl hingegen ist sie ein gewöhnungsbedürftiger Luxus. Da dieser Mechanismus wie gesagt etwas seltsam (und auch nicht besonders hilfreich, da nicht konsequent) ist, sei hier nur auf die Online-Dokumentation verwiesen: D:\>perldoc perlsub
Die Variable @_ Im Funktionsrumpf wird vom Interpreter die vordefinierte Variable @_ zur Verfügung gestellt — das ist ein Array, in dem alle Aufrufparameter gespeichert sind. Verwechseln Sie bitte nicht @_ mit $_. Letzteres ist eine ebenfalls vordefinierte skalare Variable in Perl und wird für so ziemlich alles gebraucht (und von manchen Programmierern manchmal auch missbraucht). Diese Variable wird an späterer Stelle noch erläutert. Beispiel für den Gebrauch von @_: sub add { # Vom aufrufenden Programmcode werden die ersten # beiden Argumente in die lokalen Variablen # $var1 und $var2 kopiert. # Beachte: Links vom Gleichheitszeichen muss # eine Liste, gekennzeichnet durch runde # Klammern, stehen, da auch rechts vom # Gleichheitszeichen eine Liste steht. my ( $var1, $var2 ) = @_; # Mit dem "return"-Statement beendet sich die # Funktion und liefert dem Aufrufer die # Summe der beiden Argumente zurück. return $var1 + $var2; }
150
2
Grundlagen
Wenn Sie in der Funktion mit einer for- oder foreach-Schleife auf die einzelnen Funktionsparameter schreibend zugreifen und deren Werte ändern, dann verändern Sie die Originalwerte des aufrufenden Programmcodes! Beispiel: #!/usr/bin/perl -w # Hauptprogramm use strict; my $var = 1; myFunc( $var ); print( "var = '$var'\n" ); exit( 0 ); # Unterfunktion sub myFunc { foreach my $arg ( @_ ) { $arg .= "hallo"; } }
Die Ausgabe des Programms ist: var = '1hallo'
Wie wir schmerzlich sehen, hat unsere Unterfunktion den Wert der eigentlich nur im Hauptprogramm definierten Variable $var verändert. Dieses Verhalten ist meist nicht das, was man eigentlich wollte. Also: Vorsicht bei Schleifen über die Variable @_! In Perl wird eine Funktion sowohl ohne Funktionstyp als auch ohne formale Funktionsparameter deklariert. Unsere Beispielfunktion add() müsste in der Programmiersprache C z.B. wie folgt definiert werden: /* Deklaration der Funktion add mit Funktionstyp und formalen Parametern in C. Bei var1 sowie var2, muss ebenfalls der Datentyp angegeben sein. */ int add( int var1, int var2 ) { return var1 + var2; }
Sowohl der Rückgabewert der Funktion (Datentyp der Funktion) als auch Typ und Anzahl der Funktionsparameter müssen in C angegeben werden.
Funktionen
151
Da in Perl weder der Datentyp der Funktion noch die Anzahl und die Typen der Funktionsparameter aufgeführt sein müssen, kann eine Funktion je nach Programmlogik unterschiedliche Datentypen an den Aufrufer zurückgeben. Auch die Datentypen und die Anzahl der Funktionsargumente können sich von Aufruf zu Aufruf einer Funktion ändern. Der Vorteil dieser Regel ist, dass eine Funktion je nach Programmzustand unterschiedliche Datentypen entgegennehmen und auch zurückliefern kann (auch Arrays oder Hashes können an den Aufrufer der Funktion zurückgegeben werden). Der Nachteil: Manchmal ist es schwer festzustellen, wann eine Funktion welchen Datentyp erwartet bzw. zurückliefert. Beispiel: sub doIt { my ( $argCount, $arg1, $arg2, $arg3 ) = @_; if ( $argCount == 1 ) { return 1; } if ( $argCount == 2 ) { return "hello"; } my %hash = ( "key1" => 1, "key2" => 2 ); return %hash; }
Je nachdem, mit wie vielen Parametern die Funktion doIt() aufgerufen wird, gibt sie unterschiedliche Werte zurück: # Hauptprogramm # Der Rückgabewert von doIt() wird direkt an die Funktion # print() übergeben. print( doIt( 1 ), "\n" ); print( doIt( 2, "a" ), "\n" ); print( doIt( 3, "a", "b" ), "\n" );
Es wird Folgendes ausgegeben: 1 hallo key11key22
Die letzte Ausgabe sieht etwas seltsam aus. Das liegt daran, dass sowohl Keys als auch Values des Hash %hash der Funktion doIt() als Liste an die print()-Funktion übergeben werden, die alle Elemente der Liste ohne Zwischenraum ausgibt.
152
2
Grundlagen
Grundsätzlich kann man Funktionen innerhalb einer Datei an beliebigen Stellen definieren, jedoch dient es der Übersicht, wenn Funktionen nach dem Hauptprogramm aufgeführt werden. Funktionen dürfen aufgerufen werden, bevor sie definiert werden: #!D:/Perl/bin/perl.exe -w # Zuerst das Hauptprogramm ... f1(); f2(); exit( 0 ); # Dann die Funktionen sub f1 { ... } sub f2 { ... } ... 1;
Die letzte Zeile (1;) kann in Skripts auch entfallen, bei Perl-Modulen ist sie jedoch zwingend vorgeschrieben.
2.9.2 Funktionsaufruf Syntax: Funktionsname( ... ); &Funktionsname( ... );
Wie weiter oben bereits erwähnt, unterscheidet man den Funktionsaufruf von einer Funktionsdefinition dadurch, dass ein Funktionsaufruf immer durch ein Paar runder Klammern begleitet ist, das hinter dem Funktionsnamen angegeben ist. Bei alten bzw. schlampig geschriebenen Perl-Programmen findet man allerdings häufig eine etwas andere Version. Mehr hierzu weiter unten nach den »sauberen Beispielen«. Beispiele: # Funktionsaufruf im skalaren Kontext # (Der Rückgabewert der Funktion wird einer # skalaren Variable zugewiesen.) my $sum = add( 3, 5 ); # Oder auch so:
Funktionen
153
$sum = &add( 7, -10 ); # Man kann den skalaren Kontext auch mit scalar() # erzwingen: my $date = scalar( localtime() ); # Funktionsaufruf im List-Kontext # (Der Rückgabewert der Funktion wird einer # List Variable zugewiesen.) my @array = myFunc(); # Ebenfalls List-Kontext, weil die print()-Funktion # immer einen List-Kontext herstellt: print( myFunc() ); # Funktionsaufruf in Void-Kontext # Der Rückgabewert wird überhaupt nicht benutzt. myFunc(); # Aufruf einer anonymen Funktion über die # Referenzvariable &{ $refVar }( $arg1, $arg2 );
Wie man sieht, unterscheidet man einen Funktionsaufruf von der Deklaration einer Funktion zum einen dadurch, dass beim Aufruf einer Funktion das Bareword »sub« fehlt, zum anderen dadurch, dass dem Funktionsnamen ein Paar runder Klammern folgt. Optional kann man vor den Funktionsnamen ein Kaufmännisches Und »&« stellen. Diese Methode ist zum einen historisch bedingt (in der Version 4.x von Perl war dies die einzige Möglichkeit, Funktionen aufzurufen), zum anderen in bestimmten Situationen erforderlich, wenn anonyme Funktionen in Verbindung mit Referenzvariablen verwendet werden. Verzweifeln Sie nicht, wenn Sie zum Beispiel diesen Code sehen: print sort keys %h;
Bisher habe ich versucht zu verschweigen, dass Funktionen auch ohne runde Klammern aufgerufen werden können. Aus gutem Grund, denn diese Unart sollten Sie sich nicht angewöhnen, vielmehr schreiben Sie bitte den oben gezeigten Code so: print( sort( keys( %h ) ) );
Leider gibt es aber viel zu viele Programmierer, die aus Gründen der Faulheit oder weil sie möglichst kurzen Programmcode schreiben wollen, die Klammern weglassen. Wenn Sie also in Zukunft den flapsigen Code von irgendeinem Perl-Programm oder -Modul durchlesen, wissen Sie, dass »print sort keys« nicht Chinesisch ist, sondern dass es sich dabei um drei Funktionsaufrufe handelt.
154
2
Grundlagen
Wie weiter oben bereits erwähnt, können Funktionen auch anonym über Referenzvariablen definiert und aufgerufen werden. In diesem Fall muss die Referenzvariable definiert werden, bevor der Aufruf erfolgt. Beim Aufruf selbst muss das Kaufmännische Und »&« vor die Referenzvariable gesetzt werden, damit der Interpreter weiß, dass es ein Funktionsaufruf ist: # Definition einer Referenzvariable auf eine anonyme # Funktion my $funcRef = sub { print( @_ ); }; # Aufruf der Funktion über die Referenzvariable &{ $funcRef }( "hi", "there" );
Die Definition einer anonymen Funktion muss mit einem Semikolon abgeschlossen werden, da es sich dabei um eine Zuweisung an eine Variable handelt.
2.9.3 Datenübergabe an Funktionen Wenn beim Funktionsaufruf Daten vom aufrufenden Programm an die Funktion übergeben werden, gibt man diese als kommaseparierte Liste innerhalb der runden Klammern an. Die Funktionsargumente dürfen sowohl Konstanten oder normale skalare Variablen als auch Hashes, Arrays, Referenzvariablen oder sogar wiederum Funktionsaufrufe sein. In Perl ist sowohl die Anzahl der Argumente als auch der Typ der einzelnen Argumente völlig beliebig! Normalerweise werden die Funktionsparameter als Kopien der Originalwerte an die Funktion übergeben (»call-by-value«). Bei dieser Art der Datenübergabe kann vom Code der Funktion der Originalwert einer Variable des aufrufenden Codes nicht verändert werden. Es ist aber auch möglich, explizit Referenzen auf die Originalwerte zu übergeben (»call-by-reference«), indem man vor das Typkennzeichen eines Parameters einen Backslash »\« stellt. Das »call-by-value«-Prinzip stimmt nicht immer. Wird in for- oder foreach-Schleifen vom Code der Funktion auf die Parameterliste schreibend zugegriffen, dann verändert man damit die Originalwerte des aufrufenden Programmcodes! Beispiele für die »ungewollte« Ausnutzung von »call-by-reference«: D:\>perl -w use strict; sub f1 { for ( my $i = 0; $i <= $#_; $i++ ) { $_[ $i ] = 1; } }
Funktionen
155
my $v1 = 2; my $v2 = 3; f1( $v1, $v2 ); print( "v1 = $v1, v2 = $v2\n" ); ^Z v1 = 1, v2 = 1 D:\>perl -w use strict; sub f1 { foreach my $arg ( @_ ) { $arg = 1; } } my $v1 = 2; my $v2 = 3; f1( $v1, $v2 ); print( "v1 = $v1, v2 = $v2\n" ); ^Z v1 = 1, v2 = 1
Beispiele mit einem Argument: # Übergabe eines Arguments myFunc( "hello, world" ); # Dasselbe, jedoch wird das Argument aus mehreren # Teilen zusammengebaut myFunc( "hello," . " " . "world" ); # Die Funktion myFunc() holt sich das Argument aus @_ sub myFunc { # Die Funktion shift()entfernt das erste # Element einer Liste und gibt dieses zurück. # Innerhalb von Funktionen ist shift() so # eingestellt, dass automatisch die Variable "@_" # verwendet wird, wenn nichts angegeben ist. my $arg = shift(); # Oder "@_" wird explizit angegeben, # was ich für besser verständlich halte my $arg = shift( @_ ); # Bei beiden Varianten wird das Argument Array @_ # verkürzt und damit verändert.
156
2
Grundlagen
# Man kann auch eine Kopie des ersten Elements # lesen, ohne das Argument Array "@_" zu ändern. my ( $arg ) = @_; # Beachte: Es müssen runde Klammern um $arg gesetzt # werden, da die Zuweisung eine Listenkopie ist # (Zuweisung einer Liste). }
Hinweis: Die Funktion shift() wird auch in Hauptprogrammen verwendet, um aus den Parametern der Kommandozeile, die in der vordefinierten Variable @ARGV gespeichert sind, einzelne Argumente zu holen. Dabei ist Perl magisch. Wird die Funktion shift() in Hauptprogrammen ohne Argumente aufgerufen, dann entfernt sie das erste Element aus @ARGV , innerhalb von Funktionen aber wird das erste Element von @_ entfernt und zurückgegeben. Also: Man gibt besser @ARGV oder @_ explizit an, um den geneigten Leser seines Codes nicht verzweifeln zu lassen. (Wer weiß, vielleicht ist man selbst dieser Leser, die Uhr ist vielleicht um ein paar Monate vorgerückt, und schon kennt man sich in seinem eigenen Code nicht mehr aus.) Normalerweise werden Argumente an ein Skript in der Kommandozeile durch Blanks getrennt als Liste hinter dem Dateinamen des Skripts angegeben, z.B.: D:\temp>args.pl 1 2 3
Hier der Programmcode des Skripts args.pl: #!D:/Perl/bin/perl.exe -w use strict; while ( @ARGV ) { print( "arg = '", shift( @ARGV ), "'\n" ); } exit( 0 ); 1;
Im Beispiel habe ich dem Skript drei Argumente übergeben, es macht also folgende Ausgaben: 1 2 3
Wenn nun ein Argument ebenfalls Leerzeichen oder Tabs enthält, dann muss das gesamte Argument in Quotes gesetzt werden:
Funktionen
157
D:\temp>args.pl "1 mit Blank" 2 3 1 mit Blank 2 3 D:\temp>
Hätten wir keine Quotes um den String »1 mit Blanks« gesetzt, dann würde jedes Wort des Strings als separates Argument an das Skript übergeben: D:\temp>args.pl 1 mit Blank 2 3 1 mit Blank 2 3 D:\temp>
In UNIX kann man statt doppelter Quotes auch einfache Anführungszeichen verwenden: /home/user% args.pl '1 mit Blank' 2 3 1 mit Blank 2 3 /home/user %
Der Unterschied zwischen den beiden Varianten ist, dass die UNIX-Shell in Strings, die durch doppelte Quotes umrahmt sind, Variablen evaluiert (auch in der Shell hat z.B. das Dollarzeichen »$« eine besondere Bedeutung), während bei einfachen Quotes keine Evaluierung stattfindet. DOS verhält sich hier anders, für die DOS-Shell ist ein einfaches Quote in der Kommandozeile nur ein ganz normales Zeichen ohne Sonderbedeutung. Man kann es also nicht für Argumente mit Leerzeichen verwenden. Beispiel eines Funktionsaufrufs mit zwei Argumenten: myFunc( "hello, ", "world" ); # Die Funktion myFunc() holt sich die Argumente aus @_ sub myFunc { # Parameterübergabe mit Listenkopie # @_ wird dabei nicht verändert my ( $arg1, $arg2 ) = @_; # Es ginge auch so: my $arg1 = shift( @_ ); my $arg2 = shift( @_ );
158
2
Grundlagen
# @_ wird mit jedem Aufruf von shift() geändert # Oder so: my ( $arg1, $arg2 ) = ( shift( @_ ), shift( @_ ) ); # Auch hier wird @_ verändert }
Beispiel eines Funktionsaufrufs mit einem Hash als zweitem Argument: my %hash = ( "time" => time(), "ltime" => localtime() ); myFunc( "my hash", %hash ); # Die Funktion myFunc() holt sich die Argumente aus @_ sub myFunc { # Vorsicht mit Hashes als Parameter! # Siehe weiter unten my ( $arg1, %hash ) = @_; ... }
Beispiel für einen Funktionsaufruf, bei dem eine Referenz auf ein Hash übergeben wird, indem man vor das Typkennzeichen der Variable einen Backslash »\« stellt: myFunc( "my hash", \%hash ); # Die Funktion myFunc() holt sich die Argumente aus @_ sub myFunc { # Jetzt ist das zweite Argument eine skalare # Variable in Form einer Hash-Referenz my ( $arg1, $href ) = @_; }
Beispiel eines Funktionsaufrufs mit einem Array als zweitem Argument: my @array = localtime(); myFunc( "my array", @array ); # Die Funktion myFunc() holt sich die Argumente aus @_ sub myFunc { my ( $arg1, @array ) = @_; }
Beispiel für einen Funktionsaufruf, bei dem eine Referenz auf ein Array übergeben wird, indem man vor das Typkennzeichen der Variable einen Backslash stellt: my @array = localtime(); myFunc( "my array", \@array ); # Die Funktion myFunc() holt sich die Argumente aus @_ sub myFunc {
Funktionen
159
# Jetzt ist das zweite Argument eine skalare # Variable in Form einer Array-Referenz my ( $arg1, $aref ) = @_; }
Vorsicht geboten ist bei der Datenübergabe an Funktionen, wenn man Arrays oder Hashes übergibt. Im ersten Beispiel mit einem Array als Parameter wurde als erstes Argument ein Skalar, als zweites Argument ein Array übergeben. Würde man die Reihenfolge der Argumente umdrehen, so dass zuerst das Array und dann die skalare Variable steht, käme es zu unangenehmen Effekten: sub myFunc { my ( @array, $arg2 ) = @_; # Die Variable $arg2 ist immer undef, weil @array # sämtliche Argumente aufnimmt }
Die Variable $arg2 wird in dieser Konstellation nie versorgt, da bei der Kopie der rechten Seite aus dem Übergabe-Array @_ alle Elemente der Parameterliste an @array übergeben werden. Somit bleibt für $arg2 nichts mehr übrig. Dasselbe Problem hat man, wenn ein Hash beteiligt ist (das intern im Prinzip auch ein Array ist), jedoch würde man hier zusätzlich noch eine Fehlermeldung des Interpreters erhalten, wenn die Anzahl der Übergabeargumente ungerade ist: my %hash = ( "a" => 1, "b" => 2, ); sub myFunc { my ( %hash, $arg2 ) = @_; } myFunc( %hash, 1 );
Die Wirkung des Programmcodes ist eine hübsche Fehlermeldung etwa in der Art: Odd number of elements in hash assignment at - line 3.
Als Regel bei der Datenübergabe an Funktionen gilt: Hashes oder Arrays sollten an Funktionen entweder als letztes Argument oder als Referenz übergeben werden. Werden Daten als Referenz übergeben, dann können die Originalwerte des aufrufenden Programmcodes innerhalb der Funktion geändert werden. Man kann dieses Feature dazu benutzen, um die Anzahl der Rückgabewerte der Funktion zu verringern.
2.9.4 Datenübergabe an den Aufrufer einer Funktion Eine Funktion kann Daten an den aufrufenden Programmcode mit Hilfe des returnStatements zurückgeben. Da in Perl eine Funktion keinen festgelegten Datentyp hat, kann ein und dieselbe Funktion alle möglichen Datentypen zurückgeben, sogar Referenzen auf lokale Variablen. Außerdem ist die Anzahl der Rückgabedaten nicht begrenzt, es dürfen also mehrere Variablen an den Aufrufer zurückgegeben werden.
160
2
Grundlagen
Allerdings gilt hier dasselbe wie bei der Datenübergabe vom aufrufenden Programmcode an eine Funktion: Hashes und Arrays müssen entweder als letztes Argument oder als Referenz übergeben werden. Hinweis: Wenn eine Funktion nicht mit dem return-Statement endet, dann erhält der aufrufende Programmcode den Wert undef im skalaren Kontext, im List-Kontext bekommt er eine leere Liste zurück. Beispiele: sub myFunc { ... # Die Funktion beendet sich und gibt den numerischen # Wert "1" an den Aufrufer zurück. return 1; } sub myFunc { ... my $ret = undef; ... # Die Funktion gibt den Inhalt der skalaren Variable # "$ret" zurück. return $ret; } sub myFunc { ... my $success = 1; my @arr = ( ... ); ... # Die Funktion gibt zwei verschiedene Werte # zurück. # So ist es richtig: return ( $success, @arr ); # Und so ist es falsch: return ( @arr, $success ); } sub myFunc { ... # Die Funktion gibt den Rückgabewert eines # Funktionsaufrufs zurück. return localtime(); } sub myFunc { ...
Funktionen
161
my $result = ...; ... my %hash = ( "status" => 1, "retValue" => $result ); ... # Die Funktion gibt ein Hash zurück return %hash; # Oder als Referenz return \%hash; } sub myFunc { ... # # # #
Die Funktion gibt implizit nach dem Abarbeiten des letzten Statements vor der schließenden geschweiften Klammer je nach Programmkontext entweder "undef" oder eine leere Liste zurück.
}
Bevor man aus einer Funktion Referenzen von lokal in der Funktion definierten Variablen an den Aufrufer zurückgibt, sollte man prüfen, ob es nicht möglich ist, diese Daten über Referenzparameter beim Funktionsaufruf auszutauschen: # Definition eines Hash im Hauptprogramm my %hash = ( "a" => 1, "b" => 2, ); # Übergabe des Hashs als Referenz, d.h., die Funktion # kann das Hash verändern myFunc( \%hash ); # Nach dem Aufruf der Funktion besitzt das Hash nur noch # ein Element # "newKey" => "new Value" sub myFunc { # Lesen der skalaren Hash-Referenz # Über diese Referenzvariable ist es möglich, # das Original-Hash des aufrufenden Programmcodes # zu verändern. my ( $href ) = @_; # Leeren des Hashs möglich %{ $href } = (); # Hinzufügen eines Hash-Elements $href->{ "newKey" } = "new Value"; ... }
162
2
Grundlagen
2.9.5 Funktionskontext Wie wir bereits wissen, erfolgt ein Funktionsaufruf immer entweder in einem skalaren oder in einem List-Kontext (oder auch im Void-Kontext): # Skalarer Kontext my $var = myFunc(); # List-Kontext my @array = myFunc(); # Erzwingen von skalarem Kontext print( scalar( myFunc() ) );
Als Beispiel für eine intelligente Funktion, die sich je nach Kontext unterschiedlich verhält, lassen Sie uns die Funktion getDate() implementieren, die je nach Kontext unterschiedliche Dinge an den aufrufenden Programmcode zurückgibt. Wenn die Funktion im skalaren Kontext aufgerufen wird, soll sie das aktuelle Datum als String in der Form »YYYY-MM-DD HH:MI:SS« zurückgeben, zum Beispiel »2002-06-15 17:24:33«. Im List-Kontext wollen wir ein Array zurückgeben, das folgende Elemente hat: ( YYYY, MM, DD, HH, MI, SS )
zum Beispiel: »( 2002, 06, 15, 17, 24, 33 )« Als Erstes stellt sich natürlich die Frage: »Wie bekomme ich in einer Funktion heraus, in welchem Kontext sie aufgerufen wurde?«. Perl gibt mit der Funktion wantarray() die Antwort.
Die Funktion wantarray() Die Funktion wantarray() ist ganz einfach zu bedienen: Sie gibt den logischen Wert TRUE zurück, wenn man sich im List-Kontext befindet, andernfalls liefert sie den logischen Wert FALSE zurück. Beispiel: # Hauptprogramm print( myFunc(), "\n" ); print( scalar( myFunc() ), "\n" ); exit( 0 ); # Funktion myFunc
Funktionen
163
sub myFunc { return wantarray() ? "List-Kontext" : "skalarer Kontext"; }
Wenn wir den Code ausführen, erhalten wir die folgende Ausgabe: List-Kontext skalarer Kontext
Implementierung von getDate() Nun können wir die Funktion getDate() implementieren: sub getDate { # Aktuelle Zeit lesen my @date = localtime(); # Kontext mit "wantarray()" ermitteln if ( wantarray() ) { # Wir sind im List-Kontext. # Die für uns interessanten Elemente # aus @date lesen my @ret = @date[ 5, 4, 3, 2, 1, 0 ]; # Jahr und Monat anpassen # Siehe hierzu "perldoc -f localtime" $ret[ 0 ] += 1900; $ret[ 1 ]++; # Alle Elemente mit Ausnahme des Jahres # sollen zweistellig mit führenden # Nullen sein for ( my $i = 1; $i <= $#ret; $i++ ) { $ret[ $i ] = sprintf( "%02d", $ret[ $i ] ); } # Das angepasste Array zurückgeben return @ret; } # Wir sind im skalaren Kontext # Hinweis: Wir brauchen keinen "else"-Zweig, # da wir im Falle des List-Kontext die Funktion # am Ende mit dem return-Statement verlassen. # Wenn wir also hier gelandet sind, kann es # sich nur um skalaren Kontext handeln. return sprintf( "%d-%02d-%02d %02d:%02d:%02d", $date[ 5 ] + 1900, $date[ 4 ] + 1, $date[ 3 ], $date[ 2 ], $date[ 2 ], $date[ 0 ] ); }
164
2
Grundlagen
2.10 Module Nachdem wir zu Beginn des Buches bereits oberflächlich Bekanntschaft mit Modulen gemacht haben, wollen wir dieses Thema hier etwas vertiefen. Module sind Dateien mit der Datei Endung .pm, in denen Funktionen und Variablen definiert sind, die von anderen Perl-Skripts und Modulen importiert werden können und so die Funktionalität von Perl erweitern. Man sollte sich angewöhnen, den ersten Buchstaben des Dateinamens eines Moduls großzuschreiben. Für das Laden von Modulen verwendet der Perl-Interpreter die vordefinierte Variable @INC, die mit der Umgebungsvariable PERLLIB erweitert werden kann. Diese enthält alle Verzeichnisse, in denen der Interpreter nach Modulen sucht. Beispiele hierzu siehe in Kapitel 1. Im Gegensatz zu Perl-Skripts steht in Modulen kein Hauptprogramm, außerdem muss die letzte Programmzeile eines Perl-Moduls eine Anweisung enthalten, die den logischen Zustand TRUE zurückliefert. Dies erfolgt am einfachsten mit der Zeile. 1;
Perl-Module können sowohl prozeduralen als auch Objektorientierten Code enthalten. Module können auch hierarchisch aufgebaut sein, wenn man z.B. mehrere Module logisch in einer Gruppe zusammenfassen möchte. Ein Beispiel dafür ist das in der PerlDistribution mit ausgelieferte Package IO. Im Unterverzeichnis lib der Perl-Distribution ist ein gleichnamiges Unterverzeichnis IO, in dem mehrere Module in Dateien mit der Endung .pm stehen, z.B. Handle.pm. In dem Modul Handle.pm sind unter anderem Konstanten definiert, die man für die Funktion seek() benötigt. Der Package-Name des Moduls Handle.pm lautet IO::Handle, im Filesystem wird diese Modulhierarchie in Form von Unterverzeichnissen abgebildet. Als Trenner für Verzeichnisebenen und auch für den letzten Teil, der mit dem Dateinamen ohne die Endung .pm identisch ist, wird ein doppelter Doppelpunkt verwendet. Beispiele: # Die Datei IO/Handle.pm enthält den Namen des # Moduls, der mit der "package"-Direktive # gesetzt wird. package IO::Handle;
Module
165
# Im Skript oder in einem beliebigen Modul, das # das Modul IO/Handle benutzen möchte, muss # dieses erst geladen werden. Dies erfolgt mit # der "use"-Direktive. # Aus dem Verzeichnistrenner "/" wird beim # Namen des Packages ein doppelter Doppelpunkt. use IO::Handle; # Datei IO/Spec/UNIX.pm package IO::Spec::UNIX; # Geladen wird das Package mit der # "use"-Direktive: use IO::Spec::UNIX;
Dieser hierarchische Aufbau wird häufig auch in der Objektorientierten Programmierung verwendet, wenn man ausgehend von einer Basisklasse mehrere davon abgeleitete Unterklassen bildet und den Programmcode jeder Klasse in eine Datei mit der Endung .pm schreibt. Wir werden dieses Thema weiter unten noch behandeln.
2.10.1 Die package-Direktive Syntax: package name;
name ist in der Regel identisch mit dem Dateinamen ohne die Endung .pm. und gibt dem Modul einen Namen, den man für das Laden des Moduls mit use oder require benutzt. Der Perl-Interpreter verwendet Modulnamen, um Variablen und Funktionen einen eindeutigen Namespace zuzuordnen, vergleichbar mit der Wohnungsadresse, unter der wir unsere Post erhalten. So wie es Tausende von Meiers und Hubers gibt, ohne dass der Briefträger die Übersicht verliert, weil jeder Meier eine eindeutige Adresse hat, so hat auch jede Variable und jede Funktion in Perl einen eindeutigen Namespace, auch dann, wenn eine Variable mit demselben Namen in unterschiedlichen Modulen existiert. Im Prinzip ist jedes Perl-Skript ein Modul, auch das Hauptprogramm, obwohl man dies nicht erkennt, weil für Hauptprogramme der Modulname vordefiniert ist. Jedes Hauptprogramm hat den Packagenamen main. Die package-Direktive sollte am Anfang eines Perl-Moduls stehen. (Die Kommentarzeile für den Pfad des Perl-Interpreters (Hashbang-Zeile) entfällt in Modulen, sie ist nur in Hauptprogrammen notwendig.) Eine Datei kann auch mehrere Packages enthalten.
166
2
Grundlagen
Beispiel: #!/Perl/bin/perl.exe -w use strict; package util; # Ab hier gilt ein eigener Namespace für Variablen- und # Funktionsnamen our $var = 1; package main; # Ab hier steht der Code für das Hauptprogramm und seine # Funktionen our $var = 2; print( "Hauptprogramm-Variable = $var\n", "Variable aus dem Package util = $util::var\n" ); exit( 0 );
Wenn wir den Programmcode ausführen, dann wird Folgendes ausgegeben: Hauptprogramm-Variable = 2 Variable aus dem Package util = 1
2.10.2 Die require-Direktive Ein Perl-Skript, das Funktionalitäten benutzt, deren Code in anderen Dateien implementiert ist, muss die entsprechenden Module mit Hilfe der require-Direktive oder der use-Direktive importieren. Beide Direktiven haben weitere Bedeutungen, vor allem die use-Direktive ist eine eierlegende Wollmilchsau. Wir werden noch des Öfteren darauf zu sprechen kommen. Sowohl use als auch require sind eigentlich normale Perl-Funktionen, deshalb sind sie in der Online-Dokumentation auch unter dem Thema »perlfunc« zu finden. Zunächst wollen wir uns die require-Direktive näher ansehen: Syntax der require-Direktive: require Modulname; require Version;
Modulname ist der Dateiname des Moduls (ohne die Endung .pm), dessen Funktionen benutzt werden sollen. Diese Variante der Direktive wird benutzt, um das angegebene Modul zur Laufzeit des Skripts zu laden.
Module
167
Version ist eine spezielle Konstante, mit der man die Mindestversion von Perl angeben kann, die vom Skript oder Modul verlangt wird. Folgende Formen sind erlaubt: require v5.6.1; require 5.6.1; require 5.6.1_03;
Ist im Code eine der gezeigten Direktiven enthalten, dann weigert sich der Perl-Interpreter, das Skript oder das Modul zu laden, wenn die Version der Perl-Distribution kleiner als die verlangte ist. Mit der require-Direktive wird der Namespace des Programms nicht verändert, d.h., Variablen und Funktionen des importierten Moduls sind ohne absolute NamespaceAngabe nicht verwendbar. Die Benutzung von Package-Variablen und -Funktionen wird bei Verwendung der require-Direktive von Perl zur Laufzeit des Skripts durchgeführt, nicht zur Übersetzungszeit. Das bedeutet, dass der Interpreter bei Aufruf von nicht vorhandenen Package-Funktionen (oder wenn das angegebene Modul gar nicht existiert) erst bei der Ausführung des Skripts, nicht aber bei der Syntaxprüfung, eine Fehlermeldung ausgibt. Siehe hierzu auch die use-Direktive weiter unten. Wollen wir uns ein Beispiel ansehen: Zunächst implementieren wir als Beispiel das Package MyUtil, dessen Code wir in der Datei MyUtil.pm im aktuellen Verzeichnis der DOS-Box oder der Shell (unter UNIX) abspeichern: # Datei MyUtil.pm package MyUtil; use strict; # Hier definieren wir eine Variable für die Version # unseres Packages our $VERSION = "1.0"; # Und nun noch eine einfache Funktion: sub foo { return "bla"; } # Obligatorisch bei Modulen: 1;
168
2
Grundlagen
So, das wäre geschafft. Nun schreiben wir ein kleines Skript, das unser Modul benutzt (nennen wir es tst.pl): #!D:/Perl/bin/perl.exe -w # Interpreter auf scharfes Überwachen unseres Codes # einstellen # Die Direktive "use strict;" sollte in keinem # Perl-Skript fehlen! use strict; # Nun müssen wir angeben, dass wir das Modul "MyUtil" # verwenden wollen: require MyUtil; # Nun wollen wir die Variable "$VERSION" aus dem # Modul benutzen: print( "Version des Moduls = '$MyUtil::VERSION'\n" ); # Und hier rufen wir die Funktion "foo" des Moduls auf: print( MyUtil::foo(), "\n" ); exit( 0 ); 1;
Das Skript gibt, wie erwartet, die Version aus, anschließend wird der String »bla« durch den Aufruf der Funktion foo() des Moduls ausgegeben. Für manchen ist die Angabe des absoluten Namespaces von Variablen und Funktionen anderer Module lästig. Viel einfacher wäre es doch, wenn man statt $MyUtil::VERSION einfach nur $VERSION schreiben könnte. Ich möchte hier jedoch eindeutig für die »lästige« Version des Namespaces plädieren, weil man damit sofort erkennt, wo die benutzte Variable definiert worden ist. Außerdem gehen die Leute immer mehr dazu über, Objektorientierte Programmierung zu verwenden, und dort hat man dieses Problem sowieso nicht mehr, wie wir weiter unten noch sehen werden. Aber trotzdem möchte ich Ihnen im nächsten Abschnitt unser Beispiel mit der Kurzfassung zeigen.
2.10.3 Die use-Direktive Die use-Direktive wird unter anderem wie require dazu benutzt, Perl-Module zu laden. Im Gegensatz zur require-Direktive erfolgt dies jedoch zur Übersetzungszeit, nicht zur Laufzeit. Dies bedeutet zum Beispiel, dass Sie bereits beim Eingeben des Programmcodes eine Fehlermeldung vom Interpreter erhalten (der dann auch gnadenlos mit
Module
169
dem Interpretieren aufhört), wenn etwas nicht stimmt, zum Beispiel, weil Sie ein Modul laden wollen, das entweder nicht existiert oder vom Interpreter nicht gefunden werden kann. Wie bei require können Sie auch eine Mindestversion von Perl verlangen, siehe hierzu unter require. Auch beim Laden von Modulen können Sie eine Mindestversion für das Modul angeben. Syntax der use-Direktive: use use use use
Modulename; Modulename ( list ); Modulename Version; Modulename Version ( list );
use Version; use strict; use vars (...); use constant ...;
Es gibt noch weitere Möglichkeiten, die use-Direktive zu benutzen, ich habe mir die wichtigsten herausgepickt. Wenn Sie schon Erfahrung haben, können Sie Detailinformationen in der Online-Dokumentation unter dem Thema »perlfunc« nachlesen. Die Variante use strict;
möchte ich sofort erläutern. Zum einen gibt es Varianten dieser Direktive, die ich Ihnen verschweigen will (natürlich können Sie diese in der Online-Dokumentation nachlesen), damit Sie möglichst nur diese Variante benutzen. Damit veranlassen Sie den Interpreter, aufs Schärfste die Einhaltung von sauberem Code zu überwachen. Die use strict;-Direktive sollte in keinem Programm oder Modul fehlen. Modulname ist der Dateiname des Moduls (ohne die Endung .pm), dessen Funktionen benutzt werden sollen. Der Namespace des Programms wird insofern geändert, als dass alle exportierten Funktionen, Konstanten und Variablen des Moduls ohne absolute Namespace-Angabe verwendbar sind, als wären sie im Perl-Skript, welches das Modul importiert, selbst definiert worden.
170
2
Grundlagen
list kann eine Liste von Identifiern (Namen von Variablen und Funktionen des importierten Moduls) enthalten, die man explizit in seinen Namespace aufnehmen möchte. Wenn man eine leere Liste angibt, verhält sich die use-Direktive genau wie die requireDirektive, d.h., es wird kein Variablenname und kein Funktionsname des importierten Moduls in den aktuellen Namespace aufgenommen. Im Gegensatz zur require-Direktive prüft der Perl-Interpreter jedoch sofort (zur Übersetzungszeit), ob das angegebene Modul existiert. Beispiel für list: use MyUtil qw( $VERSION foo );
In der Regel importiert man Module entweder mit der require-Direktive oder mit der Angabe einer leeren Liste für die use-Direktive (diese Methode empfehle ich), da sich damit der aktuelle Namespace nicht verändert (dies könnte zu Problemen führen, wenn im aktuellen Code Variablen oder Funktionen deklariert sind, die den gleichen Namen haben wie im importierten Modul). Will man explizit Variablen oder Funktionen aus dem importierten Modul verwenden, kann man immer den absoluten Namespace angeben. Beispiel für den Import des Namespace mit der use-Direktive: Wir wollen das bereits weiter oben implementierte Modul MyUtil.pm verwenden und es so erweitern, dass wir von außen auf Variablen und Funktionen ohne Angabe des absoluten Namespaces zugreifen können: package MyUtil; use strict; # Damit wir unseren Namespace exportieren können, # müssen wir das Package "Exporter" laden. use Exporter; # Hier ein paar neue Variablen # @ISA werden wir bei der Objektorientierten # Programmierung noch kennen lernen # # # # # # # # # #
@EXPORT nimmt alle Identifier (Namen von Variablen und Funktionen), die automatisch exportiert werden sollen, wenn unser Package über die "use"-Direktive ohne Angabe einer Liste geladen wird. Bei dieser Variable ist Vorsicht geboten, sie sollte so wenig Identifier wie möglich enthalten, da man damit den Namespace des aufrufenden Programmcodes durcheinanderbringen kann, wenn ein Identifier sowohl im Modul als auch im Programmcode definiert wird, der
Module # # # #
das Modul mit der "use"-Direktive lädt. Identifier mit kurzen oder gebräuchlichen Namen sollte man daher besser in die Variable "@EXPORT_OK" packen.
# @EXPORT_OK enthält eine Liste für Identifier, die # nur dann exportiert werden, wenn sie bei der # "use"-Direktive in der Liste angegeben werden. # # # # # # # # # # #
%EXPORT_TAGS wird für das Zusammenfassen mehrerer Identifier zu einer Gruppe benutzt, die gesammelt exportiert werden können, indem man den Gruppennamen angibt. Die Variable enthält als Keys die Gruppennamen, die Values müssen Arrayreferenzen auf die zu exportierenden Identifier sein. Alle Identifier, die in %EXPORT_TAGS benutzt werden, müssen entweder in @EXPORT oder @EXPORT_OK aufgeführt sein.
# # # # #
Bei Objektorientierter Programmierung dürfen die Namen von Objektmethoden nicht exportiert werden! Wir werden später in einem eigenen Kapitel sehen, warum.
# Sowohl @EXPORT als auch @EXPORT_OK werden vom # Package "Exporter" verwendet. # Mehr Infos gibt die Online-Dokumentation unter # dem Thema "Exporter" (perldoc Exporter) our ( @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS ); # Mit dem folgenden Statement sagen wir dem # Interpreter, er soll Funktionsnamen auch # im Package "Exporter" suchen. Für Wissbegierige: # "perldoc AutoLoader" @ISA = qw( Exporter ); # Wir wollen die Variable "$VERSION" und die # Funktion "foo()" automatisch exportieren. @EXPORT = qw( $VERSION foo ); # Die Variable "$myVar" soll nicht automatisch # exportiert werden, sondern nur dann, wenn sie # in der Liste der "use"-Direktive angegeben ist. @EXPORT_OK = qw( $myVar ); # Hier definieren wir eine Variable für die Version # unseres Packages. our $VERSION = "1.0";
171
172
2
# Zusammenfassen mehrerer Identifier zu einer Gruppe: %EXPORT_TAGS = ( "vars1" => [ qw( $VERSION $myVar ) ], ); # # # # # # #
Mit dieser Definition von %EXPORT_TAGS können die beiden angegebenen Variablen einfach mit use MyModule qw( :vars1 ); in den Namespace aufgenommen werden. Beachte: Der Doppelpunkt wird zwar in der "use"-Direktive angegeben, der Name des Hash-Keys enthält jedoch keinen Doppelpunkt.
# Und nun noch eine einfache Funktion: sub foo { return "bla"; } # Obligatorisch bei Modulen: 1;
Und hier das Skript, mit dem wir das Package MyUtil verwenden: #!D:/Perl/bin/perl.exe -w # Interpreter auf scharfes Überwachen unseres Codes # einstellen # Die Direktive "use strict;" sollte in keinem # Perl-Skript fehlen! use strict; # Nun müssen wir angeben, dass wir das Modul "MyUtil" # verwenden wollen: use MyUtil qw( $myVar $VERSION foo ); # Hinweis: Hätten wir nur "use MyUtil;" # geschrieben, dann wären nur "$VERSION" und # die Funktion "foo()" exportiert worden. # Nun wollen wir die Variable "$VERSION" aus dem # Modul benutzen: print( "Version des Moduls = '$VERSION'\n" ); print( "myVar = '$myVar'\n" ); # Und hier rufen wir die Funktion "foo" des Moduls auf: print( foo() ); exit( 0 );
Grundlagen
Module
173
Wie wir sehen, entfällt nun das lästige Schreiben von doppelten Doppelpunkten. Wie schön, denkt man. Aber was tun, wenn man fünf verschiedene Module lädt und in allen fünf Modulen Variablen mit dem Namen $VERSION oder Funktionen namens foo() definiert sind? Nun, bei mehrfachem Definieren von Variablen merkt man überhaupt nicht, dass etwas nicht stimmt. Jede use-Direktive, bei der eine bereits importierte Variable noch einmal importiert wird, überschreibt die vorhergehende, ohne dass der Interpreter eine Fehlermeldung liefert. Dasselbe tritt übrigens ein, wenn wir in unserem Skript ebenfalls eine Variable namens $VERSION oder $myVar definiert haben. Je nachdem, ob diese Definition vor oder nach der betreffenden use-Direktive kommt, enthalten die doppelt vorhandenen Variablen den Wert aus unserem Skript oder dem importierten Modul. Bei mehrfach importierten Funktionen gleichen Namens beschwert sich der Interpreter wenigstens. Die Fehlermeldung sieht in etwa so aus: Subroutine foo redefined at D:/Perl/lib/Exporter/Heavy.pm line 165 Exporter::heavy_export('MyUtil1', 'main', '$myVar', '$VERSION', 'foo') called at D:/Perl/lib /Exporter.pm line 45 Exporter::import('MyUtil1', '$myVar', '$VERSION', 'foo') called at tst.pl line 17 main::BEGIN() called at D:/Perl/lib/Carp.pm line 17 eval {...} called at D:/Perl/lib/Carp.pm line 17
Was lernen wir daraus? Importieren Sie Module möglichst mit einer Liste von Identifieren, die entweder leer ist oder nur diejenigen Identifier enthält, die Sie in Ihren aktuellen Namespace aufnehmen wollen: # Gut use MyModule (); # Auch gut use MyModule qw( $VERSION ); # Schlecht use MyModule;
Noch ein kurzes Beispiel für den Unterschied zwischen require und use. Wir versuchen, ein nicht vorhandenes Modul zu importieren. Dazu rufen wir den Perl-Interpreter z.B. in einer DOS-Box über die Kommandozeile auf und geben die Statements Zeile für Zeile ein. Nach dem letzten Statement geben wir das Steuerzeichen ^Z (Windows) und ein Zeilenende-Zeichen bzw. ^D (UNIX) ein. Erst danach führt der Interpreter die eingegebenen Kommandos aus (Laufzeit).
174
2
Grundlagen
Zunächst folgt der Code mit der require-Direktive: D:\>perl -w use strict; # Wir importieren ein nicht vorhandenes Modul require bla; ^Z Can't locate bla.pm in @INC (@INC contains: D:/Perl/lib D:/Perl/site/lib .) at line 1.
Und nun dasselbe mit der use-Direktive: D:\>perl -w use strict; # Wir importieren ein nicht vorhandenes Modul use bla; Can't locate bla.pm in @INC (@INC contains: D:/Perl/lib D:/Perl/site/lib .) at line 1. BEGIN failed--compilation aborted at - line 1.
Haben Sie es bemerkt? Mit der use-Direktive kommen wir gar nicht mehr dazu, die Eingaben mit ^Z zu beenden, weil der Interpreter bereits nach dem Einlesen der useDirektive das angegebene Modul zu laden versucht, was natürlich schief geht, da es ein solches Modul nicht gibt.
2.11 Ein-/Ausgabe (File I/O) Häufig wird die Eingabe von Daten aus Dateien statt von der Tastatur benötigt, ebenso wie die Ausgabe von Daten ins Filesystem statt auf die Standardausgabe (Bildschirm bzw. Konsole oder Terminal). Alle filebasierten Eingaben und Ausgaben erfolgen über so genannte »FileHandles« (bei Dateien) oder »DirHandles« (bei Verzeichnissen), die wir gleich näher betrachten werden. Im Folgenden wollen wir uns grundlegende Dinge wie Öffnen von Dateien, Lesen und Schreiben von Dateien sowie Positionieren von Dateizeigern und Dateisperren erarbeiten. Für hart gesottene Windows-Benutzer möchte ich nicht die Gelegenheit verpassen, noch einmal eindringlich darauf hinzuweisen, dass Dateipfade in Perl grundsätzlich mit einem Slash »/« als Verzeichnistrenner verwendet werden sollten, da sie im Gegensatz zu Backslashes, die in Windows eingesetzt werden, portabel sind, weil der PerlInterpreter automatisch die je nach Betriebssystem erforderlichen Trennzeichen einsetzt.
Ein-/Ausgabe (File I/O)
175
Zu beachten ist außerdem, dass Pfadnamen in UNIX case-sensitive sind, während unter Windows keine Unterscheidung zwischen Groß- und Kleinbuchstaben gemacht wird. Ein weiterer Unterschied zwischen UNIX und Windows ist die Tatsache, dass unter Windows Textdateien anders behandelt werden als Binärdateien, was sich auch in Programmen niederschlägt. So werden bei Textdateien unter Windows am Ende einer Zeile 2 Steuerzeichen für das Zeilenende verwendet (»\r\n«), während unter UNIX nur das Steuerzeichen Zeilenvorschub (»\n«) benutzt wird. Für den Programmierer ist dies nicht transparent, wenn er eine Zeile mit folgendem Code schreibt: print( "Das ist eine Zeile\n" );
Man würde erwarten, dass tatsächlich nur ein Steuerzeichen ausgegeben wird, unter Windows jedoch gibt die print()-Funktion in Wahrheit 2 Steuerzeichen aus (vor dem Zeilenvorschub-Zeichen wird das Wagenrücklauf-Zeichen ausgegeben). Will man diesen Effekt vermeiden, dann muss die zugehörige Datei vor einer I/O-Operation in den Binärmodus umgeschaltet werden (siehe hierzu auch die Funktion binmode()). Alle Ausgaben werden üblicherweise vom Betriebssystem gepuffert. Das bedeutet, dass die Ausgabe von wenigen Bytes nicht sofort erfolgt, sondern die Daten zunächst in den Pufferspeicher des Betriebssystems geschrieben werden. Die tatsächliche Ausgabe führt es erst dann durch, wenn entweder der Puffer voll ist oder die Systemressource für das Ausgabemedium freigegeben wird (weil sich zum Beispiel das Programm beendet und dadurch das FileHandle geschlossen wird). Wir werden noch sehen, wie man die Pufferung von Ausgabemedien verhindert.
2.11.1 FileHandles Um auf eine Datei zugreifen zu können, benötigt man ein so genanntes »FileHandle«. Das ist ein Objekt für eine Systemressource, das vom Betriebssystem für Dateien zur Verfügung gestellt werden. Eine Datei kann im weiteren Sinn auch eine Gerätedatei, eine Pipe oder auch ein »symbolic link« sein. Jedes Perl-Skript hat drei vordefinierte FileHandles: STDIN für Standardeingabe sowie STDOUT für Standardausgabe und STDERR für Fehleraus-
gabe. Um Eingaben von STDIN zu lesen oder Ausgaben auf STDOUT bzw. STDERR zu machen, muss deshalb kein neues FileHandle vom Skript erzeugt werden. Einige Funktionen bzw. Operatoren wie z.B. die print()-Funktion oder der <>-Operator verwenden STDIN bzw. STDOUT als Default-FileHandle, wenn kein explizites FileHandle angegeben ist.
176
2
Grundlagen
Beispiele für Default-FileHandles: print( "hallo\n" ); # ist dasselbe wie print( STDOUT "hallo\n" ); # Beachte: Nach STDOUT darf kein Komma stehen! # Eines der ungereimten Dinge... my $line = <>; # ist dasselbe wie my $line = <STDIN>;
Es wird allerdings wärmstens empfohlen, speziell beim Eingabe-Operator <> immer ein FileHandle anzugeben.
Öffnen von Dateien Perl bietet zwar die Funktion open() an, um neue Dateien anzulegen bzw. bestehende Dateien zu lesen oder zu schreiben, ich möchte Ihnen allerdings empfehlen, das PerlModul »FileHandle« zu verwenden, da dieses Package Objektorientiert ist und vor allen Dingen die Deklaration von FileHandle-Variablen mit dem Bareword my (oder auch our) ermöglicht. Damit kann man den Geltungsbereich der Variablen einschränken, was im übrigen immer zu empfehlen ist. Wenn man open() verwendet, dann sind die so gewonnenen Identifier der FileHandles global im gesamten Perl-Skript definiert, was zu unerwünschten Nebeneffekten führen kann. Beispiel für open() ohne die Benutzung des Packages FileHandle: # Öffen der Datei "bla.txt" zum Lesen open( FP, "bla.txt" ); # # # # # #
Das FileHandle wird in einer seltsamen Variable "FP" abgelegt, die kein Typkennzeichen trägt und nur aus Großbuchstaben bestehen darf. Der Name wurde von mir gewählt, ich hätte auch open( MYFILEHANDLE, "bla.txt" ); schreiben können.
# Das Dumme daran ist, dass diese speziellen FileHandles # im gesamten Programmcode einschließlich geladener # Module bekannt sind und damit eindeutig sein müssen!
Das Beispiel hat gezeigt, dass es durchaus Sinn hat, das Objektorientierte Package FileHandle zu benutzen. Ein FileHandle erhält man über den Konstruktor des PerlModuls FileHandle. Wer jetzt über den Begriff »Konstruktor« stolpert, sei auf das Kapi-
Ein-/Ausgabe (File I/O)
177
tel über Objektorientierte Programmierung verwiesen. Auf die Schnelle: Ein Konstruktor liefert ein Objekt zurück, über das man Methoden (das sind spezielle Funktionen) aufrufen kann. Dateien können auf verschiedene Arten geöffnet werden, die ich kurz erklären möchte: 왘 Readonly 왘 Writeonly 왘 ReadWrite 왘 Append Readonly Wenn eine Datei in diesem Modus geöffnet wird, muss sie vorher bereits existieren, ansonsten gibt es eine Fehlermeldung. Es kann nur aus der Datei gelesen, der Inhalt aber nicht geändert werden. Die erste Leseoperation beginnt ab dem ersten Byte der Datei (Offset=0). Writeonly Dieser Modus erlaubt nur schreibende Operationen, es kann also nichts gelesen werden. Falls die Datei vor dem Öffnen noch nicht existiert hat, wird sie neu angelegt. Der erste Schreibzugriff beginnt ab dem ersten Byte der Datei (Offset=0). ReadWrite Dieser Modus bedeutet, dass sowohl lesende als auch schreibende Aktionen möglich sind. Im Standardfall wird vorausgesetzt, dass die Datei vor dem Öffnen bereits existiert, mit dem Parameter O_CREAT kann allerdings erzwungen werden, dass die Datei in jedem Fall angelegt wird, wenn sie vorher noch nicht existiert hat. Die erste I/O-Operation beginnt ab dem ersten Byte der Datei (Offset=0). Append Der Append Modus ist dafür gedacht, Daten an das Ende der Datei anzuhängen. Falls die Datei vor dem Öffnen noch nicht existiert hat, wird sie neu angelegt. Der erste Schreibzugriff beginnt am Ende der Datei. Einem FileHandle ist ein Zeiger zugeordnet (Filepointer), der auf die Position innerhalb der Datei zeigt, ab der sich die nächste I/O-Operation auswirkt. Die Position des Filepointers ist als Offset in Bytes zu sehen, der entweder vom Beginn der Datei, vom Ende der Datei (in diesem Fall ist der Offset negativ) oder von der aktuellen Position aus berechnet wird. Man kann den Dateizeiger über Methoden des FileHandles sowohl abfragen als auch neu setzen.
178
2
Grundlagen
Syntaxübersicht für das Öffnen von Dateien mit dem Package FileHandle (path steht für einen String, der den Pfadnamen einer Datei enthält): use FileHandle (); # Leere Objektinstanz anlegen new FileHandle(); # Datei zum Lesen öffnen (sie muss vorher bereits # existieren) new FileHandle( path, "r" ) new FileHandle( "< path" ) # Datei zum Lesen und Schreiben gleichzeitig öffnen # (sie muss vorher bereits existieren) new FileHandle( path, "r+" ) new FileHandle( "+< path" ) # Datei zum Schreiben öffnen. Wenn sie vorher bereits # existiert, dann wird ihr Inhalt gelöscht, man erhält ein # FileHandle auf eine leere Datei. Ist sie noch nicht # existent, wird eine neue Datei angelegt. new FileHandle( path, "w" ) new FileHandle( "> path" ) # Datei zum Schreiben öffnen. Der Inhalt einer # bereits evtl. existierenden Datei wird allerdings # nicht gelöscht, Schreiboperationen fügen die # Daten am Ende der Datei an. Existiert die Datei # vorher noch nicht, dann wird sie neu angelegt. new FileHandle( path, "a" ) new FileHandle( path, "a+" ) new FileHandle( ">> path" ) oder: # Die "use"-Direktive wird nun ohne leere Liste # verwendet, d.h., alle exportierten Identifier # werden in den aktuellen Namespace übernommen. # Wir benötigen sie, um die Konstanten angegeben # zu können. use FileHandle; # Nur zum Lesen öffnen new FileHandle( path, O_RDONLY ) # Nur zum Schreiben öffnen new FileHandle( path, O_WRONLY ) # Nur zum Schreiben (Anhängen ans Ende) öffnen new FileHandle( path, O_APPEND )
Ein-/Ausgabe (File I/O)
179
# Zum Lesen und Schreiben öffnen new FileHandle( path, O_RDWR ) # Alle Möglichkeiten noch einmal, diesmal wird # aber die Datei neu angelegt, falls sie vorher # noch nicht existiert. # Funktioniert auch für nur lesendes Öffnen! new FileHandle( path, O_WRONLY | O_CREAT ) new FileHandle( path, O_APPEND | O_CREAT ) new FileHandle( path, O_RDWR | O_CREAT )
Hinweis zu den Konstanten O_RDONLY etc.: Diese Konstanten sind im Perl-Modul Fcntl.pm definiert und werden per Default exportiert, wenn man die Direktive use FileHandle; ohne Liste verwendet. Man kann sie auch gezielt in der Liste der zu importierenden Identifier angeben: use FileHandle qw( O_RDONLY O_WRONLY O_RDWR O_APPEND O_CREAT );
Die Werte der Konstanten sind binäre Flags, die durch eine ODER-Verknüpfung kombiniert werden können. So besagt die Kombination O_RDWR | O_CREAT # Achtung O_RDWR || O_CREAT # ist falsch, da "||" eine logische Verknüpfung ist, # wir aber eine binäre Verknüpfung benötigen.
zum Beispiel, dass die Datei für schreibenden und lesenden Zugriff geöffnet werden soll. Außerdem soll die Datei neu angelegt werden, falls sie noch nicht existiert. Beachte: Die ODER-Verknüpfung muss mit dem Bit-Operator »|« erfolgen, nicht mit dem logischen Operator »||«. Weiterführende Informationen gibt es mit dem Kommando perldoc Fcntl
und natürlich über die grafische Dokumentationsoberfläche der Perl-Distribution. Die Packages FileHandle und DirHandle bilden nur Wrapper für die internen PerlFunktionen wie zum Beispiel open() ab und sind nicht als Ersatz der internen Funktionen gedacht. Sie bieten dem Programmierer aber ein freundliches API (Application Programming Interface) an.
180
2
Grundlagen
Der Konstruktor new() des Perl-Moduls FileHandle liefert entweder eine Objektreferenz auf ein gültiges FileHandle zurück oder undef, wenn ein Fehler aufgetreten ist. In letzterem Fall enthält die vordefinierte Variable $! die Fehlermeldung bzw. den Fehlercode. Alle wichtigen vordefinierten Variablen sind übrigens im Anhang beschrieben. Beispiele für das Öffnen einer bereits existierenden Datei: use FileHandle; # Öffnen für ausschließlichen Lesezugriff # (Schreibzugriffe führen zu einem Fehler) # Leeres FileHandle-Objekt instanzieren my $fh = new FileHandle(); # Datei 'bla.txt' zum Lesen öffnen $fh->open( "< bla.txt" ); # Es geht auch nur mit dem Konstruktor my $fh = new FileHandle( "bla.txt", "r" ); # oder my $fh = new FileHandle( "
Beispiele für das Öffnen von Dateien, die nicht unbedingt existieren müssen, da sie automatisch angelegt werden, falls sie noch nicht vorhanden sind: use FileHandle; # Öffnen für Nur-Schreibzugriff. Falls die Datei # bereits existiert, wird ihr bisheriger Inhalt
Ein-/Ausgabe (File I/O)
181
# gelöscht! my $fh = new FileHandle( "bla.txt", "w" ); # Das Gleiche, jedoch werden Ausgaben in die Datei # am Ende angehängt, der bisherige Inhalt wird nicht # gelöscht my $fh = new FileHandle( "bla.txt", "a" ); # Das Gleiche mit importierten Konstanten use FileHandle; my $fh = new FileHandle( "bla.txt", O_WRONLY | O_CREAT ); my $fh = new FileHandle( "bla.txt", O_APPEND | O_CREAT ); my $fh = new FileHandle( "bla.txt", O_RDWR | O_CREAT );
Binärmodus Wenn man verhindern will, dass beim Schreiben des Steuerzeichens »\n« unter Windows zwei Steuerzeichen geschrieben werden (»\r\n«), dann muss man nach dem Öffnen der Datei vor der ersten I/O-Operation mit Hilfe der Funktion binmode() die Datei in den Binärmodus umstellen: use FileHandle; # Öffnen der Datei "bla.txt" als WriteOnly mit Append my $fh = new FileHandle( "bla.txt", O_WRONLY | O_APPEND ); # Prüfen, ob die Operation erfolgreich war unless ( $fh ) { print( "Fehler beim Öffnen der Datei\n" ); exit( 1 ); } # Umschalten des FileHandles in den Binärmodus binmode( $fh ); print( $fh "hallo\n" ); # oder mit der Objektmethode print() $fh->print( "hallo\n" );
182
2
Grundlagen
Es wird wirklich der String »hallo\n« in die Datei geschrieben, egal, welches Betriebssystem benutzt wird. Hätte man die Funktion »binmode()« nicht aufgerufen, dann würde unter Windows der String »hallo\r\n« in der Datei stehen. Bei der Verwendung der Objektmethode print(): $fh->print( "hallo\n" );
wird der Dereferenzierungsoperator -> verwendet, den wir schon bei Referenzvariablen kennen gelernt haben. In Verbindung mit Objekten dient er für Methodenaufrufe einer Objektinstanz. Zu diesem Thema werden wir im Kapitel »Objektorientierte Programmierung« noch ausführlich kommen.
Schließen von Dateien Geöffnete FileHandles verbrauchen Systemressourcen, deshalb müssen Dateien geschlossen und die dafür verwendeten Ressourcen wieder freigegeben werden, wenn man sie nicht mehr benötigt. Das ist vor allem bei größeren Applikationen eminent wichtig, weil FileHandles meist sehr schnell rar werden. Hier ein kleines Beispielprogramm, mit dem Sie testen können, wie viele FileHandles gleichzeitig geöffnet werden können: #!D:/Perl/bin/perl.exe -w use strict; use FileHandle; # Hash, in dem die automatisch erzeugten Pfadnamen der # geöffneten Dateien als Keys und die FileHandle-Objekte # als Values abgelegt werden my %fhs = (); while ( 1 ) { # Automatischer Zähler für die Erzeugung # der Dateinamen my $i = scalar( keys( %fhs ) ); # Generierte Dateinamen # Die erste Datei heißt "file_0.txt" my $path = "file_$i.txt"; # Jeweils nach 10 Dateien macht das Skript eine # Kontrollausgabe. Damit sieht man, wie viele # Dateien angelegt wurden. unless ( $i % 10 ) { print( "Dateizähler = $i\n" ); }
Ein-/Ausgabe (File I/O)
183
# Datei zum Schreiben öffnen. # Falls dies nicht möglich ist, wird die Schleife # abgebrochen. my $fh = new FileHandle( $path, "w" ); unless ( $fh ) { print( STDERR "Fehler $!\n" ); last; } # Das Anlegen der Datei war erfolgreich. Wir # speichern den Dateinamen als Key, das # FileHandle-Objekt als Value ab. $fhs{ $path } = $fh; } # Programmende # Die Nachbereitung wird im END-Block durchgeführt. exit( 0 ); # END-Block des Skripts. Dieser wird immer bei # Programmende ausgeführt. Wir müssen alle geöffneten # Dateien wieder schließen und die Dateien # löschen. END { foreach my $path ( keys( %fhs ) ) { # Extrahieren des FileHandles my $fh = $fhs{ $path }; # Datei schließen $fh->close(); # Datei löschen unlink( $path ); } }
FileHandles, die mit dem Perl-Modul FileHandle erzeugt wurden, lassen sich einfach auf folgende Arten wieder schließen: $fh->close(); # oder undef( $fh );
Schreiben in Dateien Zum Schreiben in geöffnete Dateien kann man entweder die print()-Funktion von Perl verwenden oder die Objektmethode print() der Objektreferenz eines FileHandles.
184
2
Grundlagen
Beispiele: use FileHandle; my $path = "bla.txt"; # Öffnen der Datei im WriteOnly-Modus my $fh = new FileHandle( $path, O_WRONLY | O_CREAT ); # Prüfen, ob das Öffnen gelang unless ( $fh ) { print( "Fehler beim Öffnen der Datei\n" ); exit( 1 ); } # Perl-Funktion print() print( $fh "Hello World\n" ); # Objekt-Methode print() $fh->print( "Hello World\n" );
Während man bei der print()-Funktion von Perl auf die seltsame Eigenschaft trifft, dass nach dem FileHandle kein Komma stehen darf, hat man bei der Objektmethode print() des Packages FileHandle keinerlei Besonderheiten zu beachten. Ausschalten des Ausgabepuffers Normalerweise benutzt das Betriebssystem einen Puffer für Ausgaben in Dateien, d.h., erst wenn der Puffer voll ist oder das Programm beendet wird, schreibt das Betriebssystem die Daten. Mit der Objektmethode autoflush() kann man den Puffer ausschalten: use FileHandle; my $path = "bla.txt"; my $fh = new FileHandle( $path, O_WRONLY | O_CREAT ); unless ( $fh ) { print( "Fehler beim Öffnen der Datei\n" ); exit( 1 ); } # Ausschalten des Ausgabe-Puffers $fh->autoflush( 1 );
Lesen aus Dateien Auch für das Lesen von Dateien gibt es mehrere Möglichkeiten. Textdateien werden in der Regel zeilenweise gelesen, hierfür kann man entweder den Operator <> verwenden oder die Objektmethoden getline() bzw. getlines() der File-
Ein-/Ausgabe (File I/O)
185
Handle-Klasse. Die Objektmethode getline() liest nur eine einzige Zeile ein und arbeitet immer im skalaren Kontext, während getlines() ausgehend von der aktuellen Dateiposition alle verfügbaren Zeilen einliest und immer in List-Kontext arbeitet (es wird ein Array zurückgegeben, das die Zeilen der Datei als Elemente enthält). Hinweis: Das Zeilenende-Zeichen ist immer Bestandteil der eingelesenen Daten. Beispiele: use FileHandle; my $fh = new FileHandle( "bla.txt", O_RDONLY ); unless ( $fh ) { print( "Fehler beim Öffnen der Datei\n" ); exit( 1 ); } # Einlesen einer Zeile my $line = $fh->getline(); # $line enthält je nach Art der Datei am Ende \n oder # \r\n # Dieses kann man mit chomp() entfernen (bitte nicht # chop() verwenden, da diese Funktion grundsätzlich # das letzte Zeichen entfernt, während chomp() nur # "white space"-Zeichen am Ende entfernt) chomp( $line ); # Einlesen aller weiterer Zeilen my @lines = $fh->getlines(); # Jedes Element von @lines enthält am Ende das # Zeilenende-Zeichen. # Man kann die Zeilenende-Zeichen aller Zeilen in einem # Kommando entfernen mit: chomp( @lines );
Auch das Lesen eines einzelnen Zeichens ist mit der Objektmethode getc() oder der Perl-Funktion getc() möglich: use FileHandle; my $fh = new FileHandle( "bla.txt", O_RDONLY ); unless ( $fh ) { print( "Fehler beim Öffnen der Datei\n" ); exit( 1 ); } my $c = $fh->getc(); # oder my $c = getc( $fh );
186
2
Grundlagen
Beliebig viele Zeichen (hier sind die Zeilenende-Zeichen ganz normale Zeichen, das Einlesen erfolgt nicht zeilenweise) können mit der Objektmethode read() oder der Perl-Funktion read() gelesen werden. Beispiel: use FileHandle; my $fh = new FileHandle( "bla.txt", O_RDONLY ); unless ( $fh ) { print( "Fehler beim Öffnen der Datei\n" ); exit( 1 ); } # Dateien, die mit read() gelesen werden, sollten immer # in den Binärmodus umgestellt werden. binmode( $fh ); my $buf = ""; my $count = $fh->read( $buf, # Es werden 20 Bytes von der # eingelesen. unless ( defined( $count ) ) print( STDERR "Fehler\n" }
20 ); Datei "bla.txt" in $buf { );
if ( $count == 0 ) { print( "Dateiende erreicht\n" ); }
Positionieren innerhalb einer Datei Jedes FileHandle besitzt einen internen Dateizeiger, der den aktuellen Offset innerhalb der Datei angibt (in Bytes gemessen), welcher bei der nächsten I/O-Operation wirksam ist. Nach dem erstmaligen Öffnen einer Datei zum Lesen oder Schreiben steht der Dateizeiger auf »0«. Wird die Datei im Append Modus geöffnet, steht der Dateizeiger am Ende der Datei. Der Dateizeiger hat die Einheit Byte. Steht er zum Beispiel auf »100« und bezieht man sich auf den Beginn der Datei, dann ist der aktuelle Offset innerhalb der Datei 100 Byte, das heißt, die nächste I/O-Operation wird ab dem 101-ten Byte durchgeführt. Mit Hilfe der Objektmethode setpos() des Perl-Moduls FileHandle bzw. der PerlFunktion seek()kann man den Dateizeiger beliebig positionieren. Die Objektmethode getpos() des Perl-Moduls FileHandle bzw. die Perl-Funktion tell() geben die aktuelle Position des Dateizeigers zurück.
Ein-/Ausgabe (File I/O)
187
Im Perl-Modul IO::Handle sind für das Setzen des Dateizeigers 3 vordefinierte Konstanten enthalten, die man benutzen kann, um die speziellen Dateizeigerpositionen Anfang (SEEK_SET), Ende (SEEK_END) und aktuelle Position (SEEK_CUR) anzugeben, die für den Aufruf der Objektmethode setpos() bzw. der Perl-Funktion seek() benötigt werden. Die Konstanten werden nur dann in den aktuellen Namespace importiert, wenn man das Package FileHandle mit der use-Direktive importiert und nicht mit require. Man kann die Konstanten aber auch explizit mit folgender Anweisung in den aktuellen Namespace aufnehmen: use IO::Handle qw( SEEK_SET SEEK_CUR SEEK_END ); # Es geht auch use Fcntl qw ( :seek ); # oder use Fcntl ( ':seek' ); # weil die Konstanten auch im Modul "Fcntl.pm" als # Gruppe ":seek" exportiert werden
Beispiele für das Positionieren des Dateizeigers: use FileHandle; # Die Methode "print()" von IO::Handle mit in den # Namespace aufnehmen, damit wir statt # print( STDERR "xxx" ) # STDERR->print( "xxx" ) # verwenden können. use IO::Handle qw( print ); my $fh = new FileHandle( "bla.txt", O_RDWR ); unless ( $fh ) { STDERR->print( "Fehler beim Öffnen der Datei\n" ); exit( 1 ); } my $pos = $fh->getpos(); # oder $pos = tell( $fh ); print( "Dateiposition = $pos\n" ); # Position setzen # auf Offset 10 unless ( $fh->setpos( 10, SEEK_SET ) ) { STDERR->print( "Fehler beim Positionieren\n" ); exit( 1 ); }
188
2
Grundlagen
print( "Dateiposition = ", $fh->getpos(), "\n" ); # 2 Bytes von der aktuellen Position zurück zum Anfang unless ( seek( $fh, -2, SEEK_CUR ) ) { STDERR->print( "Fehler beim Positionieren\n" ); exit( 1 ); } print( "Dateiposition = ", $fh->getpos(), "\n" ); # Am Ende positionieren unless ( $fh->setpos( 0, SEEK_END ) ) { STDERR->print( "Fehler beim Positionieren\n" ); exit( 1 ); } print( "Dateiposition = ", $fh->getpos(), "\n" );
Dateisperren Wenn mehrere Prozesse gleichzeitig auf dieselbe Datei schreibend zugreifen, muss sichergestellt sein, dass die Daten konsistent bleiben. Zu diesem Zweck kann die PerlFunktion flock() verwendet werden, mit deren Hilfe man Dateien sperren kann. Die Dateisperre wirkt sich nur auf Prozesse aus, die ebenfalls den flock-Mechanismus verwenden. Mit einem Editor kann trotz gesperrter Datei der Inhalt verändert werden! Mehr Informationen über Dateisperren einschließlich Beispiel finden Sie in der Beschreibung der Perl-Funktion flock() in Anhang C.
Dateiinformationen Die Objektmethode stat() des Packages FileHandle liefert Detailinformationen über die geöffnete Datei. Siehe hierzu die Beschreibung der Perl-Funktion stat(). Während die Perl-Funktion stat() auch Informationen über Verzeichnisse liefert, kann man mit der Objektmethode stat() von FileHandle nur Informationen über normale Dateien erhalten. Außerdem muss hier die Datei bereits geöffnet sein, während dies bei der Perl-Funktion stat() nicht nötig ist, da man dieser Funktion den Dateipfad als Parameter übergeben kann. Jetzt habe ich natürlich ein bisschen geflunkert: Die Objektmethode stat() ist nicht in FileHandle, sondern in IO::Handle implementiert. Da jedoch die Klasse FileHandle von IO::Handle abgeleitet ist, erbt sie alle Methoden der übergeordneten Klassen.
Ein-/Ausgabe (File I/O)
189
Wer jetzt nach Klassen oder Vererbung fragt, der sei auf das Kapitel »Objektorientierte Programmierung« verwiesen: Dort werden die Begriffe sonnenklar.
Dateiinhalte löschen Die Klasse FileHandle stellt die Methode truncate() zur Verfügung, mit deren Hilfe alle Inhalte ab der aktuellen Position in der Datei gelöscht werden können. Intern wird, wie bei allen anderen Objektmethoden des Packages FileHandle auch, die Perl Funktion truncate() aufgerufen. truncate() erwartet ein Argument, das die neue Länge der Datei ab der aktuellen Posi-
tion ergibt. Die Funktion gibt einen TRUE Wert bei Erfolg zurück, einen FALSE Wert bei einem Fehler und bricht das Hauptprogramm mit einem fatalen Fehler ab, wenn die Operation beim verwendeten Betriebssystem nicht implementiert ist. Beispiel für truncate(): # Dateiinhalt löschen (Datei leeren) # $fh ist eine Objektreferenz auf eine geöffnete Datei. # WICHTIG: Die Lösch-Operation wirkt sich ab der # aktuellen Position des Dateizeigers aus, nicht etwa # ab Beginn der Datei! # Deshalb vorher den Dateizeiger auf den Anfang der # Datei setzen use FileHandle; # Die Methode "print()" von IO::Handle mit in den # Namespace aufnehmen, damit wir statt # print( STDERR "xxx" ) # STDERR->print( "xxx" ) # verwenden können. use IO::Handle qw( print ); my $fh = new FileHandle( "bla.txt", O_RDWR ); unless ( $fh ) { STDERR->print( "Fehler beim Öffnen der Datei\n" ); exit( 1 ); } # Position des Dateizeigers auf den Beginn der Datei # setzen unless ( $fh->setpos( 0, SEEK_SET ) ) { STDERR->print( "Fehler beim Setzen des ", "Dateizeigers\n" ); exit( 1 ); }
190 # Dateiinhalt leeren unless ( $fh->truncate( 0 ) ) { STDERR->print( "Fehler beim Leeren der Datei\n" ); exit( 1 ); } # Die folgende Operation hätte keine Auswirkung, da sich # der Dateizeiger # am Ende der Datei befindet: $fh->setpos( 0, SEEK_END ); $fh->truncate( 0 ); # Neuen Inhalt in Datei schreiben unless ( $fh->setpos( 0, SEEK_SET ) ) { STDERR->print( "Fehler beim Setzen des ", "Dateizeigers\n" ); exit( 1 ); } $fh->print( "Das ist der neue Inhalt der Datei\n" ); # Datei auf 12 Bytes verkürzen unless ( $fh->setpos( 0, SEEK_SET ) ) { STDERR->print( "Fehler beim Setzen des ", "Dateizeigers\n" ); exit( 1 ); } unless ( $fh->truncate( 12 ) { STDERR->print( "Fehler beim Kürzen der Datei\n" ); exit( 1 ); } # Die letzten 10 Bytes abschneiden unless ( $fh->setpos( -10, SEEK_END ) { STDERR->print( "Fehler beim Setzen des ", "Dateizeigers\n" ); exit( 1 ); } unless ( $fh->truncate( 0 ) ) { STDERR->print( "Fehler beim Kürzen der Datei\n" ); exit( 1 ); }
2
Grundlagen
Ein-/Ausgabe (File I/O)
191
2.11.2 DirHandles Genauso wie geöffnete Dateien werden auch Verzeichnisse vom Betriebssystem verwaltet und belegen Systemressourcen, wenn sie geöffnet sind. Die dafür verwendeten Objekte sind so genannte »DirHandles« und können über das Perl-Package DirHandle benutzt werden, das Methoden zum Öffnen, Lesen, Schließen sowie Zurücksetzen des Verzeichniszeigers zur Verfügung stellt.
Öffnen eines Verzeichnisses Ähnlich wie bei FileHandles kann man Verzeichnisobjekte (DirHandles) mit dem Konstruktor new() erzeugen. Syntax: use DirHandle; new DirHandle( path )
path muss der absolute oder relative Pfadname eines existierenden Verzeichnisses sein. Zum Anlegen von Verzeichnissen siehe die Perl-Funktion mkdir() sowie die Funktion mkpath() des Packages File::Path, das in der Standard-Distribution von Perl enthalten ist. Zum Löschen von Verzeichnissen siehe die Funktion rmtree() des Perl-Packages File:: Path. Auch die Perl-Funktion rmdir() kann verwendet werden, allerdings funktioniert
sie nur, wenn das angegebene Verzeichnis leer ist. Beispiele für DirHandle: use DirHandle; # Die Methode "print()" von IO::Handle mit in den # Namespace aufnehmen, damit wir statt # print( STDERR "xxx" ) # STDERR->print( "xxx" ) # verwenden können. use IO::Handle qw( print ); # Öffnen des aktuellen Verzeichnisses my $dh = new DirHandle( "." ); unless ( $dh ) { STDERR->print( "Fehler beim Öffnen des Verz.\n" ); } # Öffnen des übergeordneten Verzeichnisses $dh = new DirHandle( ".." ); unless ( $dh ) {
192
2
Grundlagen
STDERR->print( "Fehler beim Öffnen des Verz.\n" ); } # Öffnen des Verzeichnisses mit absoluter Pfadangabe $dh = new DirHandle( "D:/temp" ); unless ( $dh ) { STDERR->print( "Fehler beim Öffnen des Verz.\n" ); }
Schließen von Verzeichnissen Ähnlich wie bei FileHandles kann (und muss) man die Systemressourcen der geöffneten Verzeichnisse nach Gebrauch wieder freigeben, indem man die DirHandles schließt: ... $dh->close(); # oder undef( $dh );
Lesen von Verzeichnissen Wenn man ein Verzeichnis geöffnet und damit ein DirHandle-Objekt erhalten hat, kann man dieses mit der Objektmethode read() auslesen. Man erhält bei jedem Aufruf von read() einen Verzeichniseintrag. Auch das aktuelle Verzeichnis (».«) sowie das übergeordnete Verzeichnis (»..«) sind je ein Eintrag. Beispiel (angenommen, das aktuelle Verzeichnis enthält 2 Dateien (a.txt und b.txt) sowie das Unterverzeichnis c): use DirHandle; # Die Methode "print()" von IO::Handle mit in den # Namespace aufnehmen, damit wir statt # print( STDERR "xxx" ) # STDERR->print( "xxx" ) # verwenden können. use IO::Handle qw( print ); my $dh = new DirHandle( "." ); unless ( $dh ) { STDERR->print( "kann Verzeichnis . nicht öffnen\n" ); exit( 1 ); }
Ein-/Ausgabe (File I/O)
193
while ( defined( my $entry = $dh->read() ) ) { print( "$entry\n" ); } $dh->close();
Es wird ausgegeben: . .. a.txt b.txt c
Verzeichnis zurückspulen Mit Hilfe der Objektmethode rewind() kann man die Verzeichniseinträge von neuem mit read() lesen. Sollte sich im Verzeichnis seit dem Öffnen etwas verändert haben (zum Beispiel eine Datei wurde gelöscht oder hinzugefügt), dann merkt man das nicht, weil die tatsächlichen Verzeichniseinträge auf der Festplatte nur einmalig beim Öffnen des Verzeichnisses gelesen werden. Anschließend verwendet das Package DirHandle die Einträge aus dem Hauptspeicher, die bereits veraltet sein können. Beispiel für das Zurückspulen eines Verzeichnisses: use DirHandle; # Verzeichnis öffnen my $dh = new DirHandle( "." ); unless ( $dh ) { print( STDERR "kann Verzeichnis . nicht öffnen\n" ); exit( 1 ); } # Verzeichniseinträge lesen while ( defined( my $entry = $dh->read() ) ) { print( "$entry\n" ); entry = $dh->read(); } # Verzeichnis zurückspulen $dh->rewind(); # der nächste read()-Aufruf gibt wieder den ersten # Eintrag zurück # Verzeichnis schließen my $ $dh->close()
194
2
Grundlagen
Spezielle Datei-Operatoren Perl stellt für Abfragen im Dateisystem einige spezielle Operatoren bereit, die ich im Folgenden beschreiben möchte. Alle aufgeführten Operatoren arbeiten nach folgendem Schema: if ( -operator ) { # Der Operator liefert TRUE } # oder unless ( -operator ) { # Der Operator liefert FALSE }
operator steht hierbei für einen der unten aufgeführten Buchstaben. Hier ein vorausblickendes Beispiel: unless ( -d "/tmp" ) { print( "Der Pfad '/tmp' ist kein Verzeichnis\n" ); }
Sollten Sie Probleme mit »Pipe«, »sticky Bit« etc. haben: Die meisten Begriffe haben nur unter UNIX eine Bedeutung und sind in der jeweiligen Betriebssystemdokumentation beschrieben.
Operatoren für den Dateityp Operator
Bedeutung
-f
Der Pfad ist eine normale Datei.
-d
Der Pfad ist ein Verzeichnis.
-l
Der Pfad ist ein symbolischer Link.
-P
Der Pfad ist eine »Named Pipe« (FIFO), oder das geöffnete FileHandle ist eine Pipe.
-S
Der Pfad ist ein Socket.
-b
Der Pfad ist eine blockorientierte Gerätedatei.
-c
Der Pfad ist eine zeichenorientierte Gerätedatei.
-t
Dem geöffneten FileHandle ist ein TTY zugeordnet.
Operatoren für Zugriffsrechte Operator
Bedeutung
-r
Die effektive User-/Gruppen-ID hat Leserechte für den Pfad.
-w
Die effektive User-/Gruppen-ID hat Schreibrechte für den Pfad.
Ein-/Ausgabe (File I/O)
195
Operator
Bedeutung
-x
Die effektive User-/Gruppen-ID hat Ausführrechte für den Pfad.
-o
Die effektive User-/Gruppen-ID ist Eigentümer des Pfads.
-R, -W, X, -O
Diese Operatoren arbeiten genauso wie die kleingeschriebenen Varianten, beziehen sich jedoch auf die reale User-/Gruppen-ID statt auf die effektive ID.
Operatoren für Datei-Informationen Operator
Bedeutung
-e
Der Pfad existiert.
-z
Der Pfad ist leer (Dateigröße 0).
-s
Gibt die Dateigröße in Bytes zurück.
Operatoren für den Dateimodus Operator
Bedeutung
-u
Der Pfad hat das »setuid« Bit gesetzt.
-g
Der Pfad hat das »setgid« Bit gesetzt.
-k
Der Pfad hat das »sticky bit« gesetzt.
Neben den gezeigten Operatoren gibt es weitere, seltener benutzte, die Sie unter dem Thema »perlfunc« der Online-Dokumentation finden.
3 Pattern Matching Die Frage »Was, bitteschön, ist Pattern Matching?« kann ich gut nachvollziehen. Für sehr viele Programmierer, gerade für Einsteiger, ist dieses Thema lange Zeit ein Buch mit sieben Siegeln. Manchen Entwicklern werden sich die Tiefen des Pattern Matching wohl nie eröffnen. Das liegt daran, dass es zum einen sehr kompliziert ist, zum anderen besitzt selbst ein relativ einfach aufgebautes Pattern sehr kryptische Zeichen wie zum Beispiel; ^(.+).*?a{3}$
Ich selbst habe einige Jahre gebraucht, bis ich so weit war, dieses schwierige Thema zu verstehen und mein Wissen einigermaßen verständlich an andere weiterzugeben. Deshalb möchte ich kurz darauf eingehen, warum sich einige schlaue Leute auf die Wahnsinnstat eingelassen haben, Pattern Matching in Verbindung mit regulären Ausdrücken (neudeutsch: »regular expressions«) in die Welt zu setzen: Einer der vielen Gründe für das Entstehen von Pattern Matching ist wohl die Frage: »Wie kann ich feststellen, ob eine vorgegebene E-Mail-Adresse gültig ist oder nicht?« Für Amerikaner könnte der Grund auch sein: »Wie kann ich möglichst einfach alle deutschen Sonderzeichen (Umlaute und scharfes »ß«) aus Texten entfernen?« Oder für Mathematiker: »Wie kann ich überprüfen, ob in einem String eine gültige Festkommazahl steht oder nicht?« Man könnte die Liste der Entstehungsgründe von Pattern Matching beliebig lange fortführen. Die Komplexität von Pattern Matching kommt hauptsächlich dadurch zustande, dass den Zeichenketten eine bestimmte Semantik zugrunde liegt, die wie die Semantik einer Sprache eben sehr komplex ist. Allein dies begründet, warum es bis heute kein wirklich brauchbares Programm gibt, um Texte in eine andere Sprache zu übersetzen. Zuerst wollen wir die seltsamen Begriffe etwas verständlicher machen:
Was bedeutet Matching? Der Begriff »Matching« kann ins Deutsche übersetzt werden mit »Auffinden« oder »Übereinstimmung«. Wenn wir zum Beispiel nach einem »e« in der Zeichenkette »Der« suchen, dann haben wir einen »Match« (man könnte auch »Treffer« sagen), weil
198
3
Pattern Matching
das »e« im Wort »Der« vorkommt. Suchen wir das gleiche Zeichen aber in der Zeichenkette »Das«, dann erzeugt die Suche keinen »Match«, weil »e« eben nicht in »Das« vorkommt. Hätten wir das Zeichen »E« in der Zeichenkette »Der« gesucht, dann hängt das Ergebnis der Suche davon ab, ob wir wirklich nach dem großen »E« suchen oder ob uns Groß- und Kleinschreibung nicht interessiert. Wir können beim Matching also angeben, ob die Suche case-sensitive sein soll oder nicht. Nachdem wir den Begriff »Matching« geklärt haben, wollen wir uns dem nächsten unverständlichen Begriff, »Pattern«, zuwenden:
Was ist ein Pattern? Das ist schnell erklärt: Ein »Pattern« ist ein Muster. Aber ich will den geneigten Leser nicht verzweifeln lassen. »Muster« ist die wörtliche Übersetzung von »Pattern« und kann alles möglich bedeuten. In unserem Fall ist damit der Begriff »Suchmuster« gemeint. Im einfachsten Fall kann dies ein einzelnes Zeichen sein, das in einer Zeichenkette gesucht wird. So kann zum Beispiel das Zeichen »e« ein Suchmuster sein, nach dem ich in einer Zeichenkette, zum Beispiel in »Der Apfel fällt nicht weit vom Birnbaum« suche. Etwas umständlicher wird ein Pattern, wenn es reguläre Ausdrücke enthält. Auch hier ein kleines Beispiel zur Einführung: Ich möchte eine Zeichenkette daraufhin überprüfen, ob sie mit einem Punkt endet. In diesem Fall reicht es nicht aus, nach einem Punkt zu suchen, denn dieser kann ja überall in der Zeichenkette vorkommen, am Anfang, mittendrin oder auch am Ende. Wir müssen einen Weg finden, das Suchmuster (unser »Pattern«) so zu schreiben, dass der Interpreter weiß: Wir suchen einen Punkt am Ende der Zeichenkette, nicht irgendwo. Wie sagt man das dem Interpreter? In Form von regulären Ausdrücken. Und damit wären wir schon beim nächsten erklärungsbedürftigen Begriff:
Was sind reguläre Ausdrücke? Ein regulärer Ausdruck bringt Semantik in das Suchmuster (englisch: »search pattern« oder kurz »pattern«). Nun werden manche fragen, »Was ist Semantik? Ich kenne nur Syntax.« Kurz zum Unterschied zwischen Syntax und Semantik: Wenn in einem deutschen Text das Zeichen »Ø« vorkommt, dann handelt es sich wohl um einen Syntaxfehler, weil dieses Zeichen in der deutschen Sprache nicht vorkommen kann.
199
Wenn jemand das Wort »weil« mehrfach hintereinander schreibt, dann ist das kein Syntaxfehler, denn das Wort laut Duden durchaus erlaubt ist. Aber auf der anderen Seite ergibt der Satz »weil weil weil weil« nicht wirklich Sinn, oder? Das ist nämlich ein Semantikfehler, weil nach dem Wort »weil« dasselbe Wort nicht noch einmal stehen kann. Wie wir sehen, sind Syntaxfehler wesentlich leichter zu erkennen als Semantikfehler, da Semantik immer komplexer ist als reine Syntax. Soweit zum Ausflug in das Land »Semantik«. Bleiben wir der Einfachheit halber bei unserem Beispiel: Wir suchen nach einem Punkt am Ende einer Zeichenkette. Zu diesem Zweck haben die Erfinder der regulären Ausdrücke spezielle Sonderzeichen erfunden, mit denen unter anderem das Ende eines Strings identifiziert werden kann. Bei regulären Ausdrücken wurde das Dollarzeichen »$« dafür auserkoren. Wann immer man am Ende eines Patterns ein Dollarzeichen findet, heißt dies: Ende der zu durchsuchenden Zeichenkette. (Obwohl es natürlich auch hier eine Ausnahme gibt, aber alles zu seiner Zeit.) Und nun zu unserer ursprünglichen Aufgabe: »Suche nach einem Punkt am Ende der Zeichenkette«. Ganz intuitiv würde ich das folgende Pattern vorschlagen: .$
Das sieht doch schon nicht schlecht aus: Das Pattern besteht aus einem Punkt und einem Dollarzeichen am Ende. Also: »Suche nach einem Punkt am Ende der Zeichenkette!«. Leider ist die Sache nicht ganz so einfach, denn der Punkt wurde ebenfalls als Sonderzeichen für Patterns ausgesucht und hat somit eine besondere Bedeutung (für Wissbegierige: er steht für ein beliebiges Zeichen). Jetzt stellt sich die Frage, wie man dem Interpreter angibt, dass er nach einem Punkt am Ende und nicht nach irgendeinem beliebigen Zeichen am Ende des Strings suchen soll. Die Antwort ist, wie so oft, der Backslash: Wenn wir einen Backslash vor ein Zeichen setzen, das als Sonderzeichen fungiert, dann verliert es seine besondere Bedeutung und wird zu einem ganz normalen Zeichen. Also schreiben wir unser Pattern noch mal: \.$
Und schon haben wir unser erstes Pattern geschrieben! Ich möchte ja kein Schwarzseher sein, aber ganz ehrlich: Von hier bis zur völligen Verinnerlichung von regulären Ausdrücken wird noch eine (hoffentlich nicht zu lange) Zeit vergehen. Als abschreckendes Beispiel wollen wir eines der eingangs erwähnten Probleme noch einmal aufgreifen: »Wie kann ich feststellen, ob eine vorgegebene E-Mail-Adresse gültig ist oder nicht?«
200
3
Pattern Matching
In Worten ausgedrückt ist es gar nicht so schwer: Eine gültige E-Mail-Adresse muss das At-Zeichen »@« genau einmal enthalten. Umlaute und sonstige Sonderzeichen dürfen nicht vorhanden sein, mit Ausnahme der Zeichen ».« und »-«. Nach dem At-Zeichen »@« muss eine Internet-Domain in der Form »abc.def« stehen, sie kann aber auch länger sein, zum Beispiel »ab.cd.ef.gh«. Ein Punkt muss hier mindestens vorhanden sein, sonst ist die Internet-Domain nicht gültig (»de« geht also nicht). Zum Schluss sei noch angemerkt, dass E-Mail-Adressen caseinsensitive sind, »abc« also dasselbe ist wie »Abc«, »ABC« oder »aBc«. Das scheint wohl doch nicht so einfach zu sein. Wenn ich nun den regulären Ausdruck dafür zeige, sehen Sie gleich, warum: ^[A-Z\d_.-]+\@([A-Z_\d.-]+\.)+[A-Z]+$
Im Moment wissen wir nur, dass ein Dollarzeichen am Ende des Patterns das Ende des zu durchsuchenden Strings bedeutet und die Zeichenkette »\.« einen literalen Punkt (also wirklich das Zeichen ».«). Wie wir deutlich sehen, müssen noch viele Dinge gelernt werden, um den Rest zu verstehen.
Der Binding-Operator Pattern Matching wird immer in Verbindung mit dem Binding-Operator =~ bzw. dessen Negation !~ sowie dem Matching-Operator m oder dem Substitutionsoperator s verwendet. Was bedeutet nun wieder »Binding-Operator«? In Ergänzung zur Beschreibung dieses seltsamen Gebildes im Kapitel »Operatoren« hier noch einmal die Kurzfassung des Operators =~: Der Operator =~ ist binär und hat somit zwei Operanden: einen links und einen rechts. Der links vom Operator stehende Operand ist der String, auf den die rechts vom Operator stehende Operation angewendet werden soll. Der rechte Operand ist entweder eine Suche (englisch: »search«) oder eine Ersetzung (englisch: »substitution«). Syntax für den Binding-Operator: string =~ /searchPattern/options string =~ m~searchPattern~options # Hinweis: statt "~" kann auch ein anderes Zeichen # verwendet werden, siehe unten string !~ /searchPattern/options string !~ m~searchPattern~options
201
Der Ausdruck rechts vom Binding-Operator besteht aus zwei Teilen: dem Suchmuster (searchPattern), nach dem im links vom Binding-Operator stehenden string gesucht wird, sowie einem zweiten Teil (options), mit dem das Verhalten der Suche gesteuert werden kann. Dieser zweite Teil kann auch leer sein, in diesem Fall werden die Standardeinstellungen verwendet. Die beiden Teile werden durch ein spezielles Zeichen voneinander getrennt. In der ersten Variante wird das Standardtrennzeichen »/« als Trenner für die beiden Teile verwendet. Bei der zweiten Variante kann ein selbst definiertes Trennzeichen verwendet werden. In diesem Fall muss vor dem Trennzeichen der Buchstabe »m« (Matching) stehen. Diese Variante sollte man immer dann verwenden, wenn das Standardtrennzeichen selbst Bestandteil des searchPattern ist, da man sonst den Slash durch Voranstellen eines Backslashs entwerten müsste. Noch einmal zur Verinnerlichung: Bei Pattern Matching sind m und s häufig keine normalen Zeichen, sondern Operatoren. string ist normalerweise eine skalare Variable, kann aber auch ein beliebiger Ausdruck wie zum Beispiel ein Funktionsaufruf sein, der zu einem skalaren Wert evaluiert (das heißt, das Ergebnis des Ausdrucks muss ein skalarer Wert sein). Beispiel für einfaches Pattern Matching: # # # #
Wir wollen wissen, ob in einem String die Zeichenkette "/usr/bin" vorkommt. Es werden die Standardeinstellungen ohne Optionen übernommen (die Suche ist case-sensitive).
# Verwendung des Standardtrennzeichens (Slash muss # entwertet werden, weil er auch in unserem Pattern # selbst vorkommt): /\/a\/b/ # Einfacher: Verwendung eines selbst definierten # Trennzeichens, das nicht im Pattern vorkommt m~/a/b~ # Der Buchstabe "m" muss in diesem Fall vor dem # selbst definierten Trennzeichen stehen.
Als selbst definiertes Trennzeichen kann jedes Zeichen außer Leerzeichen, Tab und Zeilenende-Zeichen verwendet werden. Man sollte aber ein Zeichen wählen, das selten vorkommt wird (wie z.B. die Tilde ~).
202
3
Pattern Matching
Wenn das Pattern einen Treffer (englisch: »match«) im zu durchsuchenden String erzielt, dann evaluiert der Ausdruck rechts vom Binding-Operator =~ zu einem TRUEWert, andernfalls zu FALSE. Man kann also mit einer if-Abfrage überprüfen, ob das Suchmuster vorkommt oder nicht. Beispiel: my $str = "hallo wie geht es? - danke, geht so"; if ( $str =~ /geht/ ) { print( "Im String kommt das Wort 'geht' vor\n" ); } else { print( "Das Wort 'geht' kommt nicht vor\n" ); }
Die Suche nach dem Suchmuster beginnt zunächst immer am Anfang des zu durchsuchenden Strings (Ausnahme: wenn die Option g verwendet wird; siehe unten). Dies bedeutet, dass im obigen Beispiel das Wort »geht« des Satzteiles »hallo wie geht es« gefunden wird, nicht das zweite Vorkommen desselben Wortes im zweiten Satzteil »danke, geht so«. Wenn die vordefinierte Variable $_ nach einem Suchmuster durchsucht werden soll, kann der Binding-Operator entfallen: $_ = "hallo wie geht es? - danke, geht so"; if ( / wie/ ) # Beachte: Vor "wie" steht ein Blank. # Das Suchmuster wird gefunden, die Abfrage ist TRUE. if ( / hallo/ ) # Keine Übereinstimmung vorhanden (Blank vor hallo), # die Abfrage ist FALSE # Zweite Variante mit einem selbst definierten # Trennzeichen if ( m~-danke~ ) # Keine Übereinstimmung, die Abfrage ist FALSE, weil # der Bindestrich im Pattern ohne Blank vor "danke" # steht. Im zu durchsuchenden String ist aber ein # Blank dazwischen. if ( m~- da~ ) # Übereinstimmung, die Abfrage ist TRUE
Man sollte mit der Variable »$_« vorsichtig umgehen, weil sie von vielen Funktionen und Operationen ebenfalls verändert wird, was zu unerwünschten Nebeneffekten führen kann.
203
Mit der Negation des Binding-Operators (!~) kann man feststellen, ob ein Pattern nicht in einem String vorkommt: # Verwenden des Binding-Operators "!~" my $str = "hallo wie geht es?"; if ( $str !~ /geht/ ) { print( "Das Wort 'geht' kommt nicht vor\n" ); } else { print( "Im String kommt das Wort 'geht' vor\n" ); }
Natürlich kann man denselben Effekt erreichen, indem man statt einer if-Abfrage eine unless-Abfrage macht: # Verwenden von "unless" vermeidet oft "!~", man kann # stattdessen "=~" benutzen my $str = "hallo wie geht es?"; unless ( $str =~ /geht/ ) { print( "Das Wort 'geht' kommt nicht vor\n" ); } else { print( "Im String kommt das Wort 'geht' vor\n" ); }
Metazeichen Das Suchmuster kann so genannte »Metazeichen« enthalten, das sind Zeichen mit einer besonderen Bedeutung, mit deren Hilfe reguläre Ausdrücke in das Suchmuster eingeflochten werden können. Folgende Metazeichen werden für Pattern Matching verwendet: {}[]()^$.|*+?\
Die dargestellten Metazeichen werden weiter unten noch in einem eigenen Abschnitt erläutert. Will man eines der oben aufgeführten Metazeichen literal (ohne besondere Bedeutung) im Pattern verwenden, dann muss das Metazeichen mit einem vorangestellten Backslash entwertet werden. Beispiele: # Es soll überprüft werden, ob in einer Zeile ein Punkt # vorkommt. my $line = <STDIN>; chomp( $line );
204
3
Pattern Matching
if ( $line =~ /./ ) # Diese Abfrage ist falsch, weil der Punkt eine # besondere Bedeutung hat, er steht für irgendein # Zeichen. # Das führt dazu, dass die Abfrage immer TRUE ist, # solange die Variable "$line" nicht leer ist. # Richtig: if ( $line =~ /\./ ) # Hier wird literal nach einem Punkt gesucht, # die Abfrage ist also nur dann TRUE, # wenn die Zeile einen Punkt enthält. # Beispiel für einen Dateipfad in Windows mit # Backslashes (die man in Perl nie verwenden sollte) my $path = 'C:\WINNT'; # Beachte: Hier müssen einfache Quotes verwendet werden, # da der Backslash auch in Strings mit doppelten Quotes eine besondere Bedeutung hat. if ( $path =~ /C:\\/ ) # Richtig, der Backslash muss mit einem weiteren # Backslash entwertet werden. # Falsch: if ( $path =~ /C:\/ )
# führt zu einer Fehlermeldung
Oft steht das Suchmuster nicht als Stringkonstante im Code, sondern ist in einer Variable gespeichert. my $pattern = "......."; if ( $str =~ /$pattern/ ) ...
Wir können das Standardtrennzeichen »/« auch dann verwenden, wenn der Slash im Wert der Variable $pattern enthalten ist: Es darf nur im Pattern selbst nicht im Sourcecode stehen. my $pattern = "/usr/bin"; if ( $str =~ /$pattern/ ) # funktioniert
Wenn man sicherstellen möchte, dass alle Zeichen in der Variable $pattern literal interpretiert werden, ohne dass die oben aufgeführten Metazeichen eine besondere Bedeutung haben, dann kann man die Escapesequenz \Q vor die Variable stellen: my $pattern = "......."; if ( $str =~ /\Q$pattern/ ) ...
Die Escapesequenz \Q bewirkt, dass alle nachfolgenden Zeichen literal interpretiert werden, und somit die Metazeichen keine besondere Bedeutung mehr haben.
Matching-Optionen
205
Die Escapesequenz \Q schaltet die Interpretation von Metazeichen für alle durch die Sequenz folgenden Zeichen im Suchmuster aus. Will man ab einer bestimmten Stelle im Suchmuster den Metazeichen-Mechanismus wieder einschalten, kann man die Escapesequenz \E angeben: my $pattern = "......."; if ( $str =~ /\Q$pattern\E.\Q./ ) ...
Der Metazeichen-Mechanismus wird für den Inhalt der Variable $pattern ausgeschaltet. Der darauf folgende Punkt jedoch wird wieder als Metazeichen interpretiert, da mit der Sequenz \E der Mechanismus wieder eingeschaltet ist. Der nachfolgende Punkt jedoch wird literal als normaler Punkt interpretiert, weil der Mechanismus mit \Q nochmals ausgeschaltet wurde. Weitere Metazeichen folgen noch.
3.1 Matching-Optionen Wir haben oben gelernt, dass der letzte Teil von Pattern Matching Optionen beinhalten kann, mit denen man die Matchingoperation beeinflussen kann. Ohne Angabe von Optionen führt Perl eine case-sensitive Suche durch, das heißt, Klein- und Großbuchstaben werden unterschieden. Daneben gibt es weitere Standardeinstellungen, auf die an entsprechender Stelle eingegangen wird. Im Folgenden sind die wichtigsten Optionen beschrieben, mit denen man das Verhalten der Suche einstellen kann:
3.1.1 Option i Mit dieser Option stellt man die Suche so ein, dass kein Unterschied zwischen Kleinund Großbuchstaben mehr gemacht wird. Beispiel: my $str = "Geht es gut? - Danke, GEHT so"; if ( $str =~ /geht/i ) { print( "Das Wort 'geht' kommt vor\n" ); } else { print( "Das Wort 'geht' kommt nicht vor\n" ); }
Mit der Option i führen sowohl 'Geht' als auch 'GEHT' zu einem Match.
206
3
Pattern Matching
Auch im Pattern spielt damit die Groß- und Kleinschreibung keine Rolle mehr: if ( $str =~ /GeHT/i ) # ist dasselbe wie if ( $str =~ /geht/i ) # oder if ( $str =~ /GEHT/i )
Ohne Angabe der Option i würde das Suchmuster nicht gefunden, da das Wort »geht« kleingeschrieben nicht im zu durchsuchenden String vorkommt.
3.1.2 Option m Diese Option führt dazu, dass das Metazeichen ».« bei einem Zeilenende-Zeichen keinen Treffer erzeugt (das ist die Standardeinstellung). Der zu durchsuchende String wird sozusagen in einzelne Zeilen aufgeteilt. Zeilenende-Zeichen haben also eine besondere Bedeutung. Beispiel: my $str = "\n"; if ( $str =~ /./m ) # Diese Abfrage liefert FALSE, weil mit der Option "m" # das Metazeichen "." keine Treffer für Zeilenende# Zeichen erzielt. # Da dies die Standardeinstellung ist, liefert auch die # folgende Abfrage FALSE: if ( $str =~ /./ )
Eine zweite Bedeutung gewinnt die Option m in Verbindung mit den Metazeichen »^« und »$«. Das Dollarzeichen kennen wir bereits, es bedeutet: Ende der Zeichenkette. Auch das Hütchen »^« ist schnell erklärt, es bedeutet nämlich das Gegenteil und steht für: Anfang der Zeichenkette. Mit der Option m gewinnt das Zeilenende-Zeichen die Bedeutung »Ende eines Teilstrings« bzw. »Anfang eines Teilstrings«. Wir sehen uns am besten ein Beispiel an: # Zu durchsuchender String, der aus mehreren # Zeilen besteht my $str = "Zeile1\nZeile2\nZeile3"; # Suche nach "Zeile2" am Beginn des Strings ohne # eine Option if ( $str =~ /^Zeile2/ ) { print( "/^Zeile2/ hat einen Treffer\n" ); }
Matching-Optionen
207
# Dasselbe mit der Option "m" if ( $str =~ /^Zeile2/m ) { print( "/^Zeile2/m hat einen Treffer\n" ); }
Wenn wir den Code ausführen, erhalten wir die Ausgabe: /^Zeile2/m hat einen Treffer
Da »Zeile2« nicht ganz am Beginn unseres Strings vorkommt, kann es mit dem Pattern »^Zeile2« ohne Option m nicht gefunden werden. Hier würde nur das Pattern »^Zeile1« einen Treffer landen. Mit Option m jedoch gewinnt das Metazeichen »^« auch die Bedeutung »Anfang eines Teilstrings«. Die Zeilenende-Zeichen teilen unseren String in drei Teilstrings auf, und somit führt die Suche zu einem Treffer. Dieselbe Eigenschaft hat die Option m auch bei dem Metazeichen »$«: # Zu durchsuchender String, der aus mehreren # Zeilen besteht my $str = "Zeile1\nZeile2\nZeile3"; # Suche nach "Zeile2" am Ende des Strings ohne # eine Option if ( $str =~ /Zeile2$/ ) { print( '/Zeile2$/ hat einen Treffer', "\n" ); } # Dasselbe mit der Option "m" if ( $str =~ /Zeile2$/m ) { print( '/Zeile2$/m hat einen Treffer', "\n" ); }
Auch hier erhalten wir wieder nur einen Treffer mit der Option m: /Zeile2$/m hat einen Treffer
Natürlich gilt das Gesagte auch für die Kombination von »^« und »$«: # Zu durchsuchender String, der aus mehreren # Zeilen besteht my $str = "Zeile1\nZeile2\nZeile3"; # Die Suche ist so aufgebaut, dass der zu # durchsuchende String nur die Zeichenkette "Zeile2" # enthalten darf, wenn ein Treffer erzeugt werden # soll. "Zeile2" steht also sowohl am Anfang als # auch am Ende des Strings # ohne eine Option. if ( $str =~ /^Zeile2$/ ) {
208
3
Pattern Matching
print( '/^Zeile2$/ hat einen Treffer', "\n" ); } # Dasselbe mit der Option "m" if ( $str =~ /^Zeile2$/m ) { print( '/^Zeile2$/m hat einen Treffer', "\n" ); }
Auch für diesen Fall liefert nur die Suche mit der Option m einen Treffer: /^Zeile2$/m hat einen Treffer
3.1.3 Option s Diese Option stellt die Matching-Operation so ein, dass das Metazeichen ».« auch bei einem Zeilenende-Zeichen einen Treffer erzeugt (dies ist nicht die Standardeinstellung). Der zu durchsuchende String wird als Ansammlung einzelner Zeichen aufgefasst, ohne dass Zeilenende-Zeichen eine besondere Bedeutung haben. Beispiel: my $str = "\n"; if ( $str =~ /./m ) # Diese Abfrage liefert FALSE (wie oben). if ( $str =~ /./ ) # Dasselbe, weil "m" die Standardeinstellung ist if ( $str =~ /./s ) # Diese Abfrage jedoch liefert TRUE, da das Metazeichen # "." mit der Option s auch bei einem Zeilenende-Zeichen # einen Treffer erzielt.
Die Metazeichen »^« (Anfang des Strings) und »$« (Ende des Strings) sind hier wirklich in dieser Bedeutung zu interpretieren. »^« erzielt nur dann einen Treffer, wenn das Pattern wirklich ganz am Beginn steht, »$« erzielt dann einen Match, wenn das Pattern entweder ganz am Ende steht oder wenn zwischen Pattern und String-Ende nur ein einzelnes Zeilenende-Zeichen steht. Zeilenende-Zeichen sind eine Sache für sich. In UNIX ist es das Zeichen »\n«, in Windows die Kombination »\r\n«. Wird der Inhalt einer Datei ohne besondere Vorkehrungen gelesen, dann führt der Interpreter intern eine Umwandlung der unterschiedlichen Zeilenende-Zeichen durch, je nachdem, in welchem Betriebssystem man arbeitet. Schaltet man das FileHandle vor der ersten I/O-Operation aber mit der Funktion binmode() auf Binärmodus, dann erfolgt keine Umwandlung der Zeilenende-Zeichen.
Matching-Optionen
209
Dadurch kann man einige Überraschungen erleben. Intern gilt in Perl als ZeilenendeZeichen nämlich nur »\n«, nicht aber »\r\n«, auch dann nicht, wenn man den Code unter Windows ausführt. Hier ist, so glaube ich, ein Beispiel angebracht: # Zu durchsuchender String, der aus mehreren # Zeilen besteht my $str = "Zeile1\nZeile2\nZeile3\n"; # Nun wollen wir das Zeichen "3" am Ende des Strings # mit der Option "s" suchen. Dies sollte einen Treffer # liefern. if ( $str =~ /3$/s ) { print( '/3$/s liefert einen Treffer', "\n" ); }
Die Ausgabe des Codes ist wie erwartet: /3$/s liefert einen Treffer
Stehen am Ende des Strings mehrere Zeilenende-Zeichen, wird kein Treffer erzielt: # zu durchsuchender String. Er besteht aus mehreren # Zeilen, aber am Ende stehen mehrere Zeilenende# Zeichen. my $str = "Zeile1\nZeile2\nZeile3\n\n\n"; # Nun wollen wir das Zeichen "3" am Ende des Strings # mit der Option "s" suchen, es sollte keinen Treffer # liefern if ( $str =~ /3$/s ) { print( '/3$/s liefert einen Treffer', "\n" ); } else { print( '/3$/s liefert keinen Treffer', "\n" ); }
Ausgabe: /3$/s liefert keinen Treffer
Auch das ist wie erwartet. Hinweis: Mit der Option m hätten wir einen Treffer erzielt. Wenn wir nun das Zeilenende-Zeichenvon Windows verwenden: # Zu durchsuchender String, der aus mehreren # Zeilen besteht my $str = "Zeile1\r\nZeile2\r\nZeile3\r\n";
210
3
Pattern Matching
# Nun wollen wir das Zeichen "3" am Ende des Strings # mit der Option "s" suchen. Dies sollte einen Treffer # liefern. if ( $str =~ /3$/s ) { print( '/3$/s liefert einen Treffer', "\n" ); } else { print( '/3$/s liefert keinen Treffer', "\n" ); }
Jetzt erhalten wir keinen Treffer, weil die Zeichenkette »\r\n« im Perl-Code kein Zeilenende-Zeichen ist: /3$/s liefert keinen Treffer
Schreiben wir nun den Inhalt von $str unter Windows in eine Datei und lesen diese aus (sie sollte den Inhalt »Zeile1\r\nZeile2\r\nZeile3\r\n« haben) : use FileHandle; my $fh = new FileHandle( "bla.txt", "r" ); unless ( $fh ) { print( STDERR "Fehler beim Öffnen der Datei\n" ); exit( 1 ); } my $str = join( "", <$fh> ); $fh->close(); if ( $str =~ /3$/s ) { print( '/3$/s liefert einen Treffer', "\n" ); } else { print( '/3$/s liefert keinen Treffer', "\n" ); }
Dann erhalten wir die Ausgabe: /3$/s liefert einen Treffer
obwohl in der Datei am Ende einer Zeile die Zeichenkette »\r\n« steht. Der Grund hierfür ist, dass Perl FileHandles per Default im ASCII-Modus öffnet und beim Einlesen die Zeilenende-Zeichen von Windows in »\n« umwandelt. Jetzt verwenden wir denselben Code, schalten das FileHandle aber auf Binärmodus um: use FileHandle; my $fh = new FileHandle( "bla.txt", "r" ); unless ( $fh ) { print( STDERR "Fehler beim Öffnen der Datei\n" );
Matching-Optionen
211
exit( 1 ); } # Umschalten auf Binärmodus (vor der ersten I/O# Operation) binmode( $fh ); my $str = join( "", <$fh> ); $fh->close(); if ( $str =~ /3$/s ) { print( '/3$/s liefert einen Treffer', "\n" ); } else { print( '/3$/s liefert keinen Treffer', "\n" ); }
wieder gibt es keinen Treffer, weil keine Umwandlung der Windows Zeilenende Zeichen stattfindet. Im Perl-Code sollten Sie bis auf wenige Ausnahmen nur »\n« als Zeilenende-Zeichen verwenden, da vom Interpreter alle betriebssystemabhängigen Endezeichen immer in »\n« umgewandelt werden (es sei denn, Sie lesen Daten binär ein).
3.1.4 Option ms Man kann die Optionen m und s auch gleichzeitig angeben. In diesem Fall erzeugt das Metazeichen ».« einen Match für das Zeilenende-Zeichen. Hier wird also das Verhalten der Option s benutzt. Die Metazeichen »^« und »$« werden jedoch so behandelt wie bei der Option m.
3.1.5 Option g Diese Option wird ausschließlich in Schleifen verwendet, um in einem String nach allen Vorkommnissen des Suchmusters zu suchen (nicht nur nach dem ersten). Das Besondere dabei ist, dass der Perl-Interpreter nach einem Treffer die Position des Treffers sowie die Position nach dem Treffer in eigenen Variablen speichert. Beim nächsten Durchlauf der Schleife beginnt die Suche nicht am Beginn des zu durchsuchenden Strings, sondern beim nächsten Zeichen nach dem gefundenden Treffer. Beispiel: my $str = "hallo wie geht es? - danke, GEHT so"; # Zähler für die Anzahl der Treffer my $count = 0;
212
3
Pattern Matching
# Aufbau einer Schleife mit der Option "g" while ( $str =~ /geht/g ) { $count++; } print( "Das Wort 'geht' kommt $count-mal vor\n" );
Ausgabe: Das Wort 'geht' kommt 1-mal vor
Vorsicht: Mit der Option g kann man wunderschöne Endlosschleifen bauen. Wird in der Suchschleife eine Ersetzung (siehe weiter unten) durchgeführt oder die zu durchsuchende Variable auf eine andere Weise verändert, dann beginnt die Suche im nächsten Schleifendurchlauf wieder am Anfang des Strings. Dieser Umstand wird häufig übersehen und führt zu Endlosschleifen. Beispiel für eine ungewollte Endlosschleife: my $str = "hallo wie geht es? - danke, GEHT so"; while ( $str =~ /geht/gi ) { # Beschreibung der Ersetzung weiter unten $str =~ s~geht~Geht~; }
Erklärung: Im ersten Schleifendurchlauf wird nach dem Wort »geht« (case-insensititve) gesucht. Bei einem Treffer wird dieses Wort so ersetzt, dass der erste Buchstabe groß ist. Die Folge ist, dass der nächste Schleifendurchlauf aufgrund der Ersetzung wieder am Anfang des Strings beginnt. Die Suche ist case-insensitive, also wird wiederum dasselbe Wort gefunden, der erste Buchstabe in einen Großbuchstaben umgewandelt, und die Suche beginnt erneut am Anfang des Strings usw.
Trefferposition In Verbindung mit der Option g kann die Funktion pos() verwendet werden, um solche Endlosschleifen zu vermeiden oder um die Performance zu erhöhen. Die Funktion kann sowohl lesend als auch schreibend (als »lvalue-Funktion«) verwendet werden. Was bedeutet lvalue-Funktion? Eine lvalue-Funktion kann auch links vom Zuweisungsoperator = stehen. Man kann sozusagen dem Funktionsaufruf einen Wert zuweisen. Das gibt es nur in Perl! Damit die Sache besser verständlich wird: # Normaler Aufruf von pos(). Der Rückgabewert wird # einer Variablen zugewiesen. my $pos = pos( $str );
Matching-Optionen
213
# Aufruf als "lvalue-Funktion". Jetzt wird der # Funktion der Wert von $pos zugewiesen, der # Funktionsaufruf # steht links vom Zuweisungsoperator "=". pos( $str ) = $pos;
Alle Werte in Verbindung mit pos() sind Offsets vom Beginn des Strings und werden bei 0 begonnen (das erste Zeichen hat die Position »0«). Steht die Funktion auf der rechten Seite des Zuweisungsoperators, dann gibt sie die Position innerhalb des zu durchsuchenden Strings zurück, an der eine erneute Suche beginnt. Wird sie als lvalue-Funktion links vom Gleichheitszeichen verwendet, dann wird die Position, an welcher eine erneute Suche beginnt, gezielt gesetzt: # Zeichenpositionen zum besseren Verständnis: # 01234567890123456789012345678901234 my $str = "hallo wie geht es? - danke, geht so"; while ( $str =~ /geht/gi ) { my $pos = pos( $str ); # $pos enthält 14, die nächste Suche beginnt beim # ersten Zeichen nach 'wie geht'. # Beschreibung der Ersetzung weiter unten $str =~ s~geht~Geht~; # Durch die Ersetzung wird die Position für die # nächste Suche auf 0 zurückgesetzt, was zu einer # Endlosschleife führen würde. # Deshalb muss sie explizit gesetzt werden: pos( $str ) = $pos; # Jetzt beginnt die neue Suche beim 15. Zeichen # und das zweite Vorkommen von 'geht' # wird ersetzt. }
3.1.6 Speichern von Treffern Will man bei einer Suche die Treffer weiterverwenden, dann kann man das betreffende Pattern in runde Klammern stellen. In diesem Fall speichert der Interpreter die Treffer in den vordefinierten Dollarvariablen $1, $2 usw. ab. Runde Klammern werden bei Pattern Matching grundsätzlich auch zum Gruppieren mehrerer regulärer Ausdrücke verwendet. Bis auf wenige Ausnahmen, die weiter unten erläutert sind, ist damit gleichzeitig ein Speichern von Treffern verbunden.
214
3
Pattern Matching
Als Beispiel wollen wir feststellen, wie häufig die Wörter »das« und »des« vorkommen: use strict; # Hash für die gefundenen Wörter my %words = (); # Einlesen von der Tastatur while ( defined( my $line = <STDIN> ) ) { # Zeilenende Zeichen entfernen chomp( $line ); while ( $line =~ /(d.s)/g ) { # Wir haben etwas mit 3 Zeichen gefunden. # Perl speichert den Treffer in der # Variable "$1" für uns ab. my $match = $1; # Abspeichern des Treffers inkl. Häufigkeit unless ( exists( $words{ $match } ) ) { # Der Treffer wurde zum ersten Mal gefunden. # Hash-Element initial mit der Häufigkeit # "1" anlegen $words{ $match } = 1; # Nächsten Schleifendurchlauf starten; # Damit spart man sich unten ein "else". next; } # Der Treffer wurde bereits vorher einmal # gefunden. Häufigkeit um 1 erhöhen $words{ $match }++; } # Hinweis: Außerhalb der while-Schleife ist "$1" # nicht mehr definiert! } # Ausgeben der Treffer mit ihrer Häufigkeit foreach my $word ( sort( keys( %words ) ) ) { my $cnt = $words{ $word }; print( "Das Wort $word kommt $cnt mal vor\n" ); }
Unser Beispiel krankt natürlich, weil nicht nur die Wörter »das« und »des« gefunden werden, sondern auch Wörter, in denen die Suchbegriffe als Wortteil vorkommen. Dieses Problem können wir aber erst später elegant lösen, wenn ich Ihnen die regulären Ausdrücke näher gebracht habe.
Matching-Optionen
215
Hier eine Beispieleingabe (und die zugehörige Ausgabe): das ist das haus vom nikolaus, das sieht gut aus des weiteren ist das haus des nikolaus alt jedes teil des Hauses ist mindestens 2000 Jahre alt ^Z Das Wort das kommt 4 mal vor Das Wort des kommt 5 mal vor
Wie wir sehen, lügt das Programm beim Wort »des«, denn als solches kommt es tatsächlich nur 3-mal vor. Im Wort »jedes« sowie in »mindestens« ist es aber ebenfalls als Wortteil enthalten. Sehen wir uns noch ein Beispiel an, bei dem mehrere Klammern zum Speichern verwendet werden: ... my ( $m1, $m2, $m3 ); if ( $str =~ /(das)( )(Haus)/i ) { ( $m1, $m2, $m3 ) = ( $1, $2, $3 ); ... } ...
Immer dann, wenn im String die Zeichenketten »das Haus« oder »Das Haus« (oder alle Kombinationen mit Groß- und Kleinschreibung wegen der Option i) vorkommen, speichert der Interpreter die Treffer in den Variablen $1, $2 und $3 ab. Wir sehen aus dem Beispiel, dass ich die Werte der Variablen $1 usw. über eine Listenzuweisung an eigene Variablen übergebe, die außerhalb der if-Abfrage definiert sind. Das ist notwendig, wenn die Treffer nach der if-Abfrage benötigt werden. Die Variablen $1 usw. sind aber nur innerhalb der if-Abfrage definiert. Der Perl-Interpreter geht bei der Verarbeitung von Klammernpärchen des Patterns streng von links nach rechts vor. Der Treffer, der zum ersten in runden Klammern stehenden Ausdruck gehört, wird in $1 abgespeichert, dann folgt der nächste Ausdruck in runden Klammern, der in $2 abgespeichert wird, usw. Diese Tatsache will ich Ihnen ganz deutlich einschärfen: Was in $1 wandert, gibt das erste Klammernpärchen des Patterns von links vor, nicht der Treffer, der zuerst im String gefunden wird. Hierzu ein Beispiel: /((der)(jenige))/ #| | #|| + Beginn des dritten Klammernpärchens #|+----- Beginn des zweiten Klammernpärchens #+------ Beginn des ersten Klammernpärchens
216
3
Pattern Matching
Wie wir sehen, besteht das Pattern aus drei Klammernpärchen. Sowohl »(der)« als auch »(jenige)« ist von einem weiteren Klammernpärchen umgeben. Weil dieses als erstes Pärchen von links auftritt, wandert dessen Trefferinhalt in $1, während in $2 der Treffer für »der« und in $3 der Treffer für »jenige« kommt. Wenn wir zum Beispiel im String »derjenige welcher« nach unserem Pattern suchen, dann enthält $1 den Wert »derjenige«, weil dies die Aneinanderreihung von $2 und $3 ist.
ODER-Verknüpfung von Patterns Man kann mehrere Patterns mit »|« ODER-verknüpfen, zum Beispiel: /(der)|(die)|(das)/
Auf deutsch heißt das Pattern: Suche bitte nach »der«, »die« ODER »das« und speichere den gefundenen Treffer. Die Frage ist nur, wo ein Treffer für »die« abgespeichert wird? Obwohl es durch die Erklärungen weiter oben klar sein müsste, hier noch einmal die Regel: Die Treffer werden für die Klammernpärchen streng von links nach rechts in den Variablen $1 etc. gespeichert, und zwar in der Reihenfolge, in welcher der Interpreter die Pärchen im Sourcecode findet. Das bedeutet, dass ein Treffer für »die« nicht in $1 landet, sondern in $2. Sowohl $1 als auch $3 sind für diesen Fall nicht definiert. Sehen wir uns das einmal in einem Beispiel an: my $str = "die oder der oder das kommt wo hinein?"; if ( $str =~ /(der)|(die)|(das)/ ) { print( "\$1 = '$1'\n" ); print( "\$2 = '$2'\n" ); print( "\$3 = '$3'\n" ); }
Der Code gibt Folgendes aus: Use of uninitialized value in concatenation (.) or string at - line 3. $1 = '' $2 = 'die' Use of uninitialized value in concatenation (.) or string at - line 5. $3 = ''
Hätten wir das Wort »das« an den Anfang gesetzt, wären $1 und $2 nicht definiert. Dieses manchmal etwas unübersichtliche Verhalten kann man durch Setzen eines weiteren Klammernpärchens vermeiden: my $str = "die oder der oder das kommt wo hinein?"; if ( $str =~ /((der)|(die)|(das))/ ) { print( "\$1 = '$1'\n" ); }
Matching-Optionen
217
Diesmal wird ausgegeben: $1 = 'die'
Egal, welches Wort gefunden worden ist, der Treffer wandert in jedem Fall in die Variable $1. Hinweis: Diesmal haben wir insgesamt vier »$«-Variablen, von denen nur $1 und $3 definiert sind.
Rückwärtsreferenzen Zusätzlich zu den Variablen $1, $2 usw. speichert der Perl-Interpreter gefundene Treffer in den speziellen Rückwärtsreferenzen »\1«, »\2« usw. ab (im Englischen werden Rückwärtsreferenzen auch »backward references« genannt). Diese Rückwärtsreferenzen kann und sollte man nur im Suchmuster selbst verwenden (die Variablen $1, $2 usw. können nicht im Suchmuster verwendet werden). Beispiel für die Verwendung von Rückwärtsreferenzen: my $str = "Ooh Mannomann ist Matching toll"; # Wir suchen im String alle doppelt vorkommenden Zeichen while ( $str =~ /(.)\1/g ) { print( "habe doppeltes '$1' gefunden\n" ); }
Erläuterung des Patterns: »Suche nach irgendeinem beliebigen Zeichen und merke dir dieses Zeichen in »\1«. Anschließend schaue nach, ob dasselbe Zeichen gleich anschließend noch einmal folgt. Wenn ja, erzeuge einen Match«. Während Rückwärtsreferenzen nur im Pattern selbst einen Sinn ergeben (und damit auch nur dort verwendet werden sollten), kann man die »$«-Variablen nicht im Pattern verwenden, da sie dort nicht definiert sind. Der Beispielcode macht folgende Ausgaben: habe doppeltes 'n' gefunden habe doppeltes 'n' gefunden habe doppeltes 'l' gefunden
Warum wurde das »Oo« am Beginn des Strings nicht gefunden? Antwort: Ohne die Option i ist die Suche case-sensitive. Es wird also zunächst ein Treffer für O gefunden und überprüft, ob das nächste Zeichen ebenfalls ein O ist. Es folgt aber ein kleines o, deshalb ergibt sich kein Treffer.
218
3
Pattern Matching
Umgehung der Dollarvariablen Man kann die gefundenen Treffer auch direkt in eigenen Variablen abspeichern. Dies ist dann nötig, wenn in einer Suchabfrage noch einmal eine Suche ausgeführt wird, denn bei jeder neuen Suche werden die speziellen Variablen $1, $2 usw. überschrieben. Die dafür benötigte Syntax sehen wir uns am besten anhand eines Beispiels an: # # # # # # #
Einlesen von Datensätzen aus einer Datei: Jeder einzelne Datensatz steht in einer Zeile. Die Felder des Datensatzes sind durch ein Tab-Zeichen getrennt. Die ersten beiden Felder enthalten jeweils einzelne Zeichen. Beispiel-Datensatz: "a\t1"
# Datei öffnen ... while ( defined( my $line = $fh->getline() ) ) { # Zeilenende-Zeichen entfernen chomp( $line ); # Wir suchen nach einem beliebigen Zeichen, # gefolgt von einem TAB und wiederum # einem beliebigen Zeichen. # Wenn ein Match erzeugt wird, stehen die # beiden Treffer in $1 und $2. # Diese werden als Liste über den # Zuweisungsoperator "=" an die linke Liste # bestehend aus $f1 und $f2 übergeben. my ( $f1, $f2 ) = $line =~ /^(.)\t(.)/; # Die Listenzuweisung erfolgt auch, wenn # gar kein Treffer erzielt worden ist. In diesem # Fall wird eine leere Liste kopiert, und sowohl # $f1 als auch $f2 sind "undef". Deshalb müssen # wir diesen Fall abfangen. unless ( $f1 and $f2 ) { # Die Suche lieferte keinen Treffer. } # Die obige Zeilen bewirken dasselbe wie: my ( $f1, $f2 ); if ( $line =~ /^(.)\t(.)/ ) { $f1 = $1; $f2 = $2; } }
Matching-Optionen
219
Links vor der Variable $line steht ein Gleichheitszeichen, rechts davon jedoch der Binding-Operator. Das Ergebnis der Suche ist in diesem Fall kein logischer Wert wie TRUE oder FALSE, sondern eine Liste, bestehend aus den Variablen $1, $2 usw. Diese Liste wird mit dem Gleichheitszeichen an die links stehende Liste der Variablen zugewiesen. Die Variablen werden auch dann versorgt, wenn gar kein Treffer erzielt worden ist. In diesem Fall haben beide Variablen den Pseudowert undef. Deshalb muss mit einer anschließenden unless-Abfrage überprüft werden, ob ein Treffer erzielt wurde oder nicht.
3.1.7 Die Positionsvariablen @- und @+ Wir haben bisher nur die Funktion pos() kennen gelernt, mit deren Hilfe man in Suchschleifen (Option g) die Position im zu durchsuchenden String lesen oder setzen kann, bei welcher der nächste Suchlauf beginnt. Speziell dann, wenn man mehrere Klammernpärchen im Pattern hat, kann man die beiden Variablen @- und @+ verwenden, um alle Offsets aller Treffer im String zu ermitteln. Sehen wir uns zunächst ein ganz einfaches Beispiel an: # Suche nach dem konstanten String "das" in $str my $str = "Das Wetter ist heut so gut, das ist toll"; # 0123456789012345678901234567890123456789 # Zur besseren Lesbarkeit der Offsets aller Zeichen # im String habe ich unter dem String selbst eine # Kommentarzeile mit allen Offsets $str =~ /das/; # Ausgabe der beiden Array-Variablen print( "\@- enthält ", scalar( @- ), " Elemente\n" ); for ( my $i = 0; $i <= $#-; $i++ ) { print( "\$-[ $i ] = $-[ $i ]\n" ); } print( "\@+ enthält ", scalar( @+ ), " Elemente\n" ); for ( my $i = 0; $i <= $#+; $i++ ) { print( "\$+[ $i ] = $+[ $i ]\n" ); }
Der Code macht folgende Ausgaben: @- enthält 1 Elemente $-[ 0 ] = 28 @+ enthält 1 Elemente $+[ 0 ] = 31
220
3
Pattern Matching
Wenn wir uns nun die Offsets genauer ansehen, wird klar, dass im ersten Element von @- der Offset des ersten Zeichens für den Treffer steht, während das erste Element von @+ den Offset des ersten Zeichens nach dem Treffer enthält. Nun wollen wir den Suchbegriff in runde Klammern setzen und das Ganze noch einmal ausführen. Das Pattern sieht jetzt so aus: $str =~ /(das)/;
Ausgegeben wird nun: @- enthält 2 Elemente $-[ 0 ] = 28 $-[ 1 ] = 28 @+ enthält 2 Elemente $+[ 0 ] = 31 $+[ 1 ] = 31
Da wir daraus nicht so recht schlau werden, lassen Sie mich noch einen weiteren Versuch machen, diesmal mit 3 runden Klammern: ... my $str = "Das Wetter ist heut so gut, das ist toll"; # 0123456789012345678901234567890123456789 $str =~ /(das)(.+)(toll)/i; ...
Nun sehen wir uns das Ergebnis an: @- enthält 4 $-[ 0 ] = 0 $-[ 1 ] = 0 $-[ 2 ] = 3 $-[ 3 ] = 36 @+ enthält 4 $+[ 0 ] = 40 $+[ 1 ] = 3 $+[ 2 ] = 36 $+[ 3 ] = 40
Elemente # Beginn # Beginn # Beginn # Beginn Elemente # Offset # Offset # Offset # Offset
des von von von
gesamten Treffers $1 $2 $3
nach nach nach nach
gesamtem Treffer $1 $2 $3
Ich glaube, jetzt können wir die Verhältnisse klären: In @- stehen die Offsets der ersten Zeichen von Treffern, während @+ die Offsets der jeweils auf die Treffer folgenden Zeichen enthält. Das erste Element beider Variablen nimmt eine Sonderstellung ein. Es bezieht sich auf den Offset für das gesamte Pattern. Wenn keine runden Klammern für die Guppierung verwendet werden, existiert in beiden Variablen nur dieses erste Element. Mit jedem
Reguläre Ausdrücke
221
Klammernpärchen kommt ein Array-Element hinzu. Auch hier gilt: Die Reihenfolge der Array-Elemente entspricht der Reihenfolge der Klammernpärchen von links nach rechts im Sourcecode. Nun kommen wir zum eigentlich interessanten Teil von Pattern Matching:
3.2 Reguläre Ausdrücke Bisher haben wir im Suchmuster bis auf wenige Ausnahmen nur konstante Zeichenketten verwendet, um das Prinzip von Pattern Matching zu zeigen. Aber erst mit regulären Ausdrücken wird Pattern Matching zu einem der leistungsfähigsten Instrumente einer Programmiersprache. Wohl jeder hat schon einmal einen regulären Ausdruck verwendet, ohne sich Gedanken darüber zu machen: C:\temp>dir *.txt
Diese DOS-Anweisung gibt alle Dateien im aktuellen Verzeichnis mit der Endung .txt aus. Der springende Punkt ist das Sternchen (*), das vor der Datei-Endung angegeben wird. Dies ist ein regulärer Ausdruck und besagt, dass alle Dateinamen, die irgendein Zeichen in beliebiger Anzahl vor dem String ».txt« besitzen, einen Treffer erzeugen und somit das Suchkriterium erfüllen. Auch in UNIX-Shells werden reguläre Ausdrücke verwenden, z.B.: $ ls [a-z]*
Das UNIX-Kommando ls in diesem Beispiel gibt alle Dateien aus, deren Name mit einem Kleinbuchstaben beginnt. In regulären Ausdrücken haben einige Zeichen eine besondere Bedeutung, die man Metazeichen nennt. Auf diese wollen wir im nächsten Abschnitt näher eingehen.
3.2.1 Metazeichen Zeichen, die in regulären Ausdrücken eine besondere Bedeutung haben, nennt man auch Metazeichen. Will man ein solches Metazeichen literal verwenden (also nur als normales Zeichen ohne Sonderbedeutung), dann muss es durch Voranstellen eines Backslashes »\« entwertet werden. Alternativ kann vor Zeichen, die eine besondere Bedeutung haben, durch Voranstellen der Escapesequenz »\Q« die Interpretation von Metazeichen ausgeschaltet werden (durch die Sequenz »\E« wird sie wieder aktiviert). Beide Varianten wurden schon im vorangegangenen Abschnitt kurz gezeigt. Wir werden auch in den Beispielen der regulären Ausdrücke immer wieder darauf zurückkommen.
222
3
Pattern Matching
Das Metazeichen ^ Mit diesem Zeichen wird der Beginn des zu durchsuchenden Strings gekennzeichnet: if ( $str =~ /^a/ ) { # Der Inhalt der Variable $str beginnt # mit einem kleinen "a" } else { # Der Inhalt der Variable $str beginnt mit einem # anderen Zeichen als "a" }
Hinweis: Bei Zeichenklassen in eckigen Klammern hat das Zeichen »^« eine andere, besondere Bedeutung, wie wir noch sehen werden.
Das Metazeichen $ Mit diesem Zeichen wird das Ende des zu durchsuchenden Strings gekennzeichnet (dies gilt nur, wenn das Metazeichen am Ende des Such-Patterns steht, ansonsten werden skalare Variablen damit gekennzeichnet): if ( $str =~ /a$/ ) { # Der Inhalt der Variable $str endet # mit einem kleinen "a" } else { # Der Inhalt der Variable $str endet mit einem # anderen Zeichen als "a" } # Verwendung einer Variable, die ebenfalls mit # einem $ gekennzeichnet wird my $pattern = "\\."; # Der Backslash muss entwertet werden (doppelte Quotes). # Bei einfachen Quotes ist das nicht notwendig: # my $pattern = '\.'; if ( $str =~ /$pattern$/ ) # Es wird ein Treffer gefunden, wenn der String $str mit # einem Punkt endet. # Das erste Dollarzeichen ist das Typkennzeichen der # skalaren Variable "$pattern", das letzte Dollarzeichen # hingegen ist das Metazeichen.
Das Metazeichen . (Punkt) Dieses Metazeichen kennzeichnet ein beliebiges Zeichen. Hinweis: Bei Strings, die aus mehreren Zeilen bestehen, erzeugt das Metazeichen ».« nur dann einen Match für das Zeilenende-Zeichen, wenn die Option s angegeben ist.
Reguläre Ausdrücke
223
Beispiele: my $str = "abc\ndea\nchi\nsdsddf"; while ( $str =~ /(a.c)/g ) { print( "Treffer '$1' gefunden\n" ); # Es wird die Zeichenkette "abc" gefunden. # "a\nc" dagegen erzeugt keinen Match. } # Dasselbe mit der Option s (auch \n wird mit dem # Metazeichen . gefunden) while ( $str =~ /(a.c)/gs ) { print( "Treffer '$1' gefunden\n" ); # Es werden die Zeichenkette "abc" und die # Zeichenkette "a\nc" gefunden. }
Wenn das Metazeichen ».« in Verbindung mit den Quantifiern »+« und »*« verwendet wird, ist Vorsicht geboten. Siehe hierzu die Anmerkungen bei den Beschreibungen der Quantifier weiter unten. Dort werden Sie auch erfahren, was ein Quantifier ist. Will man einen literalen Punkt finden, dann muss er im Such-Pattern mit einem Escapezeichen entwertet werden: if ( $str =~ /aber hallo\.$/ ) { # Findet nur Zeichenketten, die mit dem Punkt enden # ("aber hallo."). # Das Pattern /aber hallo./ # würde auch finden: "aber hallo " # oder "aber halloo" etc., weil hier der Punkt nicht # durch einen Backslash entwertet und somit # ein Metazeichen ist. }
Das Metazeichen | (Bar) Dieses Metazeichen wird verwendet, um mehrere verschiedene Such-Pattern mit ODER zu verknüpfen, d.h., es wird versucht, eines der angegebenen Such-Pattern zu finden. Wir haben es weiter oben bereits kurz kennen gelernt. Beispiel: if ( $str =~ /^(a)|^(A)|^(Ä)|^(ä)/ ) { # Es wird ein Treffer erzielt, wenn $str mit einem # der angegebenen Zeichen beginnt. # Vorsicht: Fängt $str mit dem Zeichen 'a' an, dann # steht der Treffer in $1. # Fängt $str mit dem Zeichen 'A' an, dann
224
3 # # # # #
steht Fängt steht Fängt steht
Pattern Matching
der Treffer in $2. $str mit dem Zeichen 'Ä' an, dann der Treffer in $3. $str mit dem Zeichen 'ä' an, dann der Treffer in $4.
# Das folgende print()-Statement liefert eine # Fehlermeldung, wenn $str mit einem der folgenden # Zeichen beginnt: 'A', 'Ä', 'ä' print( "Der String '$str' beginnt mit '$1'\n" ); # Richtig wäre: print( "Der String '$str' beginnt mit '", $1 ? $1 : ( $2 ? $2 : ( $3 ? $3 : $4 ) ), "'\n" ); } # Besser: if ( $str =~ /^(a|A|Ä|ä)/ ) { print( "Der String '$str' beginnt mit '$1'\n" ); }
Wenn der String, der in der Variable $str gespeichert ist, mit einem der im Such-Pattern angegebenen Zeichen beginnt, speichert der Interpreter den Treffer entweder in der Variable $1 ab (falls $str mit einem »a« beginnt), in der Variable $2 (wenn $str mit einem großen »A« beginnt), in der Variable $3 (wenn $str mit einem »Ä« beginnt) oder in $4 (wenn $str mit einem »ä« beginnt). Wenn man den Treffer in jedem Fall – egal, welches Pattern gefunden wird – in der Variable $1 abgespeichert haben möchte, muss man alle Suchausdrücke in ein einziges Klammernpärchen stellen: if ( $str =~ /^(a|A|Ä|ä)/ ) { print( "Der String '$str' beginnt mit '$1'\n" ); }
Man kann aber auch um die einzelnen Klammern noch eine umfassende Klammer setzen: if ( $str =~ /^((a)|(A)|(Ä)|(ä))/ ) { print( "Der String '$str' beginnt mit '$1'\n" ); }
In diesem Fall enthält $1 in jedem Fall einen Treffer (entweder »a«, »A«, »Ä« oder »ä«). $2 enthält ein »a«, wenn der String mit einem »a« beginnt, $3 enthält ein »A«, wenn der
String mit »A« beginnt, $4 enthält ein »Ä«, wenn der String mit einem »Ä« beginnt, und $5 enthält ein »ä«, wenn der String mit einem »ä« beginnt.
Reguläre Ausdrücke
225
Das Metazeichen () Wenn runde Klammern angegeben werden, dann wird ein Treffer für das Pattern, welches in den Klammern steht, in den Variablen $1, $2 etc. gespeichert. Wir haben diesen Mechanismus ja bereits kennen gelernt. Der Perl-Interpreter geht hier streng von links nach rechts vor, und zwar genau in der Reihenfolge, in der die Pärchen von runden Klammern im Suchmuster stehen, nicht in der Reihenfolge, in der die Ausdrücke gefunden werden. Dies gilt speziell dann, wenn man mit dem Metazeichen »|« eine ODER-Verknüpfung der einzelnen Suchmuster durchführt. Zusätzlich zu den Dollarvariablen $1, $2 etc. sind die Treffer auch über die Rückwärtsreferenzen »\1«, »\2« etc. ansprechbar, jedoch mit einem Unterschied: Während $1, $2 etc. nicht im Suchmuster verwendet werden können, dürfen »\1«, »\2« etc. auch dort stehen. Die Rückwärtsreferenzen wiederum können nur im Pattern verwendet werden. Beispiele für das Metazeichen »()« und die Verwendung von Rückwärtsreferenzen finden Sie im Abschnitt »Speichern von Treffern«.
Das Metazeichen [] (Zeichenklassen) Eckige Klammern können für das Bilden von Zeichenklassen verwendet werden. Zeichenklassen dienen dazu, einen Bereich oder eine Gruppe von Zeichen festzulegen, die zu einem Treffer führen können. Reiht man mehrere Zeichen in der Zeichenklasse aneinander (ohne Zwischenraum!), dann stellt dies eine Liste von Zeichen dar. Jedes einzelne Zeichen erzielt einen Match (ODER-Verknüpfung): if ( $str =~ m~^[Aa]~ ) { # Der Inhalt von $str beginnt mit einem grossen oder # kleinen "A" } if ( $str =~ /[aeiou]$/ ) { # Der Inhalt von $str endet mit einem Vokal } # Aber: if ( $str =~ /[a e i o u]/ ) # Das war wohl nicht im Sinne des Erfinders. Blanks # im String $str führen ebenfalls zu einem Match. # Merke: in regulären Ausdrücken keine Blanks # für optisch bessere Lesbarkeit verwenden, sondern # nur dann, wenn danach gesucht werden soll.
226
3
Pattern Matching
Für Buchstaben und Ziffern kann man auch einen Bereich angeben, der durch den Bindestrich gekennzeichnet wird: if ( $str =~ /^[A-Z]/ )
Die Zeichenklasse »[A-Z]« erzeugt für alle Großbuchstaben einen Treffer. In Verbindung mit dem Metazeichen »^« ist die Matching-Operation nur dann TRUE, wenn der String $str mit einem Großbuchstaben beginnt. Die Angabe des Bereichs mit dem Bindestrich ist einfach nur eine Abkürzung. Ohne Bindestrich hätten wir eine Liste aller Großbuchstaben schreiben müssen (»ABCDEFGHIJKLMNOPQRSTUVWXYZ«). Die deutschen Umlaute sind, wie wir an der ausführlichen Liste von »A-Z« sehen, nicht enthalten, auch dann nicht, wenn man die Direktive use locale; benutzt, die wir bereits früher kennen gelernt haben. Allerdings sind deutsche Umlaute bei Aktivierung der deutschen Sprachumgebung mit use locale; Bestandteil der speziellen Zeichenklasse »\w«, die weiter unten erklärt wird. Die besondere Bedeutung des Bindestrichs in Zeichenklassen kann durch Voranstellen eines Backslashs abgeschaltet werden: if ( $str =~ /^[A\-Z]/ )
Hier wird nur ein Match erzeugt, wenn der String $str mit einem großen »A«, »Z« oder mit einem Bindestrich beginnt, weil wir die besondere Bedeutung des Bindestrichs durch Voranstellen eines Backslash entwertet haben. Steht ein Bindestrich am Ende einer Zeichenklasse, verliert er ebenso seine besondere Bedeutung: if ( $str =~ /^[AZ-]/ )
In diesem Fall muss der Bindestrich nicht durch einen Backslash entwertet werden. Man kann auch Teilbereiche von Buchstaben oder Ziffern angeben: if ( $str =~ /^[1-9][0-9]/ )
In diesem Beispiel enthält die erste Zeichenklasse nicht die Ziffer »0«. Es wird also nur ein Match erzeugt, wenn der String $str mit zwei Ziffern beginnt, wobei die erste Ziffer nicht die »0« sein darf. Mit dem Bindestrich kann man nur aufsteigende Bereiche angeben: if ( $str =~ /^[z-a]/ )
Reguläre Ausdrücke
227
ergibt eine Fehlermeldung des Interpreters, weil wir einen absteigenden Bereich angegeben haben. Es ist auch möglich, mehrere Bereiche in Zeichenklassen zu definieren: if ( $str =~ /^[A-Za-z0-9]/ )
Hier wird dann ein Match erzeugt, wenn der String $str mit einem Groß- oder Kleinbuchstaben oder mit einer Ziffer beginnt. Natürlich lässt sich das Pattern mit der Option »i« vereinfachen: if ( $str =~ /^[a-z0-9]/i )
Patterns und auch Zeichenklassen können unter anderem Steuerzeichen enthalten, da ein Pattern in Perl wie ein String in doppelten Anführungszeichen behandelt wird : if ( $str =~ /[ \t\r\n]/ )
Die angegebene Zeichenklasse enthält alle White Space-Zeichen, das sind Blanks, TABs sowie die Zeilenende-Zeichen. Wie wir später noch sehen werden, gibt es dafür (und für andere spezielle Zeichenklassen) eine Abkürzung. Zusätzlich zu den hier gezeigten Steuerzeichen können weitere spezielle Zeichen mit einem vorangestellten Backslash verwendet werden (zum Beispiel in Oktal- oder Hexadezimalschreibweise). Detailinformationen erhalten Sie mit dem Kommando perldoc perlre
Während der Bindestrich »-« und das Hütchen »^« in Zeichenklassen eine besondere Bedeutung haben, ist das bei den anderen Metazeichen nicht der Fall (so hat z.B. ein Punkt innerhalb einer Zeichenklasse keine besondere Bedeutung). Will man die besondere Bedeutung von »-« und »^« innerhalb von Zeichenklassen außer Kraft setzen, muss man vor die Zeichen einen Backslash stellen. Die Steuerzeichen »\n«, »\t« und »\r« können ebenfalls in Zeichenklassen angegeben werden und verlieren dabei nicht ihre Bedeutung als Steuerzeichen.
Negierte Zeichenklassen Wird als erstes Zeichen nach der öffnenden eckigen Klammer das Metazeichen »^« angegeben, dann wird die Zeichenklasse negiert, es führen also alle Zeichen zu einem Treffer, die nicht der Zeichenklasse angehören: if ( $str =~ /[^0-9]/ )
Die Abfrage liefert TRUE, wenn keine Ziffer in $str vorkommt.
228
3
Pattern Matching
Besondere Zeichenklassen Für häufig verwendete Zeichenklassen gibt es Kurzformen, die durch einen Backslash, dem ein Zeichen folgt, gekennzeichnet werden. Damit kann man sich eine Menge Schreibarbeit sparen. Normalerweise wird der Buchstabe für die Kennzeichnung der besonderen Zeichenklasse nach dem Backslash kleingeschrieben. Ist er als Großbuchstabe vorhanden, dann wird die Bedeutung umgekehrt: \s, \S Die Zeichenklasse »\s« kennzeichnet »white space«, das sind alle Leerzeichen wie Blank, Tabulator und Zeilenende-Zeichen. »\s« ist die Abkürzung von: [ \t\r\n]
Die Zeichenklasse »\S« bedeutet das genaue Gegenteil, also alle Zeichen, die nicht »white space« sind. \d, \D Die Zeichenklasse »\d« kennzeichnet alle Ziffern. »\d« ist die Abkürzung von: [0123456789]
Die Zeichenklasse »\D« bedeutet das genaue Gegenteil, also alle Zeichen außer Ziffern. \w, \W Die Zeichenklasse »\w« kennzeichnet alle Buchstaben, Ziffern und den Unterstrich. Deutsche Umlaute gehören normalerweise nicht dazu, es sei denn, man benutzt die Direktive use locale;. »\w« ist die Abkürzung von: [ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_]
Mit der Direktive use locale; kommen noch die Zeichen [ÄäÖöÜüß] hinzu. Die Zeichenklasse »\W« bedeutet das genaue Gegenteil, also alle Zeichen, die weder Buchstaben noch Ziffern oder Unterstriche sind. Für alle abgekürzten Zeichenklassen wie »\w« gibt es auch eine andere Schreibweise, die für »\w« zum Beispiel [:alphanum:] heißt. Da deren Gebrauch aber wesentlich umständlicher ist, sei hier nur auf die entsprechende Online-Dokumentation verwiesen. Sie lautet perldoc perlre. Ich persönlich glaube, dass der Ausdruck »\w« wesentlich einfacher ist als [[:alphanum:]].
Reguläre Ausdrücke
229
Übersicht der wichtigsten vordefinierten Zeichenklassen: Zeichenklasse
Bedeutung
\w
Zeichenklasse für alle alphanumerischen Zeichen (Buchstaben, Ziffern und Unterstrich »_«. Beachte: Deutsche Umlaute sind nur Bestandteil der Zeichenklasse, wenn die Direktive use locale; verwendet wird.
\W
Negation von \w
\d
Zeichenklasse für alle Ziffern
\D
Negation von \d
\s
Zeichenklasse für alle »White Space«-Zeichen (Blank, Tabulator, ZeilenendeZeichen \n und \r
\S
Negation von \s
\b
Kennzeichen für »word boundary«. Darunter versteht man eine Wortgrenze, d.h., auf ein Zeichen der Klasse \w folgt ein Zeichen der Klasse \W oder umgekehrt. Hinweis: Innerhalb einer Zeichenklasse, die durch die Metazeichen [] definiert wird, hat \b eine andere Bedeutung und steht hier für das Backspace-Zeichen. Negation von \b
\B
Quantifier Quantifier geben an, wie oft ein Ausdruck in einem Suchmuster vorkommen darf bzw. muss, um einen Treffer zu erzeugen. Der Quantifier steht direkt nach dem Teil des Suchmusters, auf den er sich bezieht. Quantifier geben den regulären Ausdrücken erst den »letzten Schliff«. Vorsicht ist geboten, wenn nach dem Metazeichen ».« ein Quantifier angegeben wird. Siehe hierzu auch unter »?« (Minimal-Matching, nicht zu verwechseln mit dem Quantifier »?«). Der Quantifier + Dieser Quantifier gibt an, dass der vor ihm stehende Suchmuster-Ausdruck mindestens einmal und beliebig oft vorkommen kann. Beispiele für den »+«-Quantifier: Der zu durchsuchende String soll mit einem »a« beginnen. Anschließend können beliebig viele »a« folgen: if ( $str =~ /^a+/ ) {
»abc« erfüllt die Bedingung, »aaaaaaaaaaaaaaaaaaaaaaaaaabc« ebenso. Die einzige Bedingung ist, dass der String mit mindestens einem »a« beginnt.
230
3
Pattern Matching
Der Quantifier hat, wie gesagt, nur auf den direkt davor stehenden regulären Ausdruck eine Auswirkung. Sehen wir uns kurz ein Beispiel dafür an: if ( $str =~ /^tol+/ )
Die Matching-Operation liefert TRUE nicht etwa, wenn $str mit dem String »toltol« beginnt, sondern z.B. bei »toll« oder »tollllll«. Der Quantifier »+« bezieht sich nur auf das davor stehende Zeichen »l«, nicht auf das Wort »tol«. Will man einen Quantifier auf mehrere Zeichen wirken lassen, dann muss man sie gruppieren: if ( $str =~ /^(tol)+/ )
Mit dieser Gruppierung durch Setzen von runden Klammern um diejenigen Zeichen, für die der Quantifier gelten soll, werden gefundene Treffer in $str natürlich auch in den Variablen $1 etc. abgespeichert. Will man das verhindern (zum Beispiel aus Performancegründen), dann muss man die Gruppierung der Zeichen wie folgt durchführen: if ( $str =~ /^(?:tol)+/ )
Der Ausdruck ?: teilt dem Interpreter mit: »Gruppiere die folgenden Zeichen zu einer Einheit, bilde aber bitte keine Rückwärtsreferenzen und speichere die Treffer nicht in den Dollarvariablen ab.« Die Erweiterung ?: wird weiter unten noch erläutert. Ein Zahlenbeispiel für den Quantifier »+«: Der zu durchsuchende String soll eine ganze Zahl sein: if ( $str =~ /^\d+$/ )
Das Pattern liest sich wie folgt: Der String muss mit einer Ziffer beginnen. Es können beliebig viele weitere Ziffern folgen (aber keine anderen Zeichen). Nach den Ziffern muss der String zu Ende sein. Aus Performancegründen ist es unter Umständen einfacher, die Sache umzudrehen. Wenn der String irgendein Zeichen enthält, das keine Ziffer ist, haben wir eine ungültige Zahl vor uns: if ( $str =~ /\D/ )
Wenn diese Abfrage den Wert TRUE liefert, dann enthält $str keine gültige ganze Zahl. Nun wollen wir feststellen, ob eine Zeile am Ende Leerzeichen enthält (Blanks, TABs oder das Windows-Zeichen »\r«): if ( $str =~ /[ \t\r]+\n$/ )
»Zeile \n« erzeugt einen Match, ebenso »Zeile\t\t\n« oder »Zeile \r\n«.
Reguläre Ausdrücke
231
Hier könnte man verleitet werden, die abkürzende Schreibweise für »white space« zu verwenden (»\s«), zum Beispiel so: if ( $str =~ /\s+\n$/ )
Mit diesem Pattern stolpert man aber in eine Falle, denn das Zeilenende-Zeichen »\n« gehört ebenfalls zur Zeichenklasse »\s«. Eine Leerzeile würde durch dieses Pattern ebenfalls einen Match erzeugen. Wir wollen aber nur Zeilen finden, die überschüssige Leerzeichen am Ende besitzen (was bei vielen Editoren schnell der Fall ist). Wenn wir nun mit unserem Pattern die gefundenen Zeilen ersetzen, dann löschen wir damit Leerzeilen: # Originalstring hi there wie geht es? # wird zu hi there wie geht es?
Der Quantifier * Dieser Quantifier gibt an, dass der vor ihm stehende reguläre Ausdruck beliebig oft oder gar nicht vorkommen kann. Er ist dem Quantifier »+« sehr ähnlich, nur erzeugt er auch dann einen Match, wenn der davor stehende Ausdruck gar nicht vorkommt. Sehen wir uns ein Beispiel an, in dem auf eine ganze positive Zahl geprüft wird, die größer als »0« sein soll: if ( $str =~ /^[1-9]\d*$/ )
Das Pattern auf deutsch: $str muss mit einer Ziffer beginnen, die größer als 0 ist. Anschließend können beliebig viele weitere Ziffern folgen (einschließlich der 0). Der String darf nur Ziffern enthalten. « Hätten wir den Quantifier »+« verwendet: if ( $str =~ /^[1-9]\d+$/ )
dann würden die ganzen Zahlen von 1 bis 9 keinen Match liefern, da unser Pattern verlangt, dass der String mindestens aus zwei Zeichen bestehen muss. »[1-9]« heißt: genau ein Zeichen. »\d+« besagt: mindestens ein Zeichen. Und noch ein Fallstrick: Wenn man die Metazeichen »^« und »$« für Beginn und Ende des Strings weglässt, dann hat man einen Fehler begangen, weil in dem Fall auch dann ein Treffer erzielt wird, wenn die positive ganze Zahl in weitere Zeichen eingebettet ist: if ( $str =~ /[1-9]\d*/ )
232
3
Pattern Matching
Dieses Pattern besagt nur: Suche nach einer ganzen positiven Zahl in $str. So würde zum Beispiel auch der String »abc12 bla« einen Treffer erzielen, weil darin die Zahl »12« vorkommt. Also: Sowohl »^« als auch »$« müssen im Pattern angegeben sein. Der Quantifier ? Dieser Quantifier gibt an, dass der vor ihm stehende reguläre Ausdruck entweder gar nicht oder genau einmal vorkommen kann. Das Metazeichen »?« hat zusätzlich eine andere besondere Bedeutung, wenn es nicht als Quantifier verwendet wird. Wir werden diesen Aspekt weiter unten im Punkt »Minimal-Matching« näher beleuchten. Beispiele für den Quantifier »?«: if ( $str =~ /^..?$/ )
Es wird nur dann ein Treffer erzielt, wenn der Inhalt von $str aus mindestens einem beliebigen Zeichen oder aus zwei beliebigen Zeichen besteht. Der erste Punkt steht für ein beliebiges Zeichen, das genau einmal vorkommen muss. Da nach dem zweiten Punkt der Quantifier »?« steht, heißt dies, dass das zweite, ebenso beliebige Zeichen entweder gar nicht oder genau einmal vorkommen muss, damit ein Treffer erzielt wird. if ( $str =~ /^[+-]?\d*\.\d+$/ )
Dieses Pattern prüft, ob $str eine Festkommazahl enthält oder nicht. Wie definiert man das Aussehen einer Festkommazahl? Sie kann mit führendem Vorzeichen angegeben werden. Das wird im Pattern durch den Ausdruck ^[+-]? erledigt. Das Pluszeichen und das Minuszeichen bilden eine Zeichenklasse, die entweder gar nicht oder genau einmal vorkommen kann. Beachte: Innerhalb von Zeichenklassen hat das Pluszeichen keine besondere Bedeutung. Das Minuszeichen verliert innerhalb einer Zeichenklasse seine besondere Bedeutung, wenn es am Ende steht. Wir müssen es also nicht durch einen Backslash entwerten. Anschließend können beliebig viele Ziffern oder gar nichts folgen. Dies ist der Ausdruck \d*. Nun muss zwingend ein Punkt kommen, das besagt der Ausdruck \.. Da wir wirklich einen Punkt meinen und nicht irgendein beliebiges Zeichen, müssen wir das Metazeichen ».« durch einen Backslash entwerten. Da sowohl das Vorzeichen als auch führende Ziffern wegfallen können, darf die Festkommazahl auch direkt mit dem Punkt beginnen.
Reguläre Ausdrücke
233
Als letzter Bestandteil der Festkommazahl muss mindestens eine Ziffer für die Nachkommastellen vorhanden sein. Das wird durch den Ausdruck \d+$ festgelegt. So werden unter anderem folgende Strings als gültige Festkommazahlen angesehen: .15 3.14 -0.04 +100.145664
Ungültig hingegen wären die Strings: +15 (kein Komma, keine Nachkommastellen) -3. (keine Nachkommastellen) 3.0E9 (keine Festkommazahl, sondern Gleitkommazahl)
Noch ein Beispiel für Dateipfade: if ( $str =~ /^.+\.?txt$/i )
Wir suchen nach Dateien, die mit dem String »txt« enden. Da wir die Option i angegeben haben, werden auch Dateien gefunden, die mit »TXT« oder einer beliebigen Mischung von Klein- und Großbuchstaben der drei Zeichen enden. Der String »txt« kann entweder der letzte Bestandteil des Dateinamens oder die Endung des Dateinamens sein, die durch einen Punkt gekennzeichnet ist. Der Ausdruck ^.+ besagt, dass zwischen dem Beginn des Strings und dem Punkt bzw. dem String »txt« beliebig viele Zeichen, mindestens aber ein Zeichen stehen können. Wichtig ist, dass wir mit dem Dollarzeichen »$« am Ende festlegen, dass der String »txt« ganz am Schluss des Dateinamens stehen muss. Nun wollen wir den Dateinamen aus dem gesamten Dateipfad extrahieren. Der Name der Datei ist immer der letzte Bestandteil des Pfads. Immer dann, wenn man mit Dateipfaden hantiert, kocht das Problem mit dem Backslash hoch, der unter Windows als Verzeichnistrenner verwendet wird. In PerlSkripts hingegen sollte immer der Slash dafür verwendet werden. Deshalb empfiehlt es sich, vor einer Pattern Matching-Operation dafür zu sorgen, dass eventuell vorhandene Backslashes im Dateipfad durch einen Slash ersetzt werden. Wie das geht, erfahren wir weiter unten, wenn das Thema »Ersetzen von Zeichenketten« ausführlich erörtert wird. Beispiel für das Extrahieren des Dateinamens: # In $str steht zum Beispiel "/usr/tmp/myFile.txt" my ( $filename ) = $str =~ m~^.+/([^/]+)$~;
234
3
Pattern Matching
Dies sieht sehr kryptisch aus, aber so sind reguläre Ausdrücke nun mal. Das eigentlich Interessante steht rechts vom Binding-Operator =~. Hinweis: Immer dann, wenn der Slash Bestandteil des Pattern ist, sollte man den Operator m gefolgt von einem selten vorkommenden Zeichen, in unserem Fall der Tilde ~, verwenden. Sonst müssten wir nämlich jeden Slash im Pattern durch Voranstellen eines Backslashs entwerten, und unser Pattern würde nicht so gut lesbar sein: /^.+\/([^\/]+)$/
Der reguläre Ausdruck ^.+/
bedeutet: Suche, beginnend am Anfang des Strings, nach einem Slash »/«. Wenn die Suche erfolgreich war, kommt der nächste Ausdruck ([^/]+)$. Er bedeutet: Nachdem du einen Slash gefunden hast, suche bitte nach irgendwelchen Zeichen, die mindestens einmal vorkommen müssen und kein Slash sind. Diese Zeichen müssen sich bis zum Ende des Strings erstrecken (wegen des Dollarzeichens am Ende des Patterns), es darf also kein Slash dazwischen stehen. Die gefundenen Zeichen merke dir bitte und speichere den Treffer in $1 ab (wegen der runden Klammern). Das Ergebnis der Matching-Operation wird über den Zuweisungsoperator (das ist das Gleichheitszeichen) der links vom Operator stehenden Liste zugewiesen. Wichtig ist, dass wir wirklich eine Liste angeben: my ( $filename ) = ...
Hätten wir nur die skalare Variable »$filename« ohne runde Klammern für die Zuweisung verwendet: my $filename = ...
dann würde die Matching-Operation im skalaren Kontext ausgeführt. Bei einem Treffer liefert diese dann einen logischen Wert zurück, der TRUE ist, in fast allen Fällen wäre das eine 1. Nur im List-Kontext liefert die Matching-Operation eine Liste der Dollarvariablen $1 etc. zurück, und genau die brauchen wir. Von der übergebenen Liste speichern wir das erste Element in unserer Variable $filename ab. In unserem Beispiel enthält die Variable $filename also den String »myFile.txt«, genau das, was wir erwartet haben. Was passiert jedoch, wenn $str bereits den Dateinamen ohne relativen oder absoluten Pfad enthält? Nun, hier wäre die Variable $filename undef, weil ja sowohl mindestens ein Slash als auch mindestens ein Zeichen vor dem Slash stehen müssen.
Reguläre Ausdrücke
235
Diesem Problem kann man auf mehrere Arten begegnen. Als Erstes wollen wir der Variable $filename den ursprünglichen Wert von »$str« zuweisen, falls dieser bereits den Dateinamen ohne Pfad enthält: my ( $filename ) = $str =~ m~^.+/([^/]+)$~ || $str;
Die Zeile liest sich nun so: Weise der Variable »$filename« entweder den gefundenen Treffer aus der Dollarvariable $1 zu, oder, falls kein Treffer erzielt worden ist, den ursprünglichen Wert von $str. Man kann das Pattern natürlich auch so abändern, dass dieser Fall bereits von der Matching-Operation abgedeckt wird, und die Variable $1 auch für den Fall, dass $str bereits den nackten Dateinamen ohne Pfad enthält, versorgt ist: my ( $filename ) = $str =~ m~^.*/?([^/]+)$~;
Nun haben wir nach dem ersten Punkt den Quantifier »*« statt des Pluszeichens. »Irgendein Zeichen« kann jetzt auch gar nicht vorkommen. Außerdem steht nun nach dem Slash der Quantifier »?«, was bedeutet, dass auch der Slash gar nicht vorkommen darf. Eleganter als die beiden vorherigen Methoden ist jedoch diese: my ( $filename ) = $str =~ m~([^/]+)$~;
Hier suchen wir grundsätzlich nur nach Zeichen am Ende, die keinen Slash enthalten. Der zusätzliche Vorteil ist, dass wir das Pattern wesentlich vereinfacht haben. Damit wird es zum einen leichter lesbar, außerdem macht man damit die Matching-Operation schneller. Allerdings sollte man vielleicht einen kurzen Kommentar dazuschreiben, weil man aufgrund des Patterns nicht sofort ersehen kann, was wir extrahieren möchten: # Auslesen des Dateinamens ohne Pfadanteil my ( $filename ) = $str =~ m~([^/]+)$~;
Hinweis für UNIX-Anwender: Die Matching-Operation entspricht dem UNIX-Kommando »basename« in seiner einfachen Form. Nun wollen wir noch das Gegenteil implementieren, für UNIX-Anwender als »dirname« bekannt: Wir extrahieren nicht den Dateinamen, sondern den Pfad des Verzeichnisses, in welchem die Datei abgelegt ist: # Kompletter Pfad der Datei: /usr/tmp/myFile.txt # Basename (Dateiname): myFile.txt # Dirname (Pfad des Verzeichnisses von "myFile.txt"): /usr/tmp
236
3
Pattern Matching
Sehen wir uns als Erstes den Code für die Matching-Operation an: my ( $dirname ) = $str =~ m~^(.+)/[^/]+$~;
Übersetzung ins Deutsche: Suche nach einem Slash, beginnend ganz vorne, und merke dir alle Zeichen, die vor dem Slash stehen (^(.+)/). Nach dem gefundenen Slash bis zum Ende des Strings dürfen alle möglichen Zeichen mit Ausnahme eines weiteren Slashes stehen ([^/]+$). Als Ergebnis erhalten wir in $dirname den String »/usr/tmp«, genau das, was wir erhofft hatten. Wer sich jetzt fragt, wofür der Ausdruck [^/]+$ benötigt wird, da es doch auch ohne geht, dem kann geholfen werden. Lassen wir den Ausdruck einfach mal weg, dann erhalten wir: my ( $dirname ) = $str =~ m~^(.+)/~;
Das Ergebnis ist dasselbe, in $dirname steht »/usr/tmp«. Die große Frage ist: Warum? Eigentlich könnte man doch erwarten, dass die Suche nach einem Slash bereits beim ersten Treffer zu Ende ist und nicht nach dem letzten Vorkommen von »/«. Es müsste also genau genommen der String »/usr« in der Variable $dirname stehen, oder? Die Antwort heißt nein, weil Perl »greedy« ist, was man zu deutsch als »gierig« bezeichnen kann. Was damit genau gemeint ist und wie man damit umzugehen hat, werden wir im Abschnitt »Minimal -Matching« erfahren. Hier nur so viel: Der Ausdruck (.+)/ sucht bis zum letzten Vorkommen eines Slashes im zu durchsuchenden String, er hört also nicht beim ersten Treffer auf. Der Quantifier {} Mit diesem Quantifier kann man genau angeben, wie oft der vor ihm stehende Ausdruck vorkommen muss, um einen Treffer erzeugen zu können. Geschweifte Klammern werden auch benutzt, um Variablennamen einzurahmen. Beispiele für den {}-Quantifier: if ( $str = ~ /a5{1}b/ )
Eine einzelne Ziffer 5 zwischen a und b führt zu einem Treffer. Man erzielt dieselbe Wirkung wie mit: if ( $str = ~ /a5b/ )
Reguläre Ausdrücke
237
Beispiel, bei dem die Ziffer 5 mindestens zweimal hintereinander stehen muss: if ( $str =~ /5{2,}/ )
Der String »Milchstraße 52a« führt nicht zu einem Treffer, während »Milchstraße 552« einen Match erzeugt, ebenso wie »25555555«. Begrenzung der Vorkommnisse eines Ausdrucks nach oben: if ( $str =~ /5{2,5}/ )
Hier wird es ein bisschen komplizierter. Die Matching-Operation liefert bei jedem String, der die Ziffer 5 mindestens zweimal hintereinander enthält, den Wert TRUE zurück, auch wenn 5 zehnmal hintereinander vorkommt. Man müsste annehmen, dass es genau umgekehrt ist. Sehen wir uns für verschiedene Strings das Ergebnis an: foreach my $str ( "25", "255", "2555", "25555", "255555", "2555555", "2555555555555555555555555555555", ) { if ( $str =~ /5{2,5}/ ) { print( "$str hat einen Treffer\n" ); } else { print( "$str führt nicht zu einem Treffer\n" ); } }
Die Ausgabe des Codes: 25 führt nicht zu einem Treffer 255 hat einen Treffer 2555 hat einen Treffer 25555 hat einen Treffer 255555 hat einen Treffer 2555555 hat einen Treffer 2555555555555555555555555555555 hat einen Treffer
Warum? Bevor wir die Sache aufklären, lassen Sie mich das Pattern ein wenig ändern und die gefundenen Treffer mit ausgeben. Hierzu setzen wir um den Ausdruck »5{2,5}« runde Klammern, damit der Treffer in der Dollarvariable $1 abgespeichert wird: foreach my $str ( "25", "255", "2555", "25555", "255555", "2555555", "2555555555555555555555555555555", ) { if ( $str =~ /(5{2,5})/ ) {
238
3
Pattern Matching
print( "$str hat den Treffer $1\n" ); } else { print( "$str führt nicht zu einem Treffer\n" ); } }
Nun erhalten wir als Ausgabe des Codes: 25 führt nicht zu einem Treffer 255 hat den Treffer 55 2555 hat den Treffer 555 25555 hat den Treffer 5555 255555 hat den Treffer 55555 2555555 hat den Treffer 55555 2555555555555555555555555555555 hat den Treffer 55555
Die maximale Anzahl von hintereinander stehenden Fünfen bezieht sich nicht darauf, ob überhaupt ein Treffer erzielt wird, sondern darauf, wie viele Fünfen in den Dollarvariablen abgespeichert werden sollen. Ein bisschen inkonsistent, was die Begrenzung nach unten und oben angeht, aber gut. Vorsicht: Hätten wir den Ausdruck so geschrieben: if ( $str =~ /(5){2,5}/ ) {
dann sähe die Ausgabe etwas anders aus: 25 führt nicht zu einem Treffer 255 hat den Treffer 5 2555 hat den Treffer 5 25555 hat den Treffer 5 255555 hat den Treffer 5 2555555 hat den Treffer 5 2555555555555555555555555555555 hat den Treffer 5
Hier haben wir den Fehler gemacht, den Quantifier hinter die schließende runde Klammer zu schreiben. Für Perl bedeutet das: Suche nach einer 5 und speichere die gefundene 5 in $1 ab. Der Quantifier »{2,5}« sorgt zwar nach wie vor dafür, dass kein Treffer für Strings erzeugt wird, die weniger als zwei hintereinander stehende 5-en haben, in »$1« wird aber konstant nur ein Zeichen gespeichert. Benutzung von geschweiften Klammern zum Kennzeichnen von Variablennamen if ( $str =~ /abc(${myVar}){1}_bcd/ )
Der Ausdruck ${myVar} steht für die skalare Variable $myVar, während »{1}« (eins) ein Quantifier ist und verlangt, dass der Inhalt der Variable $myVar genau einmal vorkommen soll.
Reguläre Ausdrücke
239
Während man ansonsten zwischen geschweifte Klammern und Variablennamen Leerzeichen zur besseren Lesbarkeit verwenden darf, sollte man dieses Feature in Pattern von regulären Ausdrücken besser nicht nutzen. Meist funktioniert die Sache zwar, jedoch speziell bei komplexen regulären Ausdrücken können Nebeneffekte durch die Leerzeichen ins Spiel kommen, die man vorher nicht bedacht hat: # Schlecht: if ( $str =~ /abc(${ myVar }){ 1 }_bcd/ # Funktioniert nicht
Ich habe runde Klammern um ${myVar} gesetzt. Das hat seinen Grund: Solange $myVar nur ein einzelnes Zeichen enthält, könnte man die Klammern zur Gruppierung auch weglassen. Der Quantifier »{1}« bezieht sich nämlich nur auf das unmittelbar links stehende Zeichen. Durch die Klammern wird der gesamte Inhalt von $myVar zu einer Einheit gruppiert, und der Quantifier bezieht sich nun auf mehr als nur ein Zeichen.
Die Metazeichen \Q und \E Mit diesen Metazeichen kann man beeinflussen, wie das Pattern bewertet werden soll. \Q teilt dem Interpreter mit, dass alle nachfolgenden Zeichen, die normalerweise Metazeichen des Pattern Matching wären, als normale Zeichen behandelt werden. Lediglich Werte für Variablen werden eingesetzt.
Mit \E schaltet man die besondere Bedeutung von Metazeichen wieder ein. Auch hierzu ein Beispiel: my $v = "hallo"; if ( $str =~ /.+a\Q*+$v\E$/ )
Der Ausdruck .+a wird normal behandelt, das heißt, dass irgendwelche Zeichen bis zu einem a gesucht werden sollen. Nach dem Metazeichen \Q verlieren das Sternchen sowie das Pluszeichen ihre besondere Bedeutung, während für $v der Wert der Variable eingesetzt wird, und der ist »hallo«. Anschließend wird mit \E wieder auf den Metazeichen-Mechanismus umgeschaltet, das Dollarzeichen am Ende des Patterns heißt also »Ende des Strings«. Ein Treffer wird erzeugt, wenn unser String zum Beispiel »blabla*+hallo« ist.
? (Minimal-Matching) Das Fragezeichen wird zum einen als Quantifier benutzt, wobei es die Bedeutung hat, dass der davor stehende Ausdruck 0-mal oder einmal vorkommen muss. Steht das Fragezeichen aber direkt nach einem Quantifier (dies kann auch wiederum das Fragezeichen sein), gewinnt es eine völlig andere Bedeutung, nämlich »Minimal-Matching«.
240
3
Pattern Matching
Per Default ist Pattern Matching in Perl »greedy« (zu deutsch »gierig«) d.h., der reguläre Ausdruck .+ oder .* führt ein Matching bis zum letzten Treffer durch, nicht bis zum ersten. Dies lässt sich am einfachsten anhand eines Beispiels verdeutlichen: # String, den wir parsen wollen my $str = "Body";
Der String enthält keine Zeilenende-Zeichen. Nun wollen wir den Inhalt des ersten HTML-Tags extrahieren (ohne die spitzen Klammern), wir erwarten also »html«: my ( $tag ) = $str =~ /<(.+)>/;
Die Variable $tag enthält nicht etwa »html«, wie wir erwartet hätten, sondern der Ausdruck .+ führt dazu, dass der Interpreter alle Zeichen bis zum letzten Vorkommen des Zeichens > ausgibt. Die Variable $tag enthält also »html>Body body> Body EOT ( $tag ) = $str =~ /<(.+)>/;
Jetzt enthält die Variable $tag tatsächlich den String »html« wie erwartet. Leider ist das nur ein Trugschluss, denn hätten wir in der ersten Zeile nicht nur das Tag »«, sondern auch noch das »«-Tag, läge der Fall schon wieder anders. In $tag stünde dann »html>
Ganz schlimm wird es, wenn wir die Option s angeben, dann erhalten wir in jedem Fall in $tag den String »html>Body aufgehört, nicht beim letzten. Das einzige, was wir tun müssen, ist nach dem Quantifier ein Fragezeichen anzugeben. Damit stellen wir die Suche auf »Minimal-Matching« ein: my ( $tag ) = $str =~ /<(.+?)>/s;
Damit ist bereits nach dem ersten > die Suchbedingung erfüllt, und die Variable $tag enthält genau das, was wir wollten, nämlich »html«. Minimal-Matching wird nur in Verbindung mit dem Metazeichen ».« benötigt. Wenn wir unser Pattern umschreiben, funktioniert das Ganze auch ohne MinimalMatching: my ( $tag ) = $str =~ /<([^>]+)>/s;
Hier wird kein Minimal-Matching benötigt, weil wir das Metazeichen ».« nicht verwenden, sondern stattdessen in einer Zeichenklasse angeben, dass alle Zeichen mit Ausnahme der schließenden spitzen Klammer einen Treffer erzeugen sollen. Ins Deutsche übersetzt heißt unser neues Pattern: Suche die erste öffnende spitze Klammer. Dann speichere alle folgenden Zeichen, die keine schließende spitze Klammer sind, in der Dollarvariable $1. Was haben wir daraus gelernt? Das Metazeichen ».« ist sehr praktisch und verführt gerne zur Denkfaulheit. Manchmal aber heißt es aufpassen, wenn in einem Text irgendein Zeichen mehrfach vorkommt!
3.3 Ersetzen von Zeichenketten Bisher haben wir in Strings nur nach bestimmten Patterns gesucht, ohne dass die zu durchsuchenden Strings geändert wurden. Nun aber gehen wir einen Schritt weiter und wollen gezielt die Originalstrings verändern (natürlich nicht, ohne auf reguläre Ausdrücke verzichten zu müssen). Den Matching-Operator m für die Suche in Strings haben wir ja bereits kennen gelernt.
Für die Ersetzung müssen wir nun einen neuen Operator kennen lernen, der auch nur einen Buchstaben hat, nämlich s (steht für »substitution« oder zu deutsch »Ersetzung«). Im Prinzip ist er wie der m-Operator aufgebaut, nur hat er zusätzlich einen Bestandteil, in welchem angegeben wird, durch was der gefundene Treffer ersetzt werden soll. Wie schon beim Matching-Operator m dient das darauf folgende Zeichen als Trennzeichen für die einzelnen Bestandteile des Operanden, der rechts vom Binding-Operator =~ steht. Beim Matching-Operator hatten wir zwei Bestandteile (Pattern und Optionen), also sind es beim Substitutionsoperator nach Adam Riese drei Bestandteile: Pattern, Ersetzung, Optionen. Auch beim Substitutionsoperator gilt: Verwende ein Trennzeichen, das nicht im Pattern vorkommt. Syntax des Operators s: string =~ s/searchPattern/replacePattern/options string =~ s~searchPattern~replacePattern~options
Rechts neben dem Binding-Operator steht der Substitutionsoperator (hier in zwei Beispielen, einmal mit dem Standardtrennzeichen / und mit dem selbst gewählten Zeichen ~). Der Substitutionsoperator erweitert den Matching-Operator um einen dritten Teil in der Mitte zwischen Suchmuster und Optionen, dem Ersetzungspattern (replacePattern), welches anstelle eines gefundenen Treffers eingesetzt wird. Links neben dem Binding-Operator steht der String, in dem die Suche und die Ersetzung stattfindet. Nach einer Ersetzung wird die aktuelle Position innerhalb des zu durchsuchenden Strings auf 0 zurückgesetzt bzw. gelöscht. Siehe hierzu auch die Funktion pos() Dies hat vor allem Einfluss auf Pattern Matching-Operationen, die in einer Schleife mit Hilfe der Option g durchgeführt werden. Bei der Beschreibung dieser Option findet sich auch ein Beispiel. Sehen wir uns die Funktionsweise des Ersetzungs-Mechanismus am besten anhand eines Beispiels an: # Unser Input String: my $str = "1 2 3, das ist nicht schwer";
# Wir ersetzen nun die "3" durch das Wort "Drei" $str =~ s/3/Drei/; # |||| | ||-- Optionen (hier leer) # |||| | +--- Trennzeichen # |||| +----- Ersetzungspattern # |||+-------- Trennzeichen # ||+--------- Suchpattern # |+---------- Trennzeichen # +----------- Operator für Ersetzung
Ins Deutsche übersetzt können wir die Ersetzungs-Operation so lesen: Suche nach der Ziffer »3« und ersetze sie durch den String »Drei«. Unsere Variable $str enthält nach der Ersetzung »1 2 Drei, das ist nicht schwer«. Wir können auch abfragen, ob eine Ersetzung stattgefunden hat. Der Substitutionsoperator liefert grundsätzlich die Anzahl der erfolgreichen Ersetzungen zurück, unabhängig davon, in welchem Kontext er verwendet wird. Ohne die Option g wird also entweder die Zahl 0 (kein Match) oder 1 (Treffer und Ersetzung) zurückgegeben. Verwendet man die Option g, dann führt der Interpreter für alle Treffer im gesamten String eine Ersetzung durch. Auch das wollen wir an einem Beispiel demonstrieren: # Unser String my $str = "der Text print( "str davor
# Nun ersetzen wir alle überschüssigen Blanks # durch "nichts" und entfernen sie damit my $cnt = $str =~ s/ +/ /g; print( "str danach = '$str'\n" ); print( "$cnt Ersetzungen\n" );
Das Suchpattern liest sich so: Suche nach einem Blank, dem mindestens ein weiteres Blank folgt. Ersetze die gefundenen Zeichen durch ein einziges Blank. Da wir die Option g verwendet haben, wird nicht nur der erste, sondern alle Treffer ersetzt. Wenn wir den Code ausführen, erhalten wir folgende Ausgabe: str davor = 'der Text enthält zu viele Blanks ' str danach = 'der Text enthält zu viele Blanks ' 4 Ersetzungen
Alle mehrfach hintereinander stehenden Blanks wurden jeweils durch ein einzelnes Blank ersetzt. Das Blank am Ende des Strings jedoch nicht, dafür müssen wir uns etwas anderes einfallen lassen: # Unser String my $str = "Ein
# Alle "White Space" Zeichen vor dem Zeilenende # entfernen $str =~ s/[ \t\r]+\n/\n/g;
Das Ganze lässt sich wie folgt ins Deutsche übersetzen: Suche Blanks, TAB- oder »\r«Zeichen, die in beliebiger Mischung vor dem Zeilenende-Zeichen in ebenfalls beliebiger Anzahl stehen, und entferne sie. Nach Ausführung des Codes enthält die Variable $str den folgenden Wert: Ein\nschöner\n\n\nTag\n
»Es gibt doch die wunderbare Zeichenklasse \s«, wird sich mancher nun sagen, »damit geht es doch viel einfacher!« Probieren wir es aus: $str =~ s/\s+\n/\n/g;
Es fehlen ein paar Zeilenende-Zeichen in der Mitte des Strings. Das Dumme an der Sache ist: Das Zeilenende-Zeichen »\n« ist ebenfalls Bestandteil der Zeichenklasse \s. Auswirkung: Auch alle mehrfach hintereinander stehenden Zeilenende-Zeichen (eine oder mehrere Leerzeilen) werden durch ein einziges ersetzt. Man kann bei Ersetzungen auch runde Klammern in Verbindung mit Dollarvariablen benutzen: # Unser String my $str = "der modul ist kein schönes Wort"; # Ersetzen von "der" durch "das" $str = s/(d)er/${1}as/gi;
Die Pattern sind vielleicht etwas erklärungsbedürftig: Das Suchpattern ist noch recht einfach: Suche nach einem »d« gefolgt von der Zeichenkette »er«. Wenn du auf dieses Wort im String triffst, merke dir das »d« in $1. Die Suche ist wegen der Option i case-insensitive, es würde also auch das Wort »Der« gefunden, und in $1 stünde bei einem Treffer »D«.
Nun zum Ersetzungspattern: Bei einem Treffer soll das in $1 gespeicherte Zeichen gefolgt von der Zeichenkette »as« anstelle des Treffers eingesetzt werden. In unserem Beispiel dürften wir zwar die geschweiften Klammern im Ersetzungspattern »${1}as« weglassen und stattdessen »$1as« schreiben. Das gilt aber nur bei den speziellen Dollarvariablen, weil »1as« ein ungültiger Variablenname ist. Bei allen anderen Variablen müssen geschweifte Klammern gesetzt werden. Hinweis: Da wir die Option i angegeben haben, würden auch alle Vorkommnisse von »Der« in »Das« umgewandelt werden. Sowohl im Suchpattern als auch im Ersetzungspattern dürfen Variablen benutzt werden. Demonstrieren wir dies gleich anhand eines Beispiels: In HTML gilt die Regel: Verwende keine deutschen Sonderzeichen »äöüß«, sondern schreibe stattdessen die spezielle HTML-Notation dafür. So wird aus einem »ä« ein »ä«, aus »ö« wird »ö«, »ü« wird zu »ü« und das scharfe »ß« wird zu »ß«. Großgeschriebene Umlaute werden genauso dargestellt, nur ist dann das erste Zeichen nach dem Kaufmännischen Und »&« ein Großbuchstabe. Das Zeichen »Ü« wird also in HTML durch »Ü« dargestellt. Warum das Ganze? Die Sonderzeichen (und dazu gehören die deutschen Umlaute) besitzen in den verschiedenen Betriebssystemen je nach Ländereinstellung unterschiedliche Zeichencodes. Hierzu ein kleines Beispiel: Wir erstellen mit einem Windows-Texteditor ein Skript mit folgendem Inhalt: #!D:/Perl/bin/perl.exe -w use strict; print( "Schön wärs übers große Meer zu fahren\n" );
Die Hashbang-Zeile (das ist die erste Zeile, in welcher der Pfad zum Perl-Interpreter steht) müssen Sie natürlich Ihrer Perl-Installation anpassen. Wenn wir nun das Skript in einer DOS-Box ausführen, bekommen wir folgende Ausgabe: Sch÷n wõrs ³bers gro¯e Meer zu fahren
Sieht nicht so schön aus, oder? Dasselbe kann in einem Browser passieren, wenn er einen anderen Zeichencode verwendet als der Webserver. Deshalb sollte man die symbolischen Namen der Umlaute in HTML benutzen.
Viele HTML-Seiten enthalten auch heute noch Umlaute. Sehen wir uns jetzt ein Beispiel an, mit dem man auf einfache Art und Weise alle Umlaute durch deren HTMLÄquivalent ersetzen kann: # Hash, das als Keys die Umlaute, als Values die # HTML Symbole enthält my %cvt = ( "Ä" => 'Ä', "Ö" => 'Ö', "Ü" => 'Ü', "ä" => 'ä', "ö" => 'ö', "ü" => 'ü', "ß" => 'ß', ); # Beispiel-String my $str = "Schön wärs übers Meer zu fahren"; # Ersetzung aller Umlaute in "einem Rutsch" $str =~ s/([ÄÖÜäöüß])/$cvt{ $1 }/g;
Sieht sehr kurz aus. Wenn in einem Perl-String Literale vorkommen, die Zeichen wie »*«, »&«, »$«, »@« oder »\« enthalten, sollte man diese Literale in einfache Quotes statt in doppelte Anführungszeichen setzen, da der Interpreter diese sonst als Sonderzeichen behandeln würde. Fehler sind so vorprogrammiert. Kleines Beispiel: my $email = "
wird vom Interpreter sofort mit einer Warnung bestraft. my $email = '
jedoch ist in Ordnung, weil der String in einfache Quotes gesetzt ist. Das Suchpattern in der Konvertierung von Umlauten sollte klar sein: Suche nach einem deutschen Umlaut und speichere den Treffer in $1 ab. Im Ersetzungspattern wird nun der in $1 gespeicherte Wert als Key im Hash %cvt benutzt, statt des Ausdrucks $cvt{ $1 } setzt der Interpreter also den zum Key passenden Value ein. Da wir die Option g verwendet haben, erfolgt die Ersetzung für alle Umlaute im String.
Option e Das Ganze funktioniert natürlich auch umgekehrt. Angenommen, wir wollen uns eine HTML-Seite mit einem Texteditor ansehen. Dann sind die vielen '&...;' doch ganz schön verwirrend. Also versuchen wir es wieder mit einem Hash: # Hash, das als Keys die HTML-Symbole, als Values die # Umlaute enthält # Hinweis, warum die Keys etwas sonderbar aussehen # (ohne "&" und ";"), weiter unten im Code my %cvt = ( 'Auml' => 'Ä', 'Ouml' => 'Ö', 'Uuml' => 'Ü', 'auml' => 'ä', 'ouml' => 'ö', 'uuml' => 'ü', 'szlig' => 'ß', ); # Beispiel-String my $str = 'Schön wärs übers Meer' . ' zu fahren'; # Ersetzung (jetzt kann man nicht einfach schreiben: # [öä] oder ähnlich, man muss sich schon # was anderes einfallen lassen) $str =~ s/&([a-z])+;/$cvt{ $1 }/gi;
Das Ersetzungspattern sieht aus wie vorher. Beim Suchpattern habe ich aber nur die zwischen dem »&« und »;« stehenden Zeichen in Klammern gesetzt (alle Buchstaben werden in »$1« gespeichert). Das verbraucht weniger Speicher und macht die Ersetzungs-Operation schneller, weil weniger Daten gespeichert werden müssen. Mit unserem String funktioniert die Sache wunderbar, wir erhalten als Ergebnis in »$str«: Schön wärs übers Meer zu fahren
Wenn alles im Leben so einfach wäre! In HTML sind natürlich nicht nur deutsche Umlaute als Symbole definiert, die durch das Zeichen »&« als Anfangskennzeichen und »;« als Endezeichen umrahmt werden. Es gibt viel mehr solcher Symbole, zum Beispiel das Kaufmännische Und »&«, das in HTML durch »&« dargestellt wird. Enthielte unser String ein solches Symbol, das wir nicht als Key im Hash definiert haben, würde es vom Interpreter prompt eine Fehlermeldung geben.
Wir sind nun ganz frech und packen die Umwandlung der Symbole in die entsprechenden Umlaute in eine Funktion, die wir im Ersetzungspattern aufrufen: # Funktion für die Umwandlung sub cvt { my ( $key ) = @_; my %cvt = ( 'Ä' => 'Ä', 'Ö' => 'Ö', 'Ü' => 'Ü', 'ä' => 'ä', 'ö' => 'ö', 'ü' => 'ü', 'ß' => 'ß', ); # Prüfen, ob der Key bei uns bekannt ist if ( exists( $cvt{ $key } ) ) { # Key ist bekannt, wir geben den Value zurück # aus "ä" wird also "ä" return $cvt{ $key }; } # Der Key ist uns nicht bekannt, wir geben ihn # unverändert zurück return $key; } # Beispiel-String my $str = 'Schön wärs übers Meer' . ' zu fahren &'; # Im Ersetzungspattern rufen wir die Funktion # "cvt" mit $1 als Parameter auf $str =~ s/(&[a-z]+;)/cvt( $1 )/gi;
Wenn wir den Code ausführen, erhalten wir zwar keine Fehlermeldung, jedoch sieht das Ergebnis nicht so aus, wie wir erwartet haben: Schcvt( ö )n wcvt( ä )rs cvt( ü )bers Meer zu fahren cvt( & )
Es sieht so aus, als hätte der Interpreter nicht verstanden, was wir meinen. Für ihn steht im Ersetzungspattern kein Funktionsaufruf, sondern ein konstanter String (mit Ausnahme von $1). Also teilen wir ihm mit dem Zeichen »&« explizit mit, dass es ein Funktionsaufruf ist, indem wir das »&« vor den Funktionsnamen stellen: $str =~ s/(&[a-z]+;)/&cvt( $1 )/gi;
Das Ergebnis ist aber leider genauso wenig berauschend, nun haben wir auch noch ein »&« im String, ohne dass die HTML-Symbole umgewandelt wären: Sch&cvt( ö )n w&cvt( ä )rs &cvt( ü )bers Meer zu fahren &cvt( & )
Hätten wir den Abschnitt »Pattern Matching« genauer gelesen, dann wüssten wir, dass Patterns als Strings in doppelten Anführungszeichen behandelt werden. Variablen werden darin zwar ersetzt, wie wir wissen, aber Funktionsaufrufe sind dort eben nur eine Reihe von Zeichen. Auflösung des Rätsels: Mit der Option e kann man beliebige Ausdrücke und auch Funktionsaufrufe in das Ersetzungspattern aufnehmen. Normalerweise interpretiert Perl das Ersetzungspattern als »double quoted« String, mit der Option e jedoch als Perl-Ausdruck, und das kann auch ein Funktionsaufruf sein. Und nun das Ganze noch einmal, diesmal aber richtig: $str =~ s/(&[a-z]+;)/cvt( $1 )/egi;
Erst jetzt weiß Perl, dass cvt( $1 ) im Ersetzungspattern ein Funktionsaufruf ist, und wir erhalten als Ergebnis: Schön wärs übers Meer zu fahren &
Wie wir sehen, wurden all diejenigen HTML-Symbole ersetzt, die der Funktion cvt() bekannt sind, während alle unbekannten Symbole mit ihrem ursprünglichen Wert nochmals eingesetzt wurden. Noch ein weiteres Beispiel: Wir wollen alle Wörter mit einem Großbuchstaben beginnen lassen. Hierfür bietet Perl die Funktion ucfirst() an. Hier der Ausdruck für die Ersetzung: $str =~ s/(\w+)/ucfirst( $1 )/eg;
Mit dem Ausdruck (\w+) suchen wir nach allen Wörtern, durch den Aufruf von ucfirst( $1 ) werden die gespeicherten Treffer so umgewandelt, dass der erste Buchstabe jeweils in Großschreibung ist. Vorsicht: Deutsche Umlaute erzielen ohne die Verwendung von use locale; keine Treffer! Dazu ein kleines anschauliches Beispiel: my $str = "über sieben brücken müssen wir gehen"; $str =~ s/(\w+)/ucfirst( $1 )/eg;
Die Ausgabe des Codes ist wenig ansehnlich: üBer Sieben BrüCken MüSsen Wir Gehen
Da die deutschen Umlaute standardmäßig nicht in der Zeichenklasse \w enthalten sind, gelten sie als Worttrennzeichen, genauso wie ».« oder »,« etc. Daher ist für den Interpreter das erste Wort »ber« und nicht »über«, und er wandelt das »b« in einen Großbuchstaben um. Nun dasselbe noch einmal, aber mit der Direktive use locale;: use locale; my $str = "über sieben brücken müssen wir gehen"; $str =~ s/(\w+)/ucfirst( $1 )/eg;
Nun sieht die Ausgabe etwas freundlicher aus: Über Sieben Brücken Müssen Wir Gehen
Hinweis: Sollte der Code auf Ihrem Rechner nach wie vor die Zeile üBer Sieben BrüCken MüSsen Wir Gehen
ausgeben, dann gibt es 1001 Möglichkeiten als Grund dafür. Unter anderem könnte es sein, dass Ihr System keine Locales unterstützt, Perl damit nicht klarkommt, oder Sie falsche Einstellungen haben. Für diesen Fall lege ich Ihnen das Thema »perllocale« der Online-Dokumentation ans Herz, das Sie unter anderem durch das Kommando perldoc perllocale
lesen können. Wenn Sie jedoch einen Windows-PC Ihr Eigen nennen, könnte auch etwas anderes an dem seltsamen Verhalten schuld sein. Sobald Sie nämlich den Code mit Umlauten in einer DOS-Box eingeben oder das Skript mit einem DOS-Editor erstellen (oder auch per Copy/Paste in das DOS-Fenster kopieren), sind alle Umlaute in einem anderen Zeichensatz kodiert als von Perl erwartet. Das Ergebnis kennen wir, glaube ich, nur allzu gut. In diesem Fall empfehle ich Ihnen: Schreiben Sie das Skript in einem Windows-Editor und leiten Sie die Ausgabe des Skripts in eine Datei um. Diese enthält dann das gewünschte Ergebnis. Hier ein Beispiel (unser Code steht in dem Skript »tst.pl«): tst.pl > mist.txt
Mit diesem Aufruf des Skripts leiten Sie dessen Ausgabe in die Datei mist.txt um, am Bildschirm wird jetzt nichts mehr ausgegeben.
3.4 Erweiterte Ausdrücke 3.4.1 (?imsx-imsx) Mit diesem erweiterten Ausdruck kann man Optionen dynamisch angeben und so den gesamten Ausdruck einschließlich Optionen in einer Variable ablegen. Die Option ims kennen wir bereits. x jedoch ist uns noch unbekannt. Sie dient dazu, Leerzeichen in regulären Ausdrücken verwenden zu können, um die Lesbarkeit zu erhöhen. Allerdings hat sie ein paar Tücken und wird selten benötigt, deshalb möchte ich Sie auf das Thema »perlre« in der Online-Dokumentation verweisen. Beispiel für (?...): 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19
# Normaler Ausdruck (die Option "i" wird im Code # angegeben): if ( $str =~ /(mustermann)/i ) { print( "$1 gefunden\n" ); } # Erweitert (die Option "i" kann zum Beispiel aus # einer Datei eingelesen und in der Variablen # "$switches" abgelegt werden). my $switches = "i"; if ( /(?$switches)(mustermann)/ ) { print( "$1 gefunden\n" ); } # Es geht natürlich auch so: my $pattern = "(?$switches)(mustermann)"; if ( /$pattern/ ) { print( "$1 gefunden\n" ); }
Die Optionen nach dem Fragezeichen können sowohl eingeschaltet als auch ausgeschaltet werden, zum Beispiel: /(?is-m)bla/
Dieser Ausdruck bewirkt, dass die Matching-Operation case-insensitive wird und das Metazeichen ».« gezielt auch bei einem Zeilenende-Zeichen einen Treffer erzeugt. (Die Option s wird eingeschaltet, während die Option m ausgeschaltet wird.) Während normalerweise runde Klammern immer dazu führen, dass für die Treffer für die in Klammern stehenden Ausdrücke Dollarvariablen reserviert werden, tritt dieser Mechanismus bei (?..) nicht in Kraft.
In Zeile 11 des vorherigen Beispielcodes sehen wir, dass im Pattern zwar zwei Klammernpärchen stehen, aber nur der Treffer für die zweite Klammer »(mustermann)« in $1 gespeichert wird. Das dynamische Setzen von Optionen erlaubt es sogar, innerhalb einer Matching-Operation unterschiedliche Suchmodi zu verwenden: # Normale Angabe der Optionen my $str = "blablablaBlaBlaBlaBlablablablaBlaBlabla"; # 012345678901234567890123456789012345678 # Wir suchen nach "blabla" ohne Unterscheidung von # Groß- und Kleinschreibung while ( $str =~ /(bla)(bla)/gi ) { my $p1 = $-[ 1 ]; my $p2 = $-[ 2 ]; print( "found '$1'at $p1, '$2' at $p2\n" ); }
0, 'bla' at 3 6, 'Bla' at 9 12, 'Bla' at 15 18, 'bla' at 21 24, 'bla' at 27 30, 'Bla' at 33
Ich habe die Anfangsposition jedes Treffers mit ausgegeben, damit Sie die Teilstrings leichter im String $str wiederfinden. Nun dasselbe, nur wollen wir das erste »bla« case-insensitive, das zweite aber casesensitive suchen: # Dynamische Angabe der Optionen my $str = "blablablaBlaBlaBlaBlablablablaBlaBlabla"; # 012345678901234567890123456789012345678 # Wir suchen nach "blabla". Das erste "bla" wird # ohne Unterscheidung zwischen Groß- und Kleinschreibung # gesucht, das zweite case-sensitive. while ( $str =~ /((?i)bla)(bla)/g ) { my $p1 = $-[ 1 ]; my $p2 = $-[ 2 ]; print( "found '$1'at $p1, '$2' at $p2\n" ); }
Der Unterschied zu vorher besteht darin, dass die Option i nun nicht mehr hinten im Optionsbestandteil der Matching-Operation angegeben wird, sondern nur für das erste Pattern wirksam ist. Die Ausgabe ist entsprechend kürzer, weil jetzt nur noch Treffer erzielt werden, wenn das zweite »bla« kleingeschrieben ist: found found found found
3.4.2 (?:pattern) und (?imsx-imsx:pattern) Bitte verwechseln Sie diese Erweiterung nicht mit der vorher beschriebenen (?...), bei der kein Doppelpunkt nach dem Fragezeichen stand. Sehen Sie sich am besten ein Beispiel an, um die Wirkungsweise kennen zu lernen: my $str = "blablablaBlaBlaBlaBlablablablaBlaBlabla"; # Wir suchen nach einem "bla", das dreimal # hintereinander vorkommt if ( $str =~ /(bla{3})/i ) { print( "found '$1'\n" ); } else { print( "found nothing\n" ); }
zeigt uns, dass wir wohl etwas falsch gemacht haben. Der Quantifier {3} wirkt sich nur für das Zeichen »a« aus, nicht etwa für die Zeichenkette »bla«. Um dies zu erreichen, müssen wir den gesamten String in runde Klammern setzen, damit die Einzelzeichen zu einer Gruppe zusammengefasst werden: my $str = "blablablaBlaBlaBlaBlablablablaBlaBlabla"; if ( $str =~ /((bla){3})/i ) { print( "found '$1', '$2'\n" ); } else { print( "found nothing\n" ); }
Hier die Auflösung des Rätsels »Was wird ausgegeben?«: found 'blablabla', 'bla'
Wenn man den Klammer-Mechanismus des Pattern Matching verstanden hat, ist alles klar: Das erste Klammernpärchen umfasst den gesamten Treffer, der sich durch die Aneinanderreihung von $1, $2 etc. ergibt. Das zweite Pärchen runder Klammern wollen wir eigentlich gar nicht, wir brauchen es aber für die Gruppierung der einzelnen Zeichen zu einer Einheit. Dadurch aber wird auch dieser Treffer abgespeichert, und zwar in »$2«. Mit dem folgenden Code können wir das unnötige Abspeichern in Dollarvariablen verhindern: my $str = "blablablaBlaBlaBlaBlablablablaBlaBlabla"; if ( $str =~ /((?:bla){3})/i ) { print( "found '$1', '", $2 ? $2 : "", "'\n" ); } else { print( "found nothing\n" ); }
Jetzt funktioniert die Sache so, wie wir es uns vorgestellt haben, die Ausgabe ist: found 'blablabla', ''
Im Aufruf der print()-Funktion müssen wir jetzt allerdings abfragen, ob $2 den Wert undef besitzt, da wir sonst eine Fehlermeldung des Interpreters erhalten.
3.4.3 (?!pattern) Diese Erweiterung wird im Englischen »zero width negative look-ahead assertion« genannt. Ins Deutsche übersetzt: Wenn wir nach einem String »balla« suchen, der nicht direkt von »balla« gefolgt ist (»ballaballa« soll also keinen Treffer erzielen, »balla balla« jedoch schon, weil ein Leerzeichen dazwischen steht), dann ist diese Erweiterung genau das Richtige für uns. Am besten, wir machen wieder ein kleines Beispiel: my $str = "ballaballa und noch mal balla"; # 01234567890123456789012345678 while ( $str =~ /(balla(?!balla))/gi ) { print( "found '$1' at $-[ 1 ]\n" ); }
Lassen wir den Code laufen und sehen wir uns seine Ausgabe an: found 'balla' at 5 found 'balla' at 24
Wie wir sehen, erzeugt das allererste »balla« am Beginn des Strings keinen Treffer, weil dasselbe Wort gleich danach noch einmal steht. Das zweite und dritte »balla« jedoch sind Treffer. Hinweis: Auch für diese Erweiterung gilt: Die runden Klammern führen nicht zu einem Abspeichern der Treffer in Dollarvariablen. Noch ein Hinweis: Wer jetzt vermutet, es müsse auch etwas geben, das sich »zero width negative look-behind assertion« nennt, der irrt. Es ist nicht (oder zumindest nicht einfach) möglich, ein Pattern zu schreiben, mit dem man einen String sucht, vor dem ein anderer NICHT steht. Wem das zu kompliziert klingt (keine Angst, ich gehöre auch dazu), dem sei das Ganze noch mal anhand eines Beispiels erläutert: Wir suchen nach dem String »ist«. Es soll aber nur dann ein Treffer erzielt werden, wenn kein »das« vor dem »ist« steht, also »das ist« soll keinen Match ergeben, »Der ist« hingegen schon. Allen, die trotzdem händeringend nach einem solchen Feature suchen, kann geholfen werden. Im nächsten Abschnitt gehen wir darauf ein. Perl stellt noch ein paar weitere Erweiterungen bereit, die allerdings den Rahmen dieses Buches sprengen würden, deshalb möchte ich Ihnen für diese Zusätze das Thema »perlre« ans Herz legen.
3.5 Besondere Matchingvariablen Perl stellt für Pattern Matching einige vordefinierte Variablen zur Verfügung, von denen wir bereits ein paar kennen gelernt haben ($1, @-, @+). Die noch unbekannten Variablen möchte ich Ihnen nun vorstellen (die bereits bekannten sind der Vollständigkeit halber noch einmal kurz aufgeführt):
3.5.1 $1, $2 ... Diese Dollarvariablen kennen wir bereits recht gut aus den vorherigen Abschnitten. Jeden Treffer, dessen regulärer Ausdruck im Pattern durch ein Pärchen aus runden Klammern umrahmt ist, legt der Interpreter in durchnummerierten Variablen ab, die mit $1, $2 usw. ansprechbar sind. Hinweis: Diese Variablen sind nur in demjenigen Block (Geltungsbereich) definiert, in dem auch die Matching-Operation steht: if ( $str =~ /(hallo)(.+)/ ) { # Hier sind $1 und $2 gültig } else {
# Hier sind $1 und $2 nicht mehr gültig } # Hier sind $1 und $2 nicht mehr gültig
3.5.2 @-, @+ Auch diese beiden Array-Variablen kennen wir bereits von früher. In @- werden die Offsets des ersten Zeichens eines Treffers gespeichert, @+ enthält die Offsets der auf die Treffer folgenden Zeichen.
Hinweis: Die Variablen sind auch außerhalb des Blocks, in welchem die MatchingOperation durchgeführt wird, definiert: if ( $str =~ /(.+)ist(.+)/ ) { # @- und @+ enthalten je drei Elemente # $-[ 0 ]: Offset des ersten Zeichens vom ersten # Treffer # $-[ 1 ]: Offset des ersten Zeichens vom ersten # Treffer (1. Klammernpärchen) # $-[ 2 ]: Offset des ersten Zeichens vom zweiten # Treffer (2. Klammernpärchen) # $+[ 0 ]: Offset des ersten Zeichens nach dem # ersten Treffer # $+[ 1 ]: Offset des ersten Zeichens nach dem # ersten Treffer (1. Klammernpärchen) # $+[ 2 ]: Offset des ersten Zeichens nach dem # zweiten Treffer (2. Klammernpärchen) } else { # @- und @+ sind leer } # # # #
Bei einem Match enthalten @- und @+ nach der if-Abfrage dieselben Elemente wie im "if"-Zweig. Falls kein Treffer erzielt wurde, sind beide Arrays leer.
3.5.3 $` und $' Diese beiden Variablen sind mit Vorsicht zu genießen, weil damit die Performance des gesamten Pattern Matching tüchtig »in die Knie« gehen kann. Aber in einigen Fällen kommt man nicht umhin, sie zu benutzen. Weiter oben hatte ich gesagt, es sei nicht auf einfache Art und Weise möglich, die so genannte »zero width negative look-behind assertion« zu implementieren (Stichwort:
Finde ein »b«, vor dem kein »a« steht). Nun will ich Ihnen zeigen, wie es doch relativ einfach geht, allerdings immer unter dem Aspekt, dass die Suchgeschwindigkeit darunter leidet. Zunächst wieder ein kleines Beispiel: my $str = <<EOT;
EOT # Beachte: Die Matching-Operation benutzt # als Trenner "~" und nicht "/", weil dieses Zeichen # im Pattern vorkommt. Deshalb wird der # Matching-Operator "m" benutzt. my ( $body ) = $str =~ m~(.+)~s; print( "body = '$body'\n" ); print( "pre = '", $`, "'\npst = '", $', "'\n" );
Ich habe die Option s natürlich mit Bedacht gewählt. Da wir im Pattern den regulären Ausdruck .+ verwenden, muss diese Option angegeben sein, damit auch Zeilenende-Zeichen einen Treffer erzielen. Ohne die Option erhielten wir eine Fehlermeldung, da die Suche keinen Treffer erzielt hätte und damit die auszugebenden Variablen den Wert undef besäßen. Nun zu der Ausgabe: Die Variable $body enthält genau das, was wir erwartet haben, nämlich den Text zwischen dem öffnendem und schließendem HTML-Tags »«.
Wir wir aus der restlichen Ausgabe entnehmen können, enthält die Variable $` den Teil vor dem Treffer, während in $' der Rest nach dem Treffer steht. Deswegen heißen die beiden Variablen in Langform auch $PRE_MATCH und $POST_MATCH. Wir können unser obiges Problem also mit Hilfe von $` lösen: my $str = "aber hallo! hallo auch"; if ( ( $str =~ /hallo/ ) and ( $` !~ /aber $/ ) ) { print( "hallo ohne führendes aber gefunden\n" ); }
Die erste Matching-Operation ist simpel. Ihr Ergebnis wird mit der zweiten MatchingOperation UND-verknüpft, bei der nicht der String $str, sondern der Inhalt von $' durchsucht wird. Dieses Variable wird erst durch die vorher ausgeführte Suche mit dem Inhalt des Strings vor dem Treffer gefüllt. Die Suche verläuft übrigens nicht erfolgreich. Der Gund liegt darin, dass die erste Matching-Operation zunächst das erste Vorkommen von »hallo« findet. Davor aber steht genau »aber «, was ja im zweiten Matching verboten ist. Damit wir einen Treffer erzielen können, müssen wir unseren String $str ändern, zum Beispiel in: my $str = "abe hallo! hallo auch";
Jetzt ist das Ende von $' nicht »aber «, sondern »abe «, und damit wird ein Match erzeugt. Vorsicht: Die beiden Sondervariablen werden bei jeder Matching-Operation überschrieben! Ein Beispiel, wie man es nicht machen sollte: my $str1 = "aber hallo! hallo auch"; my $str2 = "hallo bla fasel"; if ( ( $str1 =~ /hallo/ ) and ( $str2 =~ /bla/ ) ) { print( $`, "\n" ); }
Der Grund liegt auf der Hand: Mit dem zweiten Matching-Operator wird $str2 durchsucht, in $' wird nun der String vor dem Treffer »bla« von $str2 abgelegt und dadurch geht der vorherige Inhalt verloren.
3.5.4 $+ Bitte verwechseln Sie diese Variable nicht mit der Array-Variable @+. $+ heißt in Langform $LAST_PAREN_MATCH und enthält nach einer Matching-Operation
denjenigen Treffer, der durch das letzte Klammernpärchen der letzten Matching-Operation angegeben ist: my $str1 = "a oder b"; my $str2 = "b oder a"; if ( $str1 =~ /(a|b)/ ) { print( "str1: \$+ ist '$+'\n" ); } if ( $str2 =~ /(a|b)/ ) { print( "str2 \$+ ist '$+'\n" ); }
Diese Variable ist, wie man sieht, sehr gut geeignet, wenn man mehrere reguläre Ausdrücke mit der ODER-Funktion verknüpft und bei einem Match nicht weiß, in welcher Dollarvariable der Treffer steht. Perl stellt noch ein paar andere spezielle Variablen für Pattern Matching zur Verfügung, deren Beschreibung jedoch den Rahmen dieses Buches sprengen würde. Deshalb möchte ich für weitergehende Lektüre die Themen »perlvar« sowie »perlre« der Online-Dokumentation empfehlen.
4 Komplexe Datentypen In diesem Kapitel, das im Prinzip direkt zur objektorientierten Programmierung führt, möchte ich Ihnen zeigen, was man aus nur drei Datentypen machen kann. Wir werden sehen, dass vor allem Hashes und Referenzen der universelle Schlüssel sind, um neue Datenstrukturen aufzubauen. An sich kennt Perl nur 3 Datentypen (Skalare, Arrays und Hashes). Aus diesen lassen sich jedoch beliebig komplexe Datenstrukturen bilden, wenn man die Tatsache ausnutzt, dass auch Referenzvariablen Skalare sind. Damit können Hash- und Array-Elemente auch Referenzen auf Objekte und sogar Funktionen enthalten. Als Erstes möchte ich Ihnen zeigen, wie man in Perl mehrdimensionale Arrays »zusammenbaut«.
4.1 Mehrdimensionale Arrays Aus C und anderen Programmiersprachen wie Java sind mehrdimensionale Arrays bekannt. So definiert man in C ein zweidimensionales Array zum Beispiel wie folgt: int ar[ 2 ][ 3 ];
Man kann dasselbe auch in Perl erreichen, indem man mit Referenzvariablen arbeitet: my @ar = ( [ 1, 2, 3, ], [ 10, 20, 30, ] );
Der Vorteil in Perl ist, dass sowohl der Datentyp der einzelnen Array-Elemente als auch die Anzahl von Array-Elementen für jede Dimension des Arrays unterschiedlich sein können. Dazu ein Beispiel: # # # # #
Definition eines Arrays, dessen erstes Element wiederum ein Array mit 2 unterschiedlichen Elementen ist. Das zweite Element ist ebenso ein Array, bestehend aus 5 unterschiedlichen Elementen.
Die im Beispiel verwendete Definition eines Arrays wäre in C oder anderen strikt typisierten Programmiersprachen unmöglich. Perl dagegen erlaubt, dass jedes einzelne Element eines Arrays ein beliebiger skalarer Datentyp ist. Außerdem dürfen die untergeordneten Arrays beliebig lang sein. In Java hätten diese zum Beispiel eine fest vorgegebene Länge. Und so adressiert man die Elemente eines mehrdimensionalen Arrays: # Definition eines zweidimensionalen Arrays my @ar = ( [ 1, 2, 3, ], [ 10, 20, 30, ] ); # Zugriff auf das 2. Element des 1. Elements # mit dem Dereferenzierungsoperator $ar[ 0 ]->[ 1 ] # Es geht aber auch so: $ar[ 0 ][ 1 ]
Streng genommen muss der Dereferenzierungsoperator -> verwendet werden, da jedes Element von »@ar« eine Array-Referenz ist. Perl macht aber bei aufeinander folgenden eckigen Klammern eine Ausnahme. In diesem Fall darf der Operator -> fehlen. Diese Ausnahme gilt übrigens auch bei geschweiften Klammern (Hashes), wie wir weiter unten noch sehen werden. Bei der folgenden Definition sind alle Ebenen des Arrays Referenzen (auch die Variable ar für die erste Ebene der Arrayhierarchie wird nun als skalare Referenzvariable auf ein anonymes Array deklariert): # Definition eines zweidimensionalen Arrays mit Hilfe # einer Referenzvariable my $ar = [ [ 1, 2, 3, ], [ 10, 20, 30, ] ]; # Hier muss in der obersten Ebene der Operator -> # verwendet werden, aber in allen weiteren Dimensionen # darf man den Operator weglassen. $ar->[ 1 ][ 2 ] # Dasselbe nochmal in ausführlicher Schreibweise $ar->[ 1 ]->[ 2 ]
Zur Veranschaulichung ein Beispiel für mehrdimensionale Arrays. Es werden Zeilen von STDIN gelesen (Beenden der Eingabe unter Windows mit ^Z und Zeilenende-Zeichen, unter UNIX mit ^D). Jede einzelne Zeile wird zu einem Array-Element, ist aber zugleich ebenfalls ein Array, dessen Elemente alle in der Zeile eingegebenen Wörter sind. Ein kleines Beispiel erleichtert das Verständnis: Sie geben ein: Perl ist so schön, dass ich eigentlich nur noch in Perl programmieren möchte. Java ist zwar heutzutage das "Non-plus-Ultra", aber ich glaube, dass es gegenüber Perl immer noch gravierende Nachteile hat.
Aus diesen Eingaben soll die folgende Arraystruktur gebildet werden: [ "Perl", "ist", "so", "schön" ], [ "dass", "ich", "eigentlich", "nur", "noch", "in", "Perl", "programmieren" ], [ "möchte", ], [ "Java", "ist", "zwar", "heutzutage", "das", "\"Non-plus-Ultra\"", ], [ "aber", "ich", "glaube,", "dass", "es", "gegenüber", "Perl", ], [ "immer", "noch", "gravierende", "Nachteile", "hat", ]
Der gesamte Eingabestrom wird in ein Array gegliedert. Jede Zeile entspricht einem Array-Element und ist ihrerseits wiederum ein Array, dessen Elemente alle Wörter der Zeile sind. Hier der Programmcode: #!D:/Perl/bin/perl.exe -w use strict; # Array, das die eingegebenen Zeilen aufnimmt my @lines = (); # Eingabeschleife, die so lange durchlaufen wird, # bis man auf der Tastatur Strg-D (UNIX) # bzw. Strg-Z (Windows) eingibt. while ( defined( my $line = <STDIN> ) ) { # Zeilenende-Zeichen entfernen chomp( $line ); # Die nächste Anweisung erzeugt zunächst eine # Wortliste,
# hängt an das Array ein Element an und erzeugt # daraus eine Array-Referenz, # welche die Wortliste aufnimmt. ${ $lines[ $#lines + 1 ] } = [ split( /[,.\s]+/, $line ) ]; }
Hier noch einmal die Langfassung der Zuweisung: ${ $lines[ $#lines + 1 ] } = [ split( /[,.\s]+/, $line ) ];
Die split()-Funktion erzeugt aus dem skalaren String einer Eingabezeile eine Liste aller Wörter. Als Kennzeichen für die Umwandlung dient der Pattern Matching-Ausdruck /[,.\s]+/. Ins Deutsche übersetzt bedeutet dies: Erzeuge eine Liste von Zeichenketten. Die Sonderzeichen »,«, ».« und alle »White Space«-Zeichen dienen als Trennzeichen und werden nicht mit in die Liste aufgenommen. Sie können auch mehrfach hintereinander folgen. Der String »hallo du« wird also in die Liste »( 'hallo', 'du' )« umgewandelt. Aber auch »hallo,..., ,.,,,du« erzeugt genau dieselbe Liste. Durch die eckigen Klammern wird aus der Liste eine anonyme Array-Referenz gebildet. Diese nun weist man mit dem Zuweisungsoperator = dem Ausdruck ${ $lines[ $#lines + 1 ] }
zu. Der Ausdruck $#lines + 1 bewirkt, dass das Array @lines um ein Element am Ende erweitert wird, dem dann die Arrayreferenz zugewiesen wird. Manchmal begegnet man Arrayreferenzen, die gar nicht danach aussehen. Zur Veranschaulichung schreibe ich die Umwandlung der Zeile in eine Wortliste ein bisschen anders: @{ $lines[ $#lines + 1 ] } = split( /\s+/, $line );
Der Code scheint falsch zu sein, weil Array-Element Skalare sein müssen. Wir erzeugen mit dem Code aber ein normales Array mit dem Typkennzeichen @. In Wirklichkeit jedoch handelt es sich um eine Referenz, die vom Perl-Interpreter automatisch erzeugt wird. Dies lässt sich mit Hilfe der Funktion ref() leicht beweisen: print( ref( $lines[ $#lines ] ) || "keine Referenz" );
Der Code gibt den String »ARRAY« aus, wenn das letzte Element von @lines eine ArrayReferenz ist, andernfalls wird »keine Referenz« ausgegeben. Wenn wir den Code ausführen, erhalten wir als Ausgabe ARRAY
Sie werden häufig in Programmen die eine oder die andere Schreibweise finden, je nachdem, welche Vorliebe der Autor des Codes hat. Wir hätten den Code auch wie folgt schreiben können: # Es geht auch so: while ( defined( my $line = <STDIN> ) ) { chomp( $line ); push( @lines, [ split( /[,.\s]+/, $line ) ] ); }
Wenn man den Code wie folgt ändert: original: push( @lines, [ split( /\s+/, $line ) ] ); falsch: push( @lines, split( /\s+/, $line ) );
dann hat man einen Fehler gemacht. In der zweiten Anweisung fehlen die eckigen Klammern, mit denen man die Liste in eine Arrayreferenz umwandelt. Die Folge: Alle Wörter der Liste werden in der Reihenfolge, in der sie eingegeben wurden, ans Ende des Arrays @lines angehängt. Dieses wird also mit jeder Zeile größer, und es entstehen keine untergeordneten Arrays für die Zeilen, sondern nur ein eindimensionales Array, das alle Wörter enthält. Ausgegeben wird das Zeilen-Array des Beispiels mit folgendem Code: foreach my $line ( @lines ) { # $line ist eine Array-Referenz! print( join( " ", @{ $line } ), "\n" ); } # Oder mit einer "for" Schleife for ( my $i = 0; $i <= $#lines; $i++ ) { print( join( " ", @{ $lines[ $i ] } ), "\n" ); }
Da jedes Element von @lines eine Referenz auf ein anonymes Array ist, muss man dem Interpreter durch Voranstellen des Typkennzeichens @ mitteilen, dass er die skalare Variable als Array behandeln soll. Wenn man in der foreach-Schleife den Wert von $line ändert, dann überschreibt man damit das entsprechende Element von @lines, weil in dieser Schleife keine Kopie des Arrays übergeben wird, sondern eine Referenz auf das Original! Anmerkung zur split()-Funktion: Beim Erzeugen der Wortliste mit split( /[,.\s]+/, $line )
heißt es aufpassen, wenn Trennzeichen am Anfang oder am Ende der Zeile vorkommen.
Gibt man aber ein: Punkt steht am Ende.
Wir wir sehen, erzeugt die split()-Funktion ein leeres Element, wenn das Trennmerkmal ganz am Anfang des Strings steht. Trennzeichen am Ende des Strings jedoch erzeugen überhaupt keine Elemente. Dieses Verhalten wird für alle eminent wichtig, die CSV-Tabellen bearbeiten müssen. Wir werden weiter unten in einem Anwendungsbeispiel noch einmal zu diesem Thema kommen.
4.2 Mehrdimensionale Hashes Auch Hashes lassen sich wie Arrays schachteln und so mehrdimensional erzeugen. Anhand eines einfachen Beispiels soll dies erläutert werden: Von STDIN sollen Zeilen eingelesen werden. In jeder Zeile muss man Nachname, Vorname, Geschlecht (m/w) und Alter eingeben. Die einzelnen Angaben sind durch Leerzeichen voneinander zu trennen. Zum Aufnehmen der Datensätze in ein Hash kann dieses nicht eindimensional sein, da alle Angaben mehrfach auftreten können. So ist der Nachname »Huber« zum Beispiel sehr häufig, und das Gleiche gilt für alle anderen Angaben. Hash-Keys müssen aber eindeutig sein (bzw. sind automatisch eindeutig, da ein neu angelegtes Element ein bereits existierendes überschreibt, wenn derselbe Key angegeben wird). Irgendeine Angabe muss aber als Key verwendet werden, um die Daten strukturieren zu können. Wenn wir festlegen, dass das Geschlecht als Key in der ersten Ebene verwendet wird, das Alter als Key in der zweiten Ebene, der Nachname als Key der dritten Ebene und der Vorname als Key in der vierten Ebene, dann erhalten wir ein vierdimensionales Hash, das in der letzten Ebene den Vornamen als Key und die Anzahl von Personen als Value hat.
Ich glaube, jetzt habe ich Sie mit Keys und Values und Dimensionen genug verwirrt. Da ist wohl ein Beispiel angebracht: # Eingaben Huber Franz m Huber Franz m Huber Martina Meier Franz m
Die eingegebenen Daten sollen folgende Hash-Struktur ergeben: Key Key Key Key m 30 Huber Franz Meier Franz w 30 Huber Martina
Man kann jedoch auch eine flache Struktur wählen und den gesamten Eingabestring einer Zeile als Hash Key verwenden. In diesem Fall muss dafür gesorgt werden, dass zwischen den einzelnen Angaben immer dasselbe Trennzeichen steht. Dieses Design ist wesentlich einfacher, allerdings soll mit dem Beispiel ja gerade die Möglichkeit einer komplexen Datenstruktur gezeigt werden. Wir werden gleich sehen, warum. Beispiel für ein flaches Design, bei dem der gesamte Eingabestring ein Key ist: Key
= Value (Anzahl gleicher Datensätze) "Huber Franz m 30" = 2 "Huber Martina w 30" = 1 "Meier Franz m 30" = 1
Warum ist das flache Design schlechter? Stellen wir uns vor, wir schreiben ein Programm für ein Sekretariat, mit dem Datensätze von Mitarbeitern der Firma verwaltet werden. Dazu gehört sicherlich auch eine Suchfunktion. Lassen Sie uns nun nach allen männlichen Mitarbeitern suchen, die 30 Jahre alt sind und deren Nachname »Huber« lautet. Wenn wir das flache Design gewählt haben, müssen wir alle Datensätze mit Hilfe von Pattern Matching und eventuell aufwändigen regulären Ausdrücken durchsuchen.
Im geschachtelten Design jedoch wählt man einfach das passende Hash aus, das automatisch alle gesuchten Datensätze enthält. Für unser Beispiel der Mitarbeiter heißt das: Nehme das Hash mit dem Key »m«, gehe dort zum Hash mit dem Key »30«, dann weiter zum Hash mit dem Key »Huber«. Man kann die Hierarchie der Hash-Struktur natürlich auch anders wählen, je nachdem, nach welchen Kriterien man suchen möchte. So könnte zum Beispiel das Alter als Kriterium in der ersten Ebene der Hash-Hierarchie gewählt werden, das Geschlecht in der zweiten Ebene. Nun wollen wir uns das Ganze in einem Beispielcode ansehen: # Hash für die Datensätze my %pers = (); # Eingabeschleife while ( defined( my $line = <STDIN> ) ) { # Zeilenende-Zeichen entfernen chomp( $line ); # Extrahieren der einzelnen Bestandteile # des Datensatzes # Trennzeichen der einzelnen Bestanteile ist # "white space" # $ln = Nachname, $fn = Vorname # $ge = Geschlecht, $age = Alter my ( $ln, $fn, $ge, $age ) = split( /\s+/, $line ); # Subhash für das Geschlecht anlegen unless ( exists( $pers{ $ge } ) ) { $pers{ $ge } = {}; } # Subhash für das Alter anlegen unless ( exists( $pers{ $ge }{ $age } ) ) { $pers{ $ge }{ $age } = {}; } # Subhash für den Nachnamen anlegen unless ( exists( $pers{ $ge }{ $age }{ $ln } ) ) { $pers{ $ge }{ $age }{ $ln } = {}; } # In den Values des untersten Subhashs # steht die Anzahl gleicher Datensätze. # Sie wird, falls noch kein passender # Datensatz eingelesen wurde, mit 0 initialisiert. unless ( exists( $pers{ $ge }{ $age }{ $ln }{ $fn }
) ) { $pers{ $ge }{ $age }{ $ln }{ $fn } = 0; } # Anzahl der gleichartigen Datensätze erhöhen # Wenn es sich um einen neuen Datensatz handelt, # ist der Wert nach der Erhöhung "1". $pers{ $ge }{ $age }{ $ln }{ $fn }++; }
Beachte: Streng genommen müsste man schreiben: $pers{ $ge }->{ $age }->{ $ln }->{ $fn }++;
da bis auf die erste Ebene alle Hash-Keys Referenzen auf Hashes sind. Aber wie bei Arrays kann der Operator -> entfallen, wenn zwei verschiedene geschweifte Klammern aufeinander folgen. Wer nicht weiß, welche Priorität höher ist (-> oder ++), der kann Prioritäten auch explizit durch runde Klammer setzen: ( $pers{ $ge }{ $age }{ $ln }{ $fn } )++;
Als Gegenüberstellung hier der Code für ein flaches Design, bei dem es nur ein Hash gibt: my %pers = (); while ( defined( my $line = <STDIN> ) ) { chomp( $line ); my ( $ln, $fn, $ge, $age ) = split( /\s+/, $line ); # Als Trennzeichen der einzelnen Bestandteile # im Hash-Key wird das TAB-Zeichen "\t" verwendet unless ( exists( $pers{ "$ge\t$age\t$ln\t$fn" } ) ) { $pers{ "$ge\t$age\t$ln\t$fn" } = 0; } $pers{ "$ge\t$age\t$ln\t$fn" }++; # Oder auch # ( $pers{ "$ge\t$age\t$ln\t$fn" } )++; }
Und hier der Code zum Ausgeben der Datensätze für das geschachtelte Design: 01 foreach my $ge ( sort( keys( %pers ) ) ) { 02 # Hilfsvariable $href, sie dient der Abkürzung 03 my $href = $pers{ $ge }; 04
foreach my $age ( sort( { $a <=> $b } keys( %{ $href } ) ) ) { # Hilfsvariable $hr, sie dient der Abkürzung my $hr = $href->{ $age }; foreach my $ln ( sort( keys( %{ $hr } ) ) ) { # Und noch einmal eine Abkürzung my $h = $hr->{ $ln }; # Dies ist wesentlich besser lesbarer als: # $pers{ $ge }{ $age }{ $ln } foreach my $fn ( sort( keys( %{ $h } ) ) ) { my $cnt = $h->{ $fn }; print( "$ln $fn $ge $age = $cnt\n" ); } } }
Erläuterungen: Im Beispiel für die Ausgabe der Datensätze wurden Hilfsvariablen benutzt, um eine abkürzende Schreibweise zu erreichen: Aus: $pers{ $ge } $pers{ $ge }{ $age } $pers{ $ge }{ $age }{ $ln }
Bei der abkürzenden Schreibweise muss der Operator -> hinter dem Variablennamen verwendet werden, da $href, $hr und $h Referenzvariablen sind. Die Ausgabe der Datensätze erfolgt sortiert (in Zeile 06 werden die Keys mit dem Operator <=> numerisch sortiert, in Zeile 11 wird die Standardsortierung verwendet, bei der die Keys lexikalisch aufsteigend sortiert sind). Beim flachen Design kann man dies nicht auf einfache Art erreichen: foreach my $key ( sort( keys( %pers ) ) ) { my ( $ge, $age, $ln, $fn ) = split( /\t/, $key ); my $cnt = $pers{ $key }; print( "$ln $fn $ge $age = $cnt\n" ); }
Hier ist auch das Alter Bestandteil der lexikalischen Sortierung, damit kommt jemand, der 7 Jahre alt ist, nach jemandem, der 12 Jahre alt ist, und nicht vorher. Dieses Problem könnte man beseitigen, indem man beim Erstellen der Keys das Alter mit führenden Nullen erzeugt und diese vor der Ausgabe wieder entfernt: my %pers = (); while ( defined( my $line = <STDIN> ) ) { chomp( $line ); my ( $ln, $fn, $ge, $age ) = split( /\s+/, $line ); # Alter dreistellig mit führenden Nullen $age = sprintf( "%03d", $age ); # Es gibt wohl kaum einen Menschen, # der älter als 999 Jahre ist. unless ( exists( $pers{ "$ge\t$age\t$ln\t$fn" } ) ) { $persons{ "$ge\t$age\t$ln\t$fn" } = 0; } $pers{ "$ge\t$age\t$ln\t$fn" }++; # Oder auch # ( $pers{ "$ge\t$age\t$ln\t$fn" } )++; } ... foreach my $key ( sort( keys( %pers ) ) ) { my ( $ge, $age, $ln, $fn ) = split( /\t/, $key ); $age =~ s/^0+//; my $cnt = $pers{ $key }; print( "$ln $fn $ge $age = $cnt\n" ); }
Mehrdimensionale Hashes können unter anderem für mehrsprachige Meldungen verwendet werden. Beispiel: my %messages = ( "de" => { "success" => "Erfolg", "error" => "Fehler", }, "en" => { "success" => "Success",
"error" => "Error", }, ); # Oder, nach einer anderen Hierarchie-Philosophie my %messages = ( "success" => { "de" => "Erfolg", "en" => "Success", }, "error" => { "de" => "Fehler", "en" => "Error", }, );
Die Variable %messages ist eine normale Hash-Variable (obwohl wir sie natürlich auch als Referenzvariable definieren könnten; in diesem Fall müssten wir geschweifte Klammern statt der runden verwenden). Die Keys der ersten Ebene sind Referenzen auf anonyme Hashes, die je nach Philosophie entweder auf die Sprachvarianten oder die symbolischen Namen der Meldungstexte zeigen. Die Hashes der untersten Ebene enthalten die eigentlichen Meldungstexte. Auch sie werden über Referenzen angesprochen. Je nach Philosophie enthalten die Keys entweder die symbolischen Namen der Texte oder die Sprachvarianten. Im Programm werden nur die englischsprachigen Identifier für die Meldungstexte verwendet, das Skript selbst ist also sprachunabhängig. Der Mechanismus kann in einem Perl-Skript wie folgt verwendet werden: ... # Hier fest verdrahtet Locale auf "de" eingestellt my $locale = "de"; # Hinweis: Bei CGI-Scripts kann man die Locale auch # dynamisch aus dem URI extrahieren. # Beispiel: # URI: /cgi-bin/de/myScript.pl bzw. # /cgi-bin/en/myScript.pl # myScript.pl ist physisch nur einmal vorhanden # (z.B. unter de), alle weiteren (z.B. unter en) sind # "symbolic links" # Man kann den Webserver auch so konfigurieren, dass # "symbolic links" überflüssig sind. # Das Extrahieren der Sprachvariante aus dem URI # erfolgt mit Pattern Matching.
Wenn z.B. der URI etwa so aussieht: /cgi-bin/de/myScript.pl oder /cgi-bin/en/myScript.pl dann kann man die Sprachvariante mit der folgenden Anweisung extrahieren: my ( $locale ) = $uri =~ m~/cgi-bin/(\w\w)/~; Voraussetzung: Die Sprachvariante ist immer in Form von 2 Zeichen angegeben.
my $href = $messages{ $locale }; ... if ( Operation erfolgreich } { my $msg = $href->{ "success" }; print( "$msg\n" ); } else { print( "$href->{ 'error' }\n" ); }
Im Skript werden nur die englischsprachigen Identifier für die Meldungen verwendet. Damit ist der Programmcode sprachunabhängig. Im letzten print()-Aufruf muss der Hash-Key »error« in einfache Quotes gestellt werden, da er in einem String verwendet wird, der in doppelten Quotes steht. Stattdessen kann man in neueren Perl-Versionen (> 5.005) auch schreiben: print( "$href->{ error }\n" );
weil Hash-Keys vom Interpreter grundsätzlich immer als Strings behandelt werden, auch wenn sie ohne Quotes als so genanntes Bareword stehen. Ein anderes Beispiel für mehrdimensionale Hashes: Personendaten, die in einer Datei oder in der Datenbank gespeichert sind. Die Sortierung bei der Ausgabe ist nicht vorbestimmt, sondern das Perl-Skript kann die Datensätze beliebig sortieren: # Format der Datensätze: ID\tNachname\tVorname\tAlter # Beispieldaten 1\tHuber\tErwin\t22 2\tHuber\tFranz\t30 ... # Einlesen der Datensätze zum Beispiel aus einer Datei: ... my %pers = (); # fileHandle kann STDIN (Standardeingabe) oder ein
# FileHandle einer geöffneten Datei sein, welche die # Datensätze (pro Zeile ein Datensatz) enthält while ( defined( my $line =