Springe zum Hauptinhalt

Perl in der Praxis

Perl in der Praxis

Frank Richter, URZ, Januar 2008

Historie und Eigenschaften

Entwickelt seit Ende der 80er Jahre von Larry Wall (u.a. patch, rn)
  • UNIX-Administrator: Sammlung von Shell-Skripten und umfangreichen awk-Programmen
  • unzufrieden - entwickelte eigene Skriptsprache, bis er zufrieden war

Ergebnis: Perl - Practical Extraction and Report Language
  • Best of C, Shell, awk, sed ...
  • Veröffentlicht im Internet - enormer Zuspruch
  • GNU General Public License oder Artistic License
  • Die Skriptsprache von UNIX-Admins
  • WWW - CGI-Programme: Perl die erste Wahl

  • Schließt Lücke zwischen pragmatischer, aber oft ineffizienter/umständlicher Shell-Programmierung und performanter, aber recht umständlich handhabbarer Programmiersprache C
  • für "jede" Plattform
  • große Entwicklergemeinschaft
  • jede Menge Literatur
  • Newsgruppen comp.lang.perl, de.comp.lang.perl
  • WWW-Server: www.perl.com, www.perl.org

Großer Funktionsumfang:
  • Datentypen, Operatoren, Konstrukte, Funktionen, Klassen
  • Quasi-Standard: Perl regular expressions (PCRE)
  • kompakte, z.T. "kryptische" Notation
  • häufig mehrere Wege für eine Lösung
  • Module für jede Aufgabe: CPAN (Comprehensive Perl Archive Network)

Eingesetzt z.B. in
  • TWiki - Wiki-Autorensystem
  • OTRS - Open Ticket Request System

Konkurrenz:
  • Python als moderne Skriptsprache
  • PHP im WWW-Umfeld
  • Ruby (on Rails)

Interpreter-Sprache:
  • Zu Beginn wird gesamtes Skript eingelesen und syntaktisch geprüft.
  • -> Opcode - hohe Performance
  • kurzer Entwicklungszyklus: Text-Editor, Aufruf

Dokumentation und Literatur

Linux - englisch:
  • man perl
  • man perlfunc ...

  • perldoc -f print - Hilfe zu Funktion
  • perldoc -q muster-in-FAQ

WWW - englisch:

Bücher - deutsch:
  • RRZN-Handbuch: Perl - Eine Einführung (via URZ, 3 Euro)
  • Ditchen, Patrick: Perl - Schritt für Schritt zum Profi (in 21 Tagen)
  • Randal L. Schwartz, Tom Phoenix: Einführung in Perl (Learning Perl)

Formalitäten

  • Perl-Skript = Text
  • formatlos, d.h. Anzahl der Leerzeichen, Leerzeilen usw. egal
  • jede Anweisung mit Semikolon abschließen
  • Blöcke durch { ... }
  • # Kommentar bis Zeilenende - Kommentare sind sehr wichtig!

Aufruf

  • Skript in Datei hallo.pl
    print "Hallo Welt!\n";
    • Aufruf: % perl hallo.pl

  • "Shebang" - erste Zeile in hallo.pl
    #!/usr/bin/perl
    print "Hallo Welt!\n";
    • chmod +x hallo.pl
    • Aufruf: % ./hallo.pl

  • Von Kommandozeile: % perl -e 'print sin(1.5)'

  • Zum Test:
    % perl
    beliebiger Perl-Code
    ^D

  • Weitere Kommandozeilen-Optionen

perl -v Version
perl -w Skript Ausgabe von Warnungen - empfohlen!
perl -c Skript Syntax-Prüfung
perl -d Skript Debugger
perl -h weitere ...

Variablen (1)

Skalar genau ein Wert: Zahl, Zeichenkette $name
Array Liste von Werten, indiziert durch ganze Zahl @feld
Hash Liste von Werten, indiziert durch Zeichenkette (Assoziativfeld) %hash

  • müssen nicht zwingend deklariert und initialisiert werden - macht Perl implizit
  • toll für kleine Skripte
  • Initialisierung empfohlen für größere Projekte

use vars qw($zaehler @array);   # Deklaration von globalen Variablen
$zaehler = 0;                   # Initialisierung
@array = ();

Skalare

Immer $-Zeichen: bei Wertzuweisung und bei Zugriff auf Wert.

Zahlenwerte

$int = 123;          # ganze Zahl
$oktal = 040;        # Oktal, = 32 dezimal
$hexadezimal = 0x42; # Hexadezimal, = 66 dezimal
$float = 4.56;       # Dezimalbruch mit Punkt
$_12 = 12E-5;        # Exponentialdarstellung: "12 mal 10 hoch -5" = 0.00012

