Perl Teil II

ArticleCategory: [Es gibt verschiedene Artikel Kategorien]

Software Development

AuthorImage:[Ein Bild von Dir]

[Photo of the Author]

TranslationInfo:[Author and translation history]

original in en Guido Socher

en to de Katja Socher

AboutTheAuthor:[Eine kleine Biographie über den Autor]

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.

Abstract:[Here you write a little summary]

Perl Teil I vermittelte einen allgemeinen Überblick über Perl. In Perl Teil II werden wir jetzt unser erstes brauchbares Programm schreiben.

ArticleIllustration:[This is the title picture for your article]

[Illustration]

ArticleBody:[The article body]

Ein Rahmenprogramm für deine Programme

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.

Benutzen des Templates (Rahmenprogramm)

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.

If-Anweisungen

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.

Variablen

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.

Unterprogramme

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;
}

Ein richtiges Programm

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
...

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;
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:

  1. Wie man die Abkürzungen vom Anfang der Zeile einliest.
  2. Wie Hash Tabellen in Perl arbeiten.

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.

 

Hash Tabellen

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__ 
Du kannst das Programm herunterladen, indem du hier klickst.

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:

  1. Dies ist das erste Duplikat
  2. Es gab schon mehrere Duplikate von dieser Abkürzung
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).

Es ist wahrscheinlich das beste, den Code herunterzuladen und auszuprobieren.

Wie geht es weiter?

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.