Dieser Beitrag stammt von Linuxfocus
von Guido Socher Über den Autor:
Inhalt: |
Zusammenfassung:
Perl Teil I
vermittelte einen allgemeinen Überblick über Perl. In Perl Teil II werden wir jetzt
unser erstes brauchbares Programm schreiben.
Perl eignet sich hervorragend dazu, kleine Programme zu schreiben, die auf eine bestimmte Aufgabe spezialisiert sind. Um den Entwicklungsprozess zu beschleunigen, ist es eine gute Idee ein Rahmenprogramm zur Hand zu haben, das einige grundlegende Strukturen und Funktionalitäten besitzt, die du in den meisten Programmen haben möchtest. Das folgende Code Template ermöglicht das Lesen von einfachen Kommandozeilenoptionen und enthält außerdem ein Unterprogramm, um Hilfemeldungen anzeigen zu können.
!/usr/bin/perl -w # vim: set sw=8 ts=8 si et: # # uncomment strict to make the perl compiler very # strict about declarations: #use strict; # global variables: use vars qw($opt_h); use Getopt::Std; # &getopts("h")||die "ERROR: No such option. -h for help\n"; &help if ($opt_h); # #>>your code<< # #-=-=-=-=-=-=-=-=-=-=-=-=-=-=- sub help{ print "help message\n"; exit; } #-=-=-=-=-=-=-=-=-=-=-=-=-=-=- __END__ |
Laß uns den Code anschauen. Das &getopts() ruft ein Unterprogramm in der
Bibliothek Getopt::Std auf, um die Kommandozeilenoptionen zu lesen. Es setzt, den Optionen
der Kommandozeile entsprechend, globale Variablen mit dem Namen $opt_<option>. Alle
Optionen auf der Kommandozeile beginnen mit einem "-" (Minuszeichen) und müssen
nach dem Programmnamen und vor irgendwelchen anderen Argumenten stehen (Beachte: dies ist
eine generelle Unixregel). Die Zeichenkette, die &getopts übergeben wurde (das
"h" im obigen Programm) listet alle Optionsbuchstaben auf, die erlaubt sind.
Wenn die Option ein Argument benötigt, dann muß nach dem Optionsbuchstaben ein
Doppelpunkt geschrieben werden. &getsopt("d:x:h") sagt, daß dieses Programm
die Optionen -d, -x und -h besitzt. Die Optionen -d und -x benötigen ein Argument.
Deshalb wäre "-d irgendetwas" richtig, aber "-d -x foo" ist falsch,
da hinter dem -d das Argument fehlt.
Wenn die Option -h in der Kommandozeile eingegeben wurde, dann wird die Variable $opt_h
gesetzt und &help if ($opt_h);
ruft deshalb das Unterprogramm help auf,
wenn die Option -h in der Kommandozeile eingegeben worden war. Die Anweisung sub
help{
deklariert das Unterprogramm. Es ist im Moment nicht so wichtig, daß du
jedes Detail des Codes verstehst. Verwende es einfach als Template, in das du deine
Hauptfunktionalität hinzufügen kannst.
Laß uns einen kleinen Zahlenkonvertierer schreiben, der dieses Template benutzt. Das
Programm, laß es uns numconv nennen, soll Hexadezimalzahlen in Dezimalzahlen umwandeln
und umgekehrt.
numconv -x 30
soll die Hexadezimalzahl ausgeben, die der Dezimalzahl 30
entspricht.
numconv -d 1A
soll die entsprechende Dezimalzahl zu der Hexadezimalzahl 1A
ausgeben.
numconv -h
soll den Hilfetext anzeigen.
Die Perlfunktion hex() wandelt Hexadezimalzahlen in Dezimalzahlen um und die Funktion
printf() kann dazu benutzt werden, Dezimalzahlen in Hexadezimalzahlen umzuwandeln. Dieses
bauen wir in unser Template ein und es ergibt ein nettes Programm:
#!/usr/bin/perl -w # vim: set sw=8 ts=8 si et: # # uncomment strict to make the perl compiler very # strict about declarations: #use strict; # global variables: use vars qw($opt_d $opt_x $opt_h); use Getopt::Std; # &getopts("d:x:h")||die "ERROR: No such option. -h for help\n"; &help if ($opt_h); if ($opt_d && $opt_x){ die "ERROR: options -x and -d are mutual exclusive.\n"; } if ($opt_d){ printf("decimal: %d\n",hex($opt_d)); }elsif ($opt_x){ printf("hex: %X\n",$opt_x); }else{ # wrong usage -d or -x must be given: &help; } #-=-=-=-=-=-=-=-=-=-=-=-=-=-=- sub help{ print "convert a number to hex or dec. USAGE: numconv [-h] -d hexnum umconv [-h] -x decnum OPTIONS: -h this help EXAMPLE: numconv -d 1af \n"; exit; } #-=-=-=-=-=-=-=-=-=-=-=-=-=-=- __END__ |
In den folgenden Abschnitten werden wir uns das Programm ein bißchen intensiver anschauen und versuchen, es zu verstehen.
Die if-Anweisung in Perl hat zwei mögliche Formen:
expr if (cond);
oder
if (cond) BLOCK [[elsif (cond) BLOCK ...] else BLOCK]
BLOCK ist eine Anzahl von Anweisungen, die in geschweifte Klammern eingeschlossen sind.
Dies bedeutet, daß du z.B. schreiben kannst:
printf("hello\n") if ($i); if ($i == 2){ printf("i is 2\n"); }elsif ($i == 4){ printf("i is 4\n"); }else{ printf("i is neither 2 nor 4\n"); } |
Wie in C ist es möglich, die Short Cut Operatoren && und || zu verwenden.
printf("hello\n") if ($i);
kann deshalb auch wie folgt geschrieben werden:
($i) && printf("hello\n");
Besonders das || , so wie es in unserem Template benutzt wird, läßt sich ganz gut in
normale Sprache übersetzen.
&getopts("d:x:h")||die "ERROR\n";
"Bekomm die Optionen oder stirb". Die Funktion die() ist entspricht der
Anweisung printf gefolgt von exit. Es gibt eine Nachricht aus und beendet das Programm.
&getopts("d:x:h")||die "ERROR\n";
ist äquivalent zu
die "ERROR\n"; if (! &getopts("d:x:h"));
wobei ! ein logischer Nicht-Operator ist. Dies kann wiederum wie folgt geschrieben werden
die "ERROR\n"; unless (&getopts("d:x:h"));
unless ist dasselbe wie if-not und ist schöner zu lesen als if(!..)
Wie du sehen kannst, gibt es mehr als nur einen Weg, um eine if-Anweisung in Perl zu schreiben. Du mußt sie nicht alle benutzen. Benutze die, die dir am besten gefällt.
Im ersten Perlartikel hatten wir gesehen, daß skalare Variablen (die $-Variablen) benutzt wurden, ohne sie zu deklarieren. Sie fangen in dem Moment an zu existieren, in dem sie benutzt werden. Dies ist ein nettes Feature für kleine Programme, aber es kann in größeren Programmen zu Fehlern führen, die nur schwer zu finden sind. Das Deklarieren von Variablen gibt dem Compiler die Möglichkeit, ein paar Extraüberprüfungen für Tippfehler durchzuführen.
"use strict;" zwingt dich, alles zu deklarieren. |
Betrachte das folgende korrekte Programmbeispiel:
#!/usr/bin/perl use strict; my $i=1; print "i is $i\n"; |
Dieses Programm ist richtig und produziert "i is 1". Jetzt nimm an, daß wir aus Versehen j statt i eingeben:
#!/usr/bin/perl # $i=1; print "i is $j\n"; |
Dieser Code wird ohne Fehlermeldung in Perl ausgeführt und produziert "i is ". Das Perlmodul "use strict;" kann den Compiler dazu zwingen, sich über ein solches Programm zu beschweren. Wenn du "strict" benutzt, dann muß alles deklariert werden, sonst wird eine Fehlermeldung ausgegeben.
#!/usr/bin/perl use strict; my $i=1; print "i is $j\n"; |
Dies verursacht die folgende Meldung und macht es einfach, den Tippfehler zu finden.
Global symbol "$j" requires explicit package name at ./vardec line 4. Execution of ./vardec aborted due to compilation errors. Exit 255
Variablen können in Perl durch "my" deklariert werden, oder, wie wir schon
in unserem Rahmenprogramm gesehen haben, durch "use vars qw()":
use vars qw($opt_h);
Globale Variablen werden durch use vars deklariert. Diese Variablen
sind sogar für alle eingeschlossenen Bibliotheken global.
Lokale Variablen der aktuellen Programmdatei (global für alle Unterprogramme dieser
Datei) werden mit my am Beginn des Programms (außerhalb eines
Unterprogramms) deklariert.
Lokale Variablen des aktuellen Unterprogramms werden mit my innerhalb des
Unterprogramms deklariert.
Leute, die Erfahrung in der Shellprogrammierung haben, sind vielleicht geneigt, daß $-Zeichen wegzulassen, wenn sie eine Variable deklarieren oder ihr einen Wert zuweisen. Dies ist in Perl nicht möglich. Man schreibt immer ein $-Zeichen, wenn man eine Skalarvariable benutzt, ganz egal, was man damit macht.
Beim Deklarieren kann man einer Variablen auch direkt einen Wert zuweisen. my $myvar=10; deklariert die Variable $myvar und setzt ihren Anfangswert auf 10.
Wir haben schon das Unterprogramme "help" in unserem obigen numconv Programm
benutzt. Unterprogramme können dazu benutzt werden, eigene Funktionen zu programmieren.
Sie helfen dir dabei, dein Programm zu strukturieren.
Ein Unterprogramm kann an einer beliebigen Stelle im Programmtext eingefügt werden (bevor
oder nachdem es aufgerufen wird, das ist egal). Ein Unterprogramm beginnt mit sub
name(){... und wird mit $retval=&name(...arguments...) aufgerufen. Der
Rückgabewert ist der Wert der zuletzt ausgeführten Anweisung im Unterprogramm. Die
Argumente, die dem Unterprogramm übergeben werden, werden durch das spezielle Feld @_ an
den Code innerhalb des Unterprogramms weitergegeben. Wir betrachten dies genauer, wenn wir
in Perl Teil III über Felder sprechen. Im Moment ist es ausreichend zu wissen, daß die
Werte von Skalarvariablen innerhalb des Unterprogramms durch shift gelesen werden
können. Hier ist ein Beispiel:
#!/usr/bin/perl use strict; my $result; my $b; my $a; $result=&add_and_duplicate(2,3); print "2*(2+3) is $result\n"; $b=5;$a=10; $result=&add_and_duplicate($a,$b); print "2*($a+$b) is $result\n"; # add two numbers and multiply with 2: sub add_and_duplicate(){ my $locala=shift; my $localb=shift; ($localb+$locala)*2; } |
Jetzt, nachdem wir einiges der Perlsyntax besprochen haben, ist es Zeit, ein richtiges
Programm zu schreiben.
Perl wurde entwickelt, um mit geringem Programmieraufwand Textdateien zu verändern. Unser
erstes Perlprogramm soll deshalb eine Liste mit Abkürzungen miteinander vergleichen und
diejenigen herausfinden, die doppelt sind. Damit meinen wir Abkürzungen, die mehrmals in
der Liste erscheinen. Die Liste sieht wie folgt aus:
Es ist einfach, Textdateien mit Perl zu verändern |
AC Access Class AC Air Conditioning AFC Automatic Frequency Control AFS Andrew File System ...
Die Syntax der Datei lautet:
Wie kann man eine solche Textdatei einlesen? Hier ist der Perlcode, um den Text Zeile für Zeile einzulesen:
.... open(FD,"abb.txt")||die "ERROR: can not read file abb.txt\n"; while( #do something } close FD; .... |
Die open nimmt einen file descriptor als erstes Argument und den Namen der Datei, die
eingelesen werden muß, als zweites Argument. File descriptors sind soetwas wie spezielle
Variablen. Du übergibst sie in die open, du benutzt sie in der Funktion, die die Daten
aus der Datei ausliest und übergibst sie schließlich an die close. Das Einlesen der
Daten geschieht mit <FD>. <FD> kann als Argument in eine while Schleife
übergeben werden und dies resultiert dann in einem Zeile für Zeile Einlesen.
Traditionell werden file descriptors in Perl in Großbuchstaben geschrieben.
Wo gehen unsere Daten hin? In Perl existieren einige implizite Variablen. Dies sind
Variablen, die du nicht deklariert hast. Sie sind immer da. Eine solche Variable ist $_.
Diese Variable speichert die Zeile, die gerade innerhalb der obigen while Schleife
eingelesen wird. Probieren wir es:
#!/usr/bin/perl use strict; my $i=0; open(FD,"abb.txt")||die "ERROR: can not read file abb.txt\n"; while(<FD>){ # increment the line counter. You probably # know the ++ from C: $i++; print "Line $i is $_"; } close FD; |
In der Variablen $_ steht die augenblickliche Zeile. |
Wie du sehen kannst, haben wir NICHT print "Line $i is $_ \n" geschrieben. Die $_ Variable speichert die aktuelle Zeile aus der Textdatei einschließlich des NeueZeile Zeichens (\n).
Jetzt wissen wir, wie man eine Datei einliest. Um tatsächlich unser Programm zu vervollständigen, müssen wir noch zwei weitere Dinge lernen:
Reguläre Ausdrücke sind hochentwickelte Werkzeuge, um Muster in einer Textzeichenkette zu suchen . Wir suchen in einer Zeile die erste Zeichenkette bis zum ersten Leerzeichen. In anderen Worten, unser Muster ist "Beginn der Zeile-->eine Anzahl von Zeichen, aber kein Leerzeichen-->ein Leerzeichen". In der Sprache der regulären Ausdrücken in Perl lautet dies ^\S+\s. Wenn wir dies in ein m//; setzen, dann wendet Perl diesen Ausdruck auf die $_ Variable an (Erinnere dich: diese Variable enthält die aktuelle Zeile, nett, nicht wahr?!). Das \S+ in den regulären AusdRücken stimmt mit "eine Anzahl von Zeichen, aber nicht das Leerzeichen" überein. Wenn wir Klammern um \S+ setzen, dann bekommen wir die "Zeichen, die keine Leerzeichen sind" zurück in die Variable $1. Wir können dies zu unserem Programm hinzufügen:
#!/usr/bin/perl -w # vim: set sw=8 ts=8 si et: # use strict; # global variables: use vars qw($opt_h); my $i=0; use Getopt::Std; # &getopts("h")||die "ERROR: No such option. -h for help.n"; &help if ($opt_h); # open(FD,"abb.txt")||die "ERROR: can not read file abb.txt\n"; while(<FD>){ $i++; if (m/^(\S+)\s/){ # $1 holds now the first word (\S+) print "$1 is the abbreviation on line $i\n"; }else{ print "Line $i does not start with an abbreviation\n"; } } close FD; # #-=-=-=-=-=-=-=-=-=-=-=-=-=-=- sub help{ print "help text\n"; exit; } #-=-=-=-=-=-=-=-=-=-=-=-=-=-=- __END__ |
Der Matchoperator (m/ /) gibt 1 zurück, wenn der reguläre Ausdruck erfolgreich in der aktuellen Zeile angewendet werden konnte. Wir können ihn deshalb innerhalb der if-Anweisung benutzen. Du solltest immer eine if-Anweisung um einen Matchoperator benutzen, bevor du $1 benutzt, um sicherzustellen, daß $1 wirklich gültige Daten enthält.
Jetzt können wir die Datei einlesen und die Abkürzungen herausfiltern und alles, was noch fehlt, ist, herauszufinden, ob wir die Abkürzung bereits vorher schon eingelesen haben. Hier brauchen wir einen neuen Datentyp von Perl: Hash Tabellen (Hash Tables). Hash Tabellen sind Felder, die durch eine Zeichenkette indiziert werden können. Wenn du die gesamte Hash Tabelle meinst, schreibst du ein % Zeichen vor den Variablennamen. Um einen individuellen Wert auszulesen, benutzt man $variable_name{"index_string"}. Wir benutzen dasselbe $ wie für andere skalare Variablen, da ein Feld innerhalb einer Hash Tabelle einfach eine normale skalare Variable ist. Hier ein Beispiel:
#!/usr/bin/perl -w my %htab; my $index; # load the hash with data: $htab{"something"}="value of something"; $htab{"somethingelse"}=42; # get the data back: $index="something"; print "%htab at index \"$index\" is $htab{$index}\n"; $index="somethingelse"; print "%htab at index \"$index\" is $htab{$index}\n"; |
Wenn wir dieses Programm laufen lassen, bekommen wir folgendes:
%htab at index "something" is value of something %htab at index "somethingelse" is 42
Jetzt ist unser Programm fertig:
1 #!/usr/bin/perl -w 2 # vim: set sw=4 ts=4 si et: 3 # 4 use strict; 5 # global variables: 6 use vars qw($opt_h); 7 my %htab; 8 use Getopt::Std; 9 # 10 &getopts("h")||die "ERROR: No such option. -h for help.n"; 11 &help if ($opt_h); 12 # 13 open(FD,"abb.txt")||die "ERROR: can not read file abb.txt\n"; 14 print "Abbreviations with several meanings in file abb.txt:\n"; 15 while(<FD>){ 16 if (m/^(\S+)\s/){ 17 # we use the first word as index to the hash: 18 if ($htab{$1}){ 19 # again this abbrev: 20 if ($htab{$1} eq "_repeated_"){ 21 print; # same as print "$_"; 22 }else{ 23 # this is the first duplicate we print first 24 # occurance of this abbreviation: 25 print $htab{$1}; 26 # print the abbreviation line that we are currently reading: 27 print; 28 # mark as repeated (= appears at least twice) 29 $htab{$1}="_repeated_"; 30 } 31 }else{ 32 # the first time we load the whole line: 33 $htab{$1}=$_; 34 } 35 } 36 } 37 close FD; 38 # 39 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 40 sub help{ 41 print "finddup -- Find abbreviations with several meanins in the 42 file abb.txt. The lines in this file must have the format: 43 abrev meaning 44 \n"; 45 exit; 46 } 47 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 48 __END__ |
Wie arbeitet es? Wir lesen die Datei Zeile für Zeile ein und speichern die Zeilen in unserer Hash Tabelle, %htab genannt, (Zeile 33). Der Index der Hash Tabelle sind die Abkürzungen. Bevor wir die Hash Tabelle laden, testen wir, ob schon etwas in der Hash Tabelle gespeichert ist (Zeile 18). Wenn schon etwas in der Hash Tabelle gespeichert ist, gibt es zwei Möglichkeiten:
Um zwischen den beiden Fällen zu unterscheiden, schreiben wir die Zeichenkette "_repeated_" in die Hash Tabelle, um zu markieren, daß wir bereits ein Duplikat in der Datei gefunden haben (Zeile 29).
In diesem Artikel hast du schon einige Details der Perlsprache gelernt. Wir haben noch nicht alle Datentypen besprochen, die Perl besitzt und du fragst dich wahrscheinlich, ob es möglich ist, das Hardcoden des Dateinamens "abb.txt" in unserem obigen Programm zu vermeiden. Du weißt bereits, wie du eine Option benutzen könntest, um dies zu vermeiden (z.B. finddup -f abb.txt). Versuch, das Programm zu ändern! Der allgemeine Weg, wie man eine Kommandozeile einliest und der Datentyp Feld (array) werden im nächsten Artikel besprochen.
<< Perl Teil I - Perl Teil III >>
Dem LinuxFocus-Team schreiben © Guido Socher LinuxFocus 1999 |
Autoren und Übersetzer:
|
1999-11-27, generated by lfparser version 0.9