Arithmetische Operationen:
  • Zuweisung: =
  • Grundrechenarten: $a = 4 + 1; $b = ($a - 2) * 3;
  • Modulo %, Potenz **, Wurzel: print $b ** 0.5;
  • Kurzschreibweise: $b /= 3; # $b = $b / 3;
  • In-/Dekremtieren: ++$a; $b++; $c = $b--;
  • Reihenfolge (Präzedenz) vgl. C, am besten Klammern setzen

Numerische Vergleiche:
  • Gleich, ungleich: $b == 3; $b - 1 != 3
  • größer, kleiner (gleich): $alter >= 18

Mathematische Funktionen:
  • Winkelfunktionen: sin($x); cos($x); atan2($x, $y);
  • Exponential-, Logarithmusfunktion: $e = exp(1); log($e);
  • Konvertierung: $i = hex(16); print int($i/7);
  • Zufallszahl: print int(rand(10));   # zwischen 0 und 9 (inkl.)

Bitoperationen (ähnlich C): print 0x10 << 2 | 2;

Viele mathematische Zusatz-Module:
  • Integer-Zahlen beliebiger Länge: use Math::BigInt;

Zeichenketten

$string1 = '456';                 # Keine Substitution, außer \' = ', \\ = \
$string2 = "Hallo $string1!\n";   # Doppel-Apostroph: Variablen-Substition, \-Sonderzeichen
print "\e[38m Roter Text\e[0m\n"; # Escape-Sequenz
$html = <<END;                    # sog. Here-Dokument
<html><head>
 <title>$string2 ...
END
Automatische Konvertierung: $summe = $int + $string;        # 579

Zeichenketten-Operatoren:
  • Verkettung: $url .= '.html'; print '<a href="' . $url . '">';
  • Wiederholung: $trenner = '-' x 80;

Vergleichsoperatoren: eq - gleich, ne - ungleich: if ($string1 eq $string2) {...}

Vielzahl von Zeichenketten-Funktionen:
  $laenge = length($trenner);           # 80
  print lc($string2);                   # Kleinschreibung, uc = Großschreibung
  print ucfirst('gross und Stark');     # nur erster Buchstabve groß: Gross und Stark
  chomp($zeile);                        # entfernt abschließendes Newline
  $teil = substr('26 Juni 2007', 3, 4); # Teilzeichenkette: Juni
  substr($teil, 2, 1) = 'l';            # Ersetzen: Juli

Ausgabe / Formatieren:

$fahrenheit = 20;
$celsius = ($fahrenheit - 32) * 5 / 9;

print "$fahrenheit F = $celsius C\n";
# Besser: gerundet auf eine Nachkommastelle
printf("%d F = %5.1f C\n", $fahrenheit, $celsius);

Reguläre Ausdrücke:
  • if ($string =~ /muster/) { ... }
  • später ...

Spezial-Variablen, z.B.:
  • $0 - Skriptname
  • $_ - Standard-Variable, in Schleifen gesetzt, z.B. von Funktionen als Standard-Argument verwendet
  • $$ - Prozess-ID (wie in Shell)
    viele weitere ...

Datei-Testoperationen

  • Ähnlich test bzw. [ ... ] in der Shell:

-f name Name ist "normale Datei" if (-f $tmpname)
-d name Name ist Verzeichnis if (-d '/var/log' )
-r name Name ist lesbar if (-r '/var/log/messages')
-w name Name ist schreibbar if (-w '/etc/passwd') # Oha!
-s name Größe einer Datei in Bytes print -s '/etc/group';
Weitere: http://www.tu-chemnitz.de/docs/perldoc-html/functions/-X.html

Logische Operatoren

  • UND- bzw. ODER-Verknüpfung, NOT bzw. XOR:
if ($a > 0 && $a <= 100)          # $a zwischen 1 und 100 (inkl.), auch and
if ($s eq 'y' || $s eq 'j')       # $s gleich y oder j, auch or
if (! $s)                         # $s ist leer oder Null

Bedingte Anweisungen: $a < 10 && print "$a kleiner 10\n";

Weitere Operatoren: http://www.tu-chemnitz.de/docs/perldoc-html/perlop.html

Steueranweisungen

Verzweigungen

if (Bedingung) {
   # Anweisungen: wenn Bedingung wahr
} elsif (andere Bedingung) {
   # Anweisungen: wenn andere Bedingung wahr
} else {
   # sonst diese Anweisungen
}

Bedingung = logischer Ausdruck:
  • alles Mögliche: Vergleichsoperation, Muster, Datei-Testoperation, Funktionsruf
  • beliebig logisch verknüpft
  • falscher Wert: Zahl 0, Zeichenkette '0' oder leer
  • alles andere ist wahr

Bedingte Anweisung: print "$a kleiner 10\n" if ($a < 10);

Bedingungsoperator ? - wie in C:
  • $anrede = $geschlecht eq 'm' ? 'Herr' : 'Frau';
  • $max = $a > $b ? $a : $b;

  • Größere Abschnitte auskommentieren:
if (0) {
 ... nicht abgearbeiteter, aber interpretierter Kode - Syntax muss stimmen!
}

