original in en Guido Socher
en to de Katja Socher
Guido ist ein langjähriger Linuxfan und Perlhacker. Momentan ist er sehr damit beschäftigt, sein Haus zu renovieren so wie Salat und anderes Zeug im Garten anzupflanzen.
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__ |
Klick hier, um den
Programmcode
numconv, wie oben gezeigt, herunterzuladen.
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.
|
#!/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:
|
AC Access Class AC Air Conditioning AFC Automatic Frequency Control AFS Andrew File System ...
Du kannst die Liste hier
herunterladen. 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 (lade den
Code herunter):
#!/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; |
|
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:
Es ist wahrscheinlich das beste, den Code herunterzuladen und auszuprobieren.