Schleifen

while (Bedingung) {        # Solange Bedingung wahr ist
   ...
}

until (Bedingung) {        # Solange Bedingung nicht wahr ist
   ...
}

do {
} while (Bedingung);

Auch hier bedingte Anweisungen:
  • print "$i. Zeile\n" while (++$i <= 5);
  • print lc($line) while $line = <STDIN>;

Zählschleife

for (Initialisierung; Abbruchbedingung; Operation) {
   ...
}
z.B. Zinsberechnung:
$guthaben = 1000;
for ($jahr = 1; $jahr <= 10; $jahr++) {
    $guthaben += $guthaben * 0.05;    # Zins 5 %
    printf "Jahr %2d: %8.2f\n", $jahr, $guthaben;  # runden
}

Schleife über eine Liste: foreach $var (liste) { ... }
foreach $stadt ('Leipzig', 'Dresden', 'Chemnitz') {
    print "$stadt\n";
}

Sprungbefehle

  • Verlassen des Programmes: exit wert;, mit Fehlermeldung: die "Fehler bei ...";
  • Vorzeitiges Ausspringen aus einer Schleife: last (break in C)
  • Springen zur Schleifenbedingung: next (continue in C)
  • labels, goto ...

# Listet Dateien im aktuellen Verzeichnis mit Größe
foreach $file (<*>) {       # file globbing liefert Datei-Liste lt. Shell-Muster: *, ?, []
    next if (! -f $file);   # überspringen, wenn keine normale Datei
    $size = -s $file;
    print "$file\t$size\n"; 
}
# Das Ganze sehr kompakt - die Standardvariable $_ kommt ins Spiel
foreach (<*>) { -f && print "$_\t", -s, "\n"; }

Variablen (2)

Arrays

Initialisierung:
  • @array = liste
    liste = ( wert1, wert2, ... );
    • @wtage = ('Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So');
      @wtage = qw(Mo Di Mi Do Fr Sa So); # identisch, qw = quoted words
    • @ziffern = (0 .. 9); # Bereichsoperator ..
    • @zeiten = split(':', '12:34:56');
      ($stunde, $minute, $sekunde) = split(':', '12:34:56');
    • zweidimensional: $matrix = ( ['00', '01'], ['10', '11']);

Zugriff:
  • $array[index] - index von 0 bis $#array
    • $stunde = $zeiten[0];
    • print "Die Woche geht von $wtage[0] bis $wtage[$#wtage].\n";
    • print "Es gibt " . ($#wtage + 1) . " Wochentage.\n";
    • @wochenende = @wtage[5,6];
    • $wert = $matrix[1][0];

Funktionen:
  • pop - entfernt das letzte Element und liefert es
  • push - fügt ein oder mehrere Element(e) "hinten" an
  • shift - entfernt das erste Element und liefert es
  • unshift - fügt ein oder mehrere Element(e) "vorn" an
  • splice - komplexe Operationen im Array

Beispiel: Argumente der Kommandozeile via @ARGV
$usage = "$0 datei1 datei2 ...";

if ($#ARGV <= 1) {
    die "Aufruf: $usage\n";
}
while (@ARGV) {    # alle Argumente
   $datei = shift @ARGV;
   # ...
}

  • Array in Zeichenkette wandeln: print join(' | ', @wtage), "\n";
  • vorher alphabetisch sortieren: print join(' | ', sort @wtage), "\n";
  • oder umkehren: print join(' | ', reverse @wtage), "\n";
  • Suchen: @mtage = grep(/M/, @wtage);

Hashes - Assoziativfelder

Initialisierung:
  • %hash = (key => value, ... )
    • %telefon = ('Meier' => '1234', 'Mueller' => '5678');
    • Kopieren: %telefon_neu = %telefon;
    • Mehrfach-Hashes:
          %telefon2 = ('Meier' =>   { 'work' => '1234', 'home' => '4321'},
                       'Mueller' => { 'work' => '1234', 'home' => '4321'}
                      );

Zugriff:
  • $hash{key}
    • print $telefon{'Mueller'}, "\n";
    • $dienst = $telefon2{'Meier'}{'work'};
    • Neuer Wert: $telefon{'Meier'} = '4321';
    • Neues Element: $telefon{'Schulze'} = '9876';
    • Element löschen: delete $telefon{'Mueller'};

Funktionen:
  • if (! defined $telefon{'Chef'}) { $telefon{'Chef'} = '1000'; }
  • keys %hash liefert Liste aller Schlüssel
  • values %hash liefert Liste aller Werte

Beispiel: Gib kompletten Hash aus:
foreach $name (keys %telefon) {
    print "Name: $name\tTelefon: $telefon{$name}\n";
}
# Besser: sortiert, mit Überschrift
print "Name\tTelefon\n";
foreach $name (sort keys %telefon) {
    print "$name\t$telefon{$name}\n";
}
# Jetzt: Sortiert nach Wert
print "Telefon\tName\n";
foreach $name (sort {$telefon{$a} cmp  $telefon{$b}} keys %telefon) {
                                # cmp ist alphabetischer Vergleichsoperator
                                # <=> ist numerischer Vergleichsoperator
    print "$telefon{$name}\t$name\n";
}

Zugriff auf Umgebungsvariablen via %ENV
$home = $ENV{'HOME'};        # Lesen
$ENV{'PATH'} .= ':.';        # Setzen

Referenzen (Zeiger)

  • Erzeugen:
$zeiger_s = \$s;
$zeiger_a = \@array; 
$zeiger_h = \%hash;
$zeiger_f = \&funktion;
  • Zeiger bleiben gültig, auch wenn die ursprüngliche Variable nicht mehr existiert.

  • Zugriff:
if ($$zeiger_s < 0) ...           # auch ${$zeiger_s}

@kopie = @$zeiger_a;              # Kopie des Arrays, auf das $zeiger_a zeigt
$first = $$zeiger_a[0];           # oder $zeiger_a->[0];
$last  = $$zeiger_a[$#$zeiger_a]; # oder $zeiger_a->[$#$zeiger_a];

$wert  = $$zeiger_h{'key'};       # oder $zeiger_h->{'key'};         

&$zeiger_f(1,2);                  # oder $zeiger_f->(1,2);

Umgang mit Dateien

Lesen

  • Öffnen belegt "Dateizeiger", Konvention: Großbuchstabe(n):
    • open F, 'dateiname' - öffnet dateiname zum Lesen
    • open F, '/etc/passwd' or die "Fehler beim Oeffnen: $!\n";
      - Abfangen eines Fehlers, $! ist Systemfehlermeldung

  • Lesen via Dateizeiger in "Diamantoperator":
    • $zeile = <F>; # eine Zeile
    • while ($zeile = <F>) { ... } # fuer jede Zeile
    • @inhalt = <F>; # gesamte Datei in Array, Zeilen indexierbar

  • Schließen: (sonst erst automatisch bei Programmende)
    • close F;
    • close F or warn "Fehler beim Schliessen: $!\n"   # mit Warnung bei Fehler

  • Vordefinierter Dateizeiger: <STDIN> - Eingabe (i.A. Tastatur)
    • $eingabe = <STDIN> # liest eine Zeile von Tastatur

# Datei ausgeben mit Zeilennummer:
print "Datei: ";
$datei = <STDIN>;  # Abfrage via Eingabe
chomp $datei;      # Newline am Ende wegschneiden
open F, $datei or die "Kann $datei nicht oeffnen: $!\n";
while (<F>) {
    # Ohne Variable --> verwende Standard-Variable $_
    print $., ' ', $_;       # $. = aktuelle Zeilennummer
}

  • Wenn Skript eine oder mehrere Datei(en), die auf der Kommandozeile angeben sind, oder Daten der Eingabe bearbeiten soll:
while ($zeile = <>) {
    print lc($zeile);      # alles in Kleinbuchstaben wandeln und ausgeben.
}
# Kurzform
while (<>) {       # Standard-Variable $_ verwenden
    print lc;      # Auch lc verwendet Standard-Variable ...
}

Schreiben

  • Öffnen:
    • open S, '> dateiname' - öffnet dateiname zum (Über-)Schreiben
    • open S, '>> dateiname' - Daten werden hinten angehangen

  • Schreiben via Dateizeiger:
    • print S "eine Zeile\n"; # eine Zeile
    • printf S "%d\t%s\n", $zahl, $string;

  • Schließen wie gehabt: close S;
# Neue Datei ausgeben mit Zeilennummern:
print "Datei: ";
$datei = <STDIN>;  # Abfrage via Eingabe
chomp $datei;      # Newline am Ende wegschneiden
open LIES, $datei or die "Kann $datei nicht oeffnen: $!\n";
open SCHREIB, "> $datei.num" or die "Kann $datei.num nicht zum Schreiben oeffnen: $!\n";
while (<LIES>) {
    print SCHREIB $., ' ', $_;       # $. = aktuelle Zeilennummer
}
close SCHREIB;
close LIES;
print "Datei $datei.num geschrieben.\n";

Funktionen zum Dateisystem

mkdir dir, mode Verzeichnis anlegen -d '/tmp/test' or mkdir '/tmp/test', 0755;
chmod mode, file Rechte ändern chmod 0750, 'dada';
unlink file Datei löschen unlink <*.bak>;

viele weitere

Aufruf von externen Programmen

  • system(kommando) führt kommando aus, mit Ausgabe, liefert exit-Status:
if (system("ls -l $dir") != 0) {
    print "Fehler bei Aufruf: $?\n";
}

  • `...` (Backtick wie Shell) oder qx(...) gibt Kommandoausgabe zurück: $etc =`ls -l /etc`;

Pipes

Lesen:
  • open PROG, "command |" or die "Kann command nicht starten: $_\n";

Schreiben:
  • open P, "| command" or die "Kann command nicht starten: $_\n";

# Transparentes Behandeln evtl. komprimierter Dateien
if ($datei =~ /\.gz$/) {   # dateiname mit .gz am Ende
    open F, "zcat $datei |" or die "Kann zcat nicht starten: $!\n";
} else {
    open F, $datei or die "Kann Datei nicht oeffnen: $!\n";
}
while (<F>) { ... }
close F;

Aufruf von Perl-Kode

eval Perl-Kode - führt Perl-Kode in eigenem Interpreter aus
  • z.B. zum Umgehen des Programmabbruchs bei Division durch 0
    eval { $resultat = $a / $b; }; warn $@ if $@;
  • "Breakpunkt" während Entwicklung: eval <STDIN>;

# Mini-Taschenrechner
do {
    print "Rechner: ";
    chomp($in = <STDIN>);
    $res = eval $in;
    if ($@) {   # Fehler von eval
        print "Ungueltige Eingabe\n";
    } else {
        print "Ergebnis: $res\n";
    }
} while ($in);

Funktionen

Definieren:
  • sub funktionsname { ... }
  • Argumente via @_ - Standard-Array
  • Rückgabe via return value
  • Lokale Variablen: my $var;
  • Rekursion möglich

sub summe2 {
    my ($a, $b) = @_;
    return $a + $b;    
}
sub summe {      # beliebige Anzahl der Argumente
    my $s = 0;
    foreach (@_) { $s += $_; }
    return $s;
}

Aufruf:
  • &funktionsname(argumente ...);
  • neuerdings auch ohne &
  • Reihenfolge Definition / Aufruf egal
print summe(1..1000), "\n";

Argumente per Referenz (Zeiger):
@grossdatei = <F>;          # gesamte Datei in array
&work(\@grossdatei);

sub work {
    my ($aref) = $@;
    print $aref->[0]; ...
}

Signalbehandlung:
  • Signal (z.B. durch Drücken von ^C) an ein Perl-Skript während der Ausführung führt normalerweise zum Abbruch.
  • Dies kann gesteuert werden, entweder um manche Signale zu ignorieren oder um vorm Abbruch noch Aktionen auszuführen.
  • Hash %SIG enthält als Schlüssel die Namen der Signale, z.B. INT für Interrupt-Signal (^C).
    Als Werte sind möglich:
    • DEFAULT = Abbruch
    • IGNORE = Kein Abbruch, Signal wird nicht beachtet
    • \&func = funktion func wird aufgerufen.

# Bei ^C soll eine temporäre Datei gelöscht werden, danach wird beendet.
$SIG{'INT'} = \&tmpdatei_loeschen;
...
sub tmpdatei_loeschen {
    unlink '/tmp/temp_datei';
    exit;
}

Reguläre Ausdrücke

  • beschreiben Muster von Zeichenketten, z.B. "Eine ID-nummer und eine Beschreibung" -> "vier Ziffern, gefolgt von beliebig vielen Leerzeichen, danach beliebige Zeichen außer Ziffern"
  • Zum Filtern (und Ersetzen) von bestimmten Daten aus einem "Datenstrom"
  • auch in anderen Linux-Programmen: awk, sed, grep, vim, ...

Bindungs-, Matchoperator und Ersetzungsoperator:
  • $string =~ m/reg. Ausdruck/;
  • Wenn "Trenner" / ist, kann m auch weggelassen werden, mit m beliebiger Trenner möglich
  • $string =~ s/reg. Ausdruck/Ersetzung/; - auch hier: beliebige Trenner

print "Abbruch? ";
chomp($eingabe = <STDIN>);
exit if ($eingabe =~ /j/);        # eingabe enthält j

if ($dateiname !~ m#/#) { ... }   # dateiname enthält KEIN /

while (<>) {
    print if (/Subject:/);        # aktuelle Zeile enthält Subject:
}

Aufbau von Mustern

"Joker" für einzelne Zeichen:
Muster-Zeichen Bedeutung Beispiel Erläuterung
. ein beliebiges Zeichen /M.st/ Mast, Mist, Most, aber auch M8st, aber nicht Morast
[abc] oder [0-9] ein Zeichen aus einer Gruppe /M[aio]st/ Mast, Mist, Most
[^A-Z] ein Zeichen nicht aus dieser Gruppe /M[^a]st/ nicht Mast
\d bzw. \D eine Ziffer bzw. ein Zeichen, aber keine Ziffer / \d\d:\d\d / "Leer, 2 Ziffern, :, 2 Ziffern, Leer"
\w bzw. \W ein Wortzeichen [0-9A-z_], bzw. kein Wortzeichen    
\s bzw. \S ein Leerzeichen [ \t\n\f\f] bzw. kein Leerzeichen    
\ hebt Sonderzeichen auf /datei\.html/ Sonderbedeutung von . aufgehoben

Quantifizierer - wie oft ein Zeichen oder eine Zeichenklasse?
Muster-Zeichen Bedeutung Beispiel Erläuterung
* vorheriges Zeichen/Muster beliebig oft, auch gar nicht /M.*st/ Mast, ..., auch Morast, aber auch Mst
+ vorheriges Zeichen/Muster mindestens einmal /\d+\s+/ eine Zahl, gefolgt von mind. einem Leerzeichen
? vorheriges Zeichen/Muster einmal oder gar nicht /Jan 0?\d/ Datum vom 1. bis 9. Januar, egal ob führende 0
{n} vorheriges Zeichen/Muster genau n Mal / \d{4} / eine vierstellige Zahl in Leerzeichen
{m,n} vorheriges Zeichen/Muster m bis n Mal / [a-z]{3,8} / drei- bis achtstellige Kleinbuchstabenfolge
{m,} vorheriges Zeichen/Muster mindestens m Mal / \w{8,} / Wörter mit mind. 8 Zeichen

  • matchen normalerweise "gefräßig" (greedy), so viele Zeichen, wie sie ins Muster passen
  • minimales Matchen durch Anhängen von ? an Quantifizierer
$htmltitel = '<title>Perl-Zauberei</title>';
$text = $text1 = $text2 = $htmltitel;
$text  =~ s/<.+>//;       # ersetze alles zwischen < und > durch nichts
$text1 =~ s/<.+?>//;      # das gleiche, nur "nicht gefräßig"
$text2 =~ s/<.+?>//g;     # das gleiche, nur "nicht gefräßig" und beliebig oft

print "$text\n$text1\n$text2\n";

Verankerung:
Muster-Zeichen Bedeutung Beispiel Erläuterung
^ Anfang der Zeichenkette m#^/# beginnt mit /
$ Ende der Zeichenkette /\.html$/ endet mit .html
\b bzw. \B Wortgrenze (Anfang oder ende) bzw. keine Wortgrenze /\bftp\b/ "Wort" ftp, aber nicht Luftpumpe

Gruppierung, Alternativen, Variablen:
Muster-Zeichen Bedeutung Beispiel Erläuterung
(muster) Zeichen gruppieren /(\d+\.\d+;){5}/ fünf durch Semikolon getrennte Kommazahlen
(muster1|muster2) Alternativen /(Jan|Feb|Mar)/ Jan, Feb oder Mar
$var bzw. ${var} Variable, wird ersetzt durch Wert    

Quotierung - Sonderzeichen flüchten:
  • Bei allen Sonderzeichen, die als normale Zeichen behandelt werden sollen, muss \ vorangestellt werden:
    if ($var =~ /\[\w{3}\]/) # [...]
  • Bei vielen: $klammern = quotemeta '()[]{}'; if ($ausdruck =~ /$klammern/) ...
  • Bei Verwendung von Variablen kann regulärer Ausdruck ungültig werden - hier flüchten mit \Q...\E:
    if ($zeile =~ /\Q$eingabe\E/) ...

Rückbezug (Backreferencing)

Auf durch Klammern zusammengefasste Teile kann wieder Bezug genommen werden: (muster)...\1
  • im regulären Ausdruck: /(muster)...\1/, z.B. /(\d)\1/ passt auf 11, 22, ...
  • beim Ersetzen: s/...(muster).../$1/, z.B.
    $datum =~ s/\b([A-Z][a-z]{2}) (\d{2})\b/$2. $1/; # Datum umformen: Jan 13 -> 13. Jan
  • später /...(muster).../; $var = $1;, z.B. if ($zeile =~ /\b([A-Z][a-z]{2})\b/) { $monat = $1; }

# Palindrome mit vier Buchstaben, z.B. ANNA
if (open(F, '/usr/share/dict/words')) {
    while (<F>) {
        $l = lc;                              # Zeile in Kleinbuchstaben
        print if ($l =~ /^(\w)(\w)\2\1$/);    # zwei Zeichen, dann 2. Zeichen und wieder 1. Zeichen
    }
}

Modifizierer
  • beeinflussen das Verhalten
  • werden hinten angefügt, hinter das letzte Trennzeichen, z.B. if (/error/i) { ... }
  • mehrere Modifizierer einfach hintereinander schreiben

Modifizierer Bedeutung Beispiel Erläuterung
g global, d.h. suche/ersetze alle Vorkommen des Musters $a = 'abba'; $a =~ s/a/e/g; -> ebbe, ohne g: ebba
i ignoriere Groß-/Kleinschreibung if (/error/i) findet error, Error, ERROR, ...
s single line, . passt auch auf \n    
m multi-line, ^ und $ passen auf Beginn/Ende jeder Zeile    
e eval - nur bei Ersetzen: der Ersetzungsteil wird als Perl-Ausdruck ausgewertet s/(\d+)/$1*10/ge; Ersetze Zahlen durch ihr Zehnfaches
x extended - Kommentare und Leerzeichen/Zeilumbrüche im Muster erlaubt s/(\d+) # Zahl/$1*10/gex;  

Beispiel: Wörter zählen
while (<>) {                 # Für jede Zeile 
     while (/(.+?)\s+/g) {   # mind. 1 beliebiges Zeichen bis Leerzeichen, 
                             # minimal matchen, merken, alle Vorkommen
         $c++;
     }
}
printf "$c %s\n", $c == 1 ? 'Wort' : 'Woerter';

Erweiterte reguläre Ausdrücke

Siehe http://www.tu-chemnitz.de/docs/perldoc/perlre.html#Extended-Patterns

Reguläre Ausdrücke in split

z.B. Aufsplitten an beliebigen Leerzeichen: @teile = split(/\s+/, $zeile);

Beispiel: Auswertung Apache-Log-Datei

Common Log Format (CLF), in httpd.conf: LogFormat? "%h %l %u %t \"%r\" %>s %b" common
z.B.
# Klient      - user Datum       Zeit            Request                     Status Bytes
134.109.200.2 - fri [15/Jan/2008:13:55:36 +0100] "GET /apache_pb.gif HTTP/1.0" 200 2326

Ein möglicher regulärer Ausdruck: ^\S+ - \w+ \[.+?\] "\S+ \S+ \S+" \d+ \d+

while (<>) {    # zeilenweise für alle Dateien der Kommandozeile
    #     Klient  user    Datum/Zeit      Dokument   Status Bytes
    if (/^(\S+) - (\S+) \[(\S+) .+\] "\S+ (\S+) \S+" (\d+) (\d+)/) {
        $bytes += $6;       # Bytes summieren
        $HOSTS{$1}++;       # Hostzugriff merken
    } else {
        print STDERR "Fehler: $_";
    }
}
# Auswertung:
print "Daten: $bytes Bytes\n\n";
# Sortieren: Welcher Klient hat am meisten geholt:
print "Zugriffe : Klient\n";
foreach $h (sort {$HOSTS{$b} <=> $HOSTS{$a}} keys %HOSTS) {
    printf "%8d : %s\n", $HOSTS{$h}, $h;
}

Umgang mit Zeichensätzen

  • Nationale Sonderzeichen, z.B. Umlaute, sind in verschiedenen Zeichensätzen festgelegt, z.B. ISO-8859-1 = Latin1
  • Müssen von Zeichenkettenfunktionen und regulären Ausdrücken beachtet werden.

# Beispiel für Arbeit mit Umlauten im ISO-8859-1-Zeichensatz = Latin1

# seit perl 5.8: Daten sind Latin1, Bildschirmausgabe aber UTF-8-Zeichen
use encoding latin1, STDOUT => "utf8";

# Hier ISO-8859-1 kodiert:
$umlaute = 'Umlaute sind äöüÄÖÜ(ß)';

#      z.B. \w umfasst auch Umlaute
($entfernt = $umlaute) =~ s/\w+//g;

print "Normal: $umlaute" .
      "\nGross:  " . uc($umlaute) .
      "\nKlein:  " . lc($umlaute) .
      "\nEntfernt: .$entfernt.\n";

  • Unterstützung für Unicode seit Perl 5.6, ab 5.8 richtig
  • Praktisch ist Kodierung UTF-8 interessant.
  • Problem: ein Zeichen ist mglw. länger als ein Byte
    -> Anpassung für Zeichenkettenfunktionen und reguläre Ausdrücke

#!/usr/bin/perl
# Beispiel für Arbeit mit Umlauten: UTF-8 kodiert

use encoding 'utf8';     # seit Perl 5.8
# use utf8;              # nur nötig, wenn z.B. auch Variablennamen UTF-8 einhalten
use open ':utf8';        # zu öffnende Dateien enthalten UTF-8-Daten
  
# Hier UTF-8 kodiert:
$umlaute = 'Umlaute sind äöüÄÖÜ(ß)';

($entfernt = $umlaute) =~ s/\w+//g;     # \w umfasst auch UTF-8-Zeichen

print "Normal: $umlaute" .
      "\nGross:  " . uc($umläute) .
      "\nKlein:  " . lc($umläute) .
      "\nEntfernt: .$entfernt.\n";

Verwendung von Modulen

  • Viele Module für Standard-Aufgaben sind in der Standard-Installation enthalten.
  • hierarchisch: Gruppe::Modul, z.B. File::Copy
  • Listet installierte Module: perldoc perlmodlib
  • Dokumentation zu Modul: perldoc Modulname, z.B. perldoc CGI
  • Tausende Module via http://www.cpan.org

Verwendung:
  • use Modulname; - alle Funktionen importieren
  • use Modulname qw(func1 func2); - nur bestimmte Funktionen
  • einmalig externe Datei laden (wie #include in C): require 'Dateiname';

Beispiel: Programmargumente mit Optionen verarbeiten:
use Getopt::Std;
# Aufruf:  perlskript [-d] [-o datei]
getopts('do:');         # -d ohne Argument, -o mit Argument

$debug = $opt_d;        # 1, wenn -d vorhanden
if ($opt_o) {           # Wenn -o vorhanden
    $output = $opt_o;   # verwende Wert
}
...

Mehr: perldoc Getopt::Std; perldoc Getopt::Long

Objekte und Klassen

  • Klassen werden als package realisiert (in Modulen)
  • Objekte werden über Konstruktor erzeugt: $obj = Class->new();
  • So auch Methoden: $obj->methode();
  • Zugriff auf Objekt-Attribute: $obj->{attr};

Mehr: http://www.tu-chemnitz.de/docs/perldoc/perlobj.html

Beispiel: Kleine Oberfläche mit Modul Tk
use Tk;
my $fenster = MainWindow->new;      # Hauptfenster erzeugen
$fenster->geometry('300x150');      # Größe

# Ein Knopf mit Aktion
$fenster->Button(-text => 'Hallo Welt!', -command => \&action)->pack();

# "Warteschleife"
MainLoop;

sub action { print "Hallo Du!\n"; exit; }

Webprogrammierung

CGI-Programme (Common Gateway Interface) für dynamische WWW-Seiten:
  • WWW-Browser fordert via HTTP Daten ab
  • WWW-Server startet Perl, führt Skript aus und übergibt ggf. Daten
  • Skript führt Aktionen aus (z.B. Datenbank-Abfrage) und gibt HTML-Seite aus
  • WWW-Server schickt diese an Browser

#!/usr/bin/perl
# erstes CGI-Skript - ohne CGI.pm

print "Content-Type: text/html\n\n";

print <<HTML;
<html><head><title>Mein erstes CGI-Skript</title></head>
<body>
<h1>Hallo Welt!</h1>
</body>
</html>
HTML

  • Skript in Webspace kopieren, für TU Chemnitz: Endung .cgi
  • Ausführbar: chmod +x script.cgi

Modul CGI definiert HTML-Abkürzungen, Hilfe zur Formularauswertung, Cookies etc., siehe perldoc CGI

#!/usr/bin/perl
# CGI-Skript - mit CGI.pm

use CGI qw(:standard);
print header,
      start_html('CGI-Skript mit CGI.pm'),
      h1('Hallo Welt!'), end_html;

Formulare und deren Auswertung via Funktion param()
#!/usr/bin/perl
# CGI-Skript mit einfachem Formular und Auswertung

use CGI qw(:standard);
use HTML::Entities 'encode_entities';   # Kodierung von HTML-Sonderzeichen
print header,
      start_html('Englisches W&ouml;rterbuch'),
      h1('Englisches W&ouml;rterbuch'),
      start_form,
      'Gesuchtes Muster: ', input({'name'=>'frage'}),
      submit,
      end_form,
      hr;

if (param()) {      # Sind Werte übergeben worden? = Formular abgeschickt
    my $suchwort = param('frage');   # Zugriff auf formularwert
    if ($suchwort) {
        print 'Suche nach ', encode_entities($suchwort);
        print pre(suche($suchwort));        # schreibe Ergebnisse in <pre>
    }
}
print end_html;

sub suche {
    my ($wort) = @_;
    my $ergebnis = '';
    if (! open F, '/usr/share/dict/words') { 
        return "Kann Datei nicht &ouml;ffnen: $!\n";
    }
    while (<F>) {
        $ergebnis .= $_ if (/\Q$wort\E/);  # Sonderzeichen flüchten
    }
    close F;
    $ergebnis =~ s#$wort#<b>$wort</b>#g;   # Suchwort fett markieren: <b>...</b>
    return $ergebnis;
}

Arbeit mit SQL-Datenbanken

Schnittstelle zu verschiedenen SQL-Darenbanken via Modul DBI, siehe perldoc DBI
#!/usr/bin/perl
# Beispiel zum Zugang zu MySQL-Datenbanken

use DBI;
# DB-Verbindung          Typ  Datenbank  Server             Login Passwort
$dbh = DBI->connect('DBI:mysql:events:www-db.tu-chemnitz.de', '', '')
   or die "Kann Datenbank-Verbindung nicht herstellen: $DBI::errstr\n";

# Abfrage vorbereiten
my $sth = $dbh->prepare('SELECT * FROM event WHERE startdate >= NOW() ORDER BY startdate LIMIT 5');

if ($sth->execute) {              # Abfrage starten
    # DBI::dump_results($sth);    # Zum Test "rohe" Ausgabe

    while (my $ref = $sth->fetchrow_arrayref) {     # Ergebnisse "abholen"
        print " Titel: $$ref[1]\nBeginn: $$ref[4]\n\n";
    }
}

$dbh->disconnect();         # DB-Verbindung beenden

Mehr: perldoc DBD::mysql