Lauter kaputte Links in Webseiten, nur weil bei ihrer Erstellung Groß- und Kleinschreibung unbeachtet blieb? Dieses Problem lässt sich zum Beispiel mit einem Perl-Skript lösen.
The Answer Girl
Dass der Computeralltag auch unter Linux des Öfteren für Überraschungen gut ist, ist eher eine Binsenweisheit: Immer wieder funktionieren Dinge nicht oder nicht so, wie eigentlich angenommen. Das Answer-Girl im LinuxUser zeigt, wie man mit solchen Problemchen elegant fertig wird.
Endlich ist die in Auftrag gegebene Website fertig. Aufatmen im Büro – doch die Ernüchterung folgt auf den Fuß: Die Webdesignerin hat unter Windows gearbeitet und es mit der Groß- und Kleinschreibung von Dateinamen nicht so genau genommen, denn auf ihrem Microsoftschen Testrechner sind index.htm, Index.htm und INDEX.HTM identische Schreibweisen für eine einzige Datei. Unix-Dateisysteme wie die unter Linux hauptsächlich verwendeten ext2fs und ReiserFS bestehen hingegen darauf, dass ein großes A und ein kleines a auch in Dateinamen völlig unterschiedliche Dinge sind.
Doch der Relaunch der Seiten soll fristgerecht erfolgen, und wer mag sich jetzt noch hinsetzen, um in mehreren Dutzend Dateien all die falschen A HREF-Angaben per Hand zu korrigieren? Stellt sich also die Frage, wie sich die ganze Geschichte automatisiert erledigen lässt.
Aufgabe abstecken
Ganz trivial ist die Aufgabe nicht, denn es gibt Einiges zu tun: Zunächst gilt es, alle Verweise zu finden, die rauszusuchen, die sich auf lokale Dateien beziehen, den entsprechenden Dateinamen samt Pfad zu extrahieren und zu überprüfen, ob sich an der passenden Stelle im Dateisystem ein File dieses Namens befindet.
Stimmen die Bezeichnungen der Datei im Link und im Dateisystem überein, haben wir nichts zu tun. Sind sie vollkommen verschieden, fügen wir am besten einen Kommentar ein, dass an dieser Stelle manuell nachbearbeitet werden muss. Unterscheiden sich beide Angaben nur in der Groß- und Kleinschreibung, passen wir den Dateinamen in der Linkangabe an.
Das sieht nicht nach etwas aus, was sich einfach mit ein paar Kommandozeilentools und ein paar Pipes lösen lässt. Ehe wir uns hier verrenken, machen wir es lieber richtig und schreiben ein kleines Skript.
Ein Shell-Skript, ein sed-Skript, ein awk-Skript, ein … – der Möglichkeiten gibt es viele, doch damit der Artikel nicht allzu lang wird, einigen wir uns auf ein Perl-Skript. Das bietet sich an, denn erstens müssen wir im Wesentlichen suchen und ersetzen, wobei uns die Perlschen regulären Ausdrücke die Arbeit erleichtern. Das ginge zwar auch mit sed, doch da wir das Vorhandensein der Dateien im Dateisystem überprüfen müssen, käme sed nur mit Hilfe anderer Shell-Tools zurecht. Perl hat hier Vorteile, da es als “richtige” Programmiersprache auch Funktionen zum Zugriff auf das Dateisystem kennt und zudem schneller als ein Shell-Skript ist.
awk spielt seine Stärken speziell dann aus, wenn man spaltenorientiert arbeitet, was wir in diesem Fall nicht wollen. Doch sei an dieser Stelle betont, dass der Einsatz eines bestimmten Werkzeugs für eine bestimmte Aufgabe immer von den persönlichen Vorlieben abhängt. Wenn dieses bei Ihnen Python oder Tcl heißt, ist das vollkommen in Ordnung.
Leider hat Perl auch Nachteile: Obwohl (oder gerade weil) es massenweise Manpages und Dokumentation im Web wie in Buchform gibt, ist das Finden von Hilfe zu einer bestimmten Aufgabe eine sehr zeitraubende Beschäftigung. Da Perl zudem “menschlich” sein will, indem es mehrere Schreibweisen für eine bei anderen Programmiersprachen feste Syntax zulässt, wird zwar das Schreiben von Perl-Code auf den ersten Blick einfacher, doch das Lesen speziell dann erschwert, wenn ein Perl-Skript von Leuten mit anderen Perl-Gewohnheiten stammt. Diese Vielfalt macht das Perl-Lernen natürlich nicht leichter.
Perlen im Einsatz
Doch alles Lamentieren nutzt nichts, wenn der Release-Termin für die neue Website wartet. Also ran an den Lieblingseditor und eine neue Datei erzeugt. Nennen wie sie in schönstem Denglisch cgks als Abkürzung für “change Groß-Kleinschreibung” (denn wer will schon ein Programm aufrufen, das mit einem “ä” wie “ändere” beginnt).
Wie bei jedem Skript fällt die erste Zeile leicht: Sie besteht aus einem speziellen “Kommentar”, der besagt, welcher Interpreter hier seine Arbeit verrichten soll. Mit which perl finden wir heraus, in welchem Pfad der Perl-Interpreter sich befindet (vorausgesetzt, er ist installiert und das entsprechende Verzeichnis ist im Suchpfad eingetragen).
Damit hätten wir schon einmal
#!/usr/bin/perl
dastehen. Nur tut man sich beim Entwickeln von Programmen leichter, wenn der Interpreter auch ein bisschen mehr auf Fallstricke hinweist, als nur über echte Syntaxfehler zu meckern. Dazu sollte doch die Manpage Informationen hergeben. Doch man perl erklärt uns zunächst, dass das Perl-Manual “um den Zugriff einfacher zu machen in einzelne Sektionen aufgeteilt ist”, die als extra Manpages zu erreichen sind.
perlrun Perl execution and options
sieht nach der Sektion aus, die wir brauchen, um mehr über Optionen zu erfahren, die das Debuggen erleichtern. Tatsächlich: man perlrun erklärt eine Option -w (wie “warnen”; Listing 1 gibt die Erläuterung auf deutsch wieder), die für unsere Absicherung geeignet scheint.
Listing 1
Was tut
perl -w
?
(Auszug aus der perlrun-Manpage, ins Deutsche übersetzt)
-w warnt vor Variablennamen, die nur ein einziges Mal
vorkommen und Skalar-Variablen, die benutzt werden,
bevor sie überhaupt gesetzt wurden. Gibt des Weiteren
Warnungen aus, wenn Subroutinen mehrfach definiert,
nicht definierte Datei-Handles referenziert werden oder
auf nur-lesbare Datei-Handles geschrieben werden soll.
Weitere Warnungen gelten Werten, die als numerische Werte
benutzt werden, aber keine Zahlen sind, Arrays, die
wie Skalare benutzt werden, Subroutinen, die mehr als
100 Mal rekursiv aufgerufen werden und unzähligen
anderen Problemen.
Alles nur geklaut
Jetzt wollen wir viele Dateien bearbeiten, am besten alle, die im aktuellen Verzeichnis stehen. Sowas sollte doch auch schon vor uns jemand mal gemacht haben. Perl-Skripte gibt es auf dieser Welt und im WWW viele, das mit der verständlichen Dokumentation ist hingegen so eine Sache. Ein Lichtblick für Neu-Perlianer/innen ist Brigitte Jellineks “Perlwelt” [2], die in kleinen, gut überschaubaren Perl-Skripten praktisch relevante Aufgaben löst.
Hier finden wir unter Viele Webseiten ändern[3] ein Skript, das in allen HTML-Dateien im aktuellen Verzeichnis die Umlaut-Entities in echte ISO-Umlaute umwandelt (Abbildung 1) und mit
$^I = ".bak";
zuvor sogar immer eine Backup-Datei mit der Endung .bak anlegt.
Dieses letzte Feature markieren wir zunächst einmal mit einem # am Zeilenanfang so, dass der Interpreter es nicht beachtet. Indem wir die Zeile auskommentieren, ergibt sich als netter Nebeneffekt, dass wir einstweilen noch gar keine Dateien schreiben, sondern das Ergebnis auf der Standardausgabe angezeigt bekommen. Zum Testen ohnehin viel besser!
In Perl beginnen einfache (skalare) Variablen immer mit einem Dollarzeichen, und eine so komische Variable wie $^I muss schlichtweg etwas Vordefiniertes sein. Tatsächlich erklärt man perlvar, dass damit das Inplace-Editing, also das Editieren der gerade bearbeiteten Datei ein- oder abgeschaltet wird.
Auch die nächste Zeile,
@ARGV = <*.html>;
sieht nach einer vordefinierten Variablen aus, wegen des vorangestellten @ ein Array, also ein ein- oder mehrdimensionales Wertefeld. @ARGV, der “Argumentvektor”, ist eindimensional und enthält laut der perlvar-Manpage die Kommandozeilenargumente des Skripts. Wir sind also sehr gewitzt und definieren erst innerhalb des Programms, mit welchen Argumenten es eigentlich aufgerufen wird: natürlich mit allen, die auf .html enden.
Perl sorgt bereits von sich aus dafür, dass die Argumentdateien geöffnet werden und man über das Handle<> Zugang zu den darin enthaltenen Daten bekommt. Wir müssen den Inhalt also lediglich zeilenweise abgreifen, bis es keine Zeilen mehr gibt:
while( $zeile = <> ) { }
Ein klarer Fall für eine Schleife, die immer wieder durchlaufen wird, solange (while) die in runden Klammern stehende Bedingung stimmt. Ob wir die zur Zwischenspeicherung benötigte Variable $zeile wie in [3] vorab mit der my()-Funktion deklarieren oder sie erst da entstehen lassen, wo wir sie brauchen, spielt bei Perl keine Rolle. Erst im Zusammenhang mit objektorientierter Perl-Programmierung wird my() wirklich wichtig. Allerdings schadet es nicht, sich von vornherein daran zu gewöhnen. Wenn wir den Inhalt von $zeile innerhalb der geschweiften Klammern mit
print $zeile;
wieder ausgeben, sollte unser Skript einfach den Inhalt der .html-Dateien im aktuellen Verzeichnis Zeile für Zeile ausgeben. Testen wir das in einem Verzeichnis, das (nicht zuviele) HTML-Files enthält. Da cgks vermutlich nicht im Suchpfad liegt, geben wir den Pfad mit an (z. B. den Punkt als Abkürzung für das aktuelle Verzeichnis).
pjung@chekov:~/answergirl$ ./cgks bash: ./cgks: No such file or directory
Keine Datei, kein Verzeichnis dieses Namens? Da ist etwas faul. Wir haben wohl vergessen, uns selbst mit chmod u+x cgks Ausführbarkeitsrechte zuzugestehen.
Muster erkennen
Da uns das Skript brav die Dateiinhalte ausgibt, können wir nun die Links raussuchen. Perl kennt das schöne Konstrukt der “per Default zu bearbeitenden Daten”, die sich in der Variablen $_ verstecken. Wenn man nach etwas sucht, braucht man gar nicht angeben, worin zu suchen ist, wenn man den Inhalt von $_ meint. Wir wollen den Inhalt von $zeile bearbeiten und legen ihn daher mit
$_ = $zeile;
im Default ab. Gibt es Links in dieser Zeile? Wenn ja, folgen die auf ein <A HREF= innerhalb von Gänsefüßchen (“). Den Schluss bildet ein >. (Um das Skript nicht unnötig zu verkomplizieren, gehen wir davon aus, dass keine Zeilenumbrüche in dieser Zeichenfolge enthalten sind.) Als regulärer Ausdruck sieht das so aus:
<A HREF=\"(.*)\">
Die Gänsefüßchen müssen wir mit dem Backslash escapen, da sie auch in Perl zum Begrenzen von Stringinhalten benutzt werden. In runden Klammern merken wir uns die Referenz (entweder eine URL oder eine lokale Dateiangabe), eine beliebig lange Folge beliebiger Zeichen, .* abgekürzt.
Leider haben reguläre Ausdrücke die dumme Angewohnheit, immer soviel wie möglich abdecken zu wollen. Wenn nach dem HREF mehrere “> auf der Zeile vorkommen, wird die obige Regexp bis zum letzten Vorkommen alles in den runden Klammern speichern. Diese Gier gewöhnen wir ihr ab, indem wir das in diesem Zusammenhang etwas schwer zu erklärende Ein- oder Keinmal-Zeichen ? hinter .* setzen:
<A HREF=\"(.*?)\">
Um im Inhalt von $_ danach zu suchen, nutzen wir den “match”-Operator m/Muster/, dem wir mit einem i-Flag am Ende gleich sagen, dass a href auch kleingeschrieben werden darf (“case-insensitive Suche”). Auch wollen wir alle auf der Zeile vorkommenden Links einsammeln und bemühen dazu das Flag g (“global”):
@dateien = m/<A HREF=\"(.*?)\">/gi;
Das, was jeweils in den runden Klammern landet, speichern wir in einer Array-Variablen namens @dateien und gehen sie Schritt für Schritt durch:
foreach $datei ( @dateien ){ }
Die jeweils aktuelle Referenz legen wir dazu in der Variablen $datei ab, die als “Laufvariable” der foreach-Schleife automatisch in $_ landet. Um nur dann etwas zu ändern, wenn es sich um eine Referenz auf eine lokale Datei handelt, prüfen wir, dass ihr Inhalt nicht mit einem Protokoll wie ftp oder http beginnt (andere Protokolle wie gopher können wir vernachlässigen):
if ( ! /(ftp|http):\/\//i ){ }
Das m vom Match-Operator darf weggelassen werden, und “ftp:// oder http://” lässt sich zu (ftphttp):// verkürzen. Dabei dient das Pipe-Zeichen als logisches Oder. Da die Schrägstriche bereits das Muster einrahmen, müssen wir sie escapen, und um nicht auf Groß- und Kleinschreibung zu achten, nutzen wir das i-Flag des Match-Operators. Zu guter Letzt sorgt das Ausrufezeichen dafür, dass die Bedingung gerade dann erfüllt ist, wenn das Muster nicht gefunden wird.
Nur lokal
Wenn in der Referenz kein Protokoll steckt, versuchen wir, die Datei zu öffnen:
open( DATEI, $datei );
DATEI ist ein sogenanntes Handle, mithin ein Stellvertreter für die Datei, deren Name in $datei steckt. Geht das in Ordnung, haben wir nichts zu berichtigen und können die Datei wieder schließen:
close DATEI;
Geht das Öffnen hingegen schief, …
if ( ! open( DATEI, $datei ) ){}
… müssen wir versuchen, den richtigen Dateinamen rauszubekommen. Wenn der in der Linkangabe mit einem / beginnt, bezieht er sich als absolute Pfadangabe nicht auf das Wurzelverzeichnis des Dateisystems, sondern auf die entsprechende Document Root auf dem Webserver. Halten wir also erst einmal das Verzeichnis in einer Variablen fest, das als Startverzeichnis für die Dateien der Website dient:
$rootDir = "/home/pjung/LU/LU1001/answergirl";
Dieser Umstand macht unsere Aufgabe etwas schwerer: Um eine im Link angegebene Datei, die mit / beginnt, zu finden, gilt es, nicht in /, sondern in $rootdir danach zu suchen. Gibt es etwas zu korrigieren, darf jedoch nicht $rootdir/korrigierter_Name zurück in den Link geschrieben werden, sondern lediglich /korrigierter_Name.
Was liegt also näher, als den korrigierten Namen und das Präfix, das wir zum Auffinden der Datei im Dateisystem brauchen, getrennt zu speichern? Setzt man die Inhalte beider Variablen, $praefix und $korrDat mit dem Punktoperator . zusammen, erhalten wir die Dateiangabe maßgeschneidert für’s Dateisystem, nehmen wir nur $korrDat, haben wir die Angabe passend für den Link.
Schreiben wir also zunächst ein / in die Variable, die den korrigierten Link enthalten soll:
$korrDat = "/";
Außerdem merken wir uns das Root-Verzeichnis als Präfix:
$praefix = $rootDir;
Mit dieser Vorbereitung für’s spätere Zusammensetzen im korrigierten Dateinamen können wir auf den Schrägstrich am Anfang (^) der in $datei gespeicherten Linkangabe verzichten. Um die Bedingung zu formulieren, unter der $korrDat und $praefix wie eben beschrieben gesetzt werden sollen, benutzen wir daher nicht den Match-, sondern den Substitute-Operator s. Wir sagen dem Skript einfach: “Wenn Du am Anfang von $datei einen / durch Nichts ersetzen kannst, setze $praefix und $korrDat wie eben besprochen.”
Leider ist / mal wieder ein Fall für das Escape-Zeichen. Doch glücklicherweise gibt es eine Möglichkeit, das zu suchende und das ersetzende Muster nicht nur mit /, sondern auch mit anderen Sonderzeichen voneinander abzutrennen. Nehmen wir zum Beispiel das Dollarzeichen. Damit wird aus “Suche / am String-Anfang, und ersetze es durch Nichts” keine Escape-Orgie, sondern ein einfaches s$^/$$. Um diese Ersetzung gleich in der Variablen $datei vorzunehmen, sagen wir
$datei =~ s$^/$$;
Als Bedingung formuliert, sieht das dann so aus:
if ( $datei =~ s$^/$$ ){ }
War der Link kein absoluter, bleibt $korrDat noch leer, und im Präfix wird der Punkt als Stellvertreter des aktuellen Verzeichnisses samt Trenn-Slash gespeichert:
else {
$praefix = "./";
}
Häppchenweise
Wenn einmal der Link kaputt ist, kann dies am Dateinamen selbst oder an einem der im Pfad angegebenen Verzeichnisse liegen. Folglich müssen wir in den sauren Apfel beißen und jeden durch / getrennten Bestandteil von der Wurzel bis zur Spitze überprüfen. Dazu teilen wir den um einen eventuellen führenden / beraubten Inhalt von $datei an den Slash-Stellen in kleine Häppchen und speichern sie im Array @teile:
@teile = split( /\//, $datei );
Die split()-Funktion benötigt zwei Argumente: den String, den sie zerhacken soll, und das Trennzeichen. Statt nun einfach einen Delimiter anzugeben, kommt hier der Match-Operator zum Einsatz. Zwischen seine beiden /-Flügel setzen wir den die Verzeichnisse trennenden Slash /, und damit der nicht mit dem rechten Flügel-/ verwechselt werden kann, kommt ein \ davor.
Sukzessive nehmen wir die Teilchen nun unter die Lupe:
foreach $teil ( @teile ){ }
Wenn der Pfadbestandteil in $teil ein Punkt für das aktuelle oder () ein Doppelpunkt für das übergeordnete Verzeichnis ist, brauchen wir keine Schreibweise prüfen und fügen den Inhalt von $teil an den String an, der bereits in $korrdat steht:
if ( $teil eq "." || $teil eq ".." ){
$korrDat .= $teil . "/" ;
}
Perl kennt zwei Gleichheitsoperatoren: für numerische Werte und für Zeichenketten. Letzterer heißt eq (“equal”). $korrDat .= $teil; wiederum ist eine Kurzschreibweise für
$korrDat = $korrDat . $teil;
Mit dem Anhängeoperator für Zeichenketten, dem Punkt, fügen wir zudem einen Slash als Verzeichnistrenner ein.
Haben wir hingegen einen echten Datei- oder Verzeichnisnamen in $teil stehen, gibt es mehr zu tun. Versuchen wir zunächst, das, was bislang in $korrDat steht, zu verifizieren: Mit dem $praefix davor haben wir es mit einem Verzeichnis zu tun, dass es zu öffnen gilt:
opendir (VERZ, $praefix . $korrDat );
Schließen werden wir es später mit closedir( VERZ );. Sind wir beim Öffnen allerdings nicht erfolgreich, können wir gleich aufgeben und die Bearbeitung der @teile beenden:
opendir (VERZ, $praefix . $korrDat ) || last ;
last verlässt die aktive Schleife, sodass wir mit der Bearbeitung der nächsten $datei weitermachen können.
Wenn wir das Verzeichnis $praefix . $korrDat hingegen öffnen und den Handler VERZ “installieren” konnten, lesen wir am besten mit
readdir( VERZ );
alle darin vorhandenen Dateien aus. Gibt es dort eine Datei oder ein Verzeichnis mit dem Namen, der in $teil gespeichert ist? Auf der Shell würden wir dazu den grep-Befehl benutzen – und netterweise gibt es ihn auch in Perl:
grep ( /$teil/i , readdir( VERZ ) );
Das Muster wird dabei vom Match-Operator umgeben – und natürlich darf die i-Option nicht fehlen, denn die Groß-Kleinschreibung der tatsächlichen Datei kann von $teil durchaus verschieden sein.
Eins haben wir dabei jedoch nicht bedacht: grep findet in dieser Version auch dann Übereinstimmungen, wenn der Inhalt von $teil lediglich Bestandteil eines vorhandenen Datei- oder Verzeichnisnamens ist. Hier gilt es, das Muster zu präzisieren: Wir geben es inklusive Anfang (^) und Ende ($) an und speichern das Ergebnis in einem Hilfsarray:
@auswahl = grep ( /^$teil$/i , readdir( VERZ ) );
Enthält @auswahl nun nichts, also nicht mal ein nulltes Element, können wir keine korrigierte Fassung des Links erstellen und missbrauchen $korrDat für einen HTML-Kommentar, der sagt, dass $datei nicht auffindbar war. Weiter geht’s dank last am Schleifenanfang mit einem eventuellen nächsten Element von @dateien:
if ( $#auswahl < 0 ){
$korrDat = ("<!-- " . $datei . " nicht auffindbar! -->" );
last;
}
Mit komischen Sonderzeichenkombinationen, die für unfreiwilliges Gedächtnistraining sorgen, ist Perl reichlich gesegnet. Klaut man einem Array das @ und ersetzt es durch ein $#, erhält man ($) eine Skalarvariable, in der (#) die Anzahl der Array-Mitglieder gespeichert ist.
War die in @auswahl gespeicherte Ausbeute etwas zu erfolgreich (wir erinnern uns, verzeichnis und Verzeichnis können auf Unixsystemen problemlos nebeneinander existieren), setzen wir $korrDat auf einen Kommentar, der besagt, dass es mehrere Möglichkeiten gibt:
elsif ( $#auswahl > 0 ){
$korrDat = ("<!-- " . $datei . " nicht eindeutig! -->" );
last;
}
Nur wenn wir genau eine Variante finden, können wir das nullte Element von @auswahl an $korrDat anhängen:
else {
$korrDat .= $auswahl[0];
}
Bleibt ein trailing slash anzuhängen, um $korrdat bereit für neue Unterverzeichnisebenen zu machen. Zu blöd – wenn $teil nun den Dateinamen enthielt, hat auch der einen Slash am Ende, obwohl es da nicht mehr weiter geht. Doch so weit, wie wir bislang mit unserem Skript gekommen sind – immerhin ist es nur ein Skript, um schnell ein paar Groß-Kleinschreibungsfehler auszubügeln – genehmigen wir uns einen üblen Hack: Wir ersetzen den Slash am Ende von $korrDat mit nichts. Und weil’s so schön ist, nehmen wir dazu das Plus als Trennzeichen für den Substitute-Operator:
$korrDat =~ s+/$++;
Achja, jetzt hätten wir beinahe vergessen, die aus der Datei ausgelesene $zeile zu korrigieren, indem wir den alten Link $datei durch die korrigierte Ausgabe $korrDat ersetzen …
$zeile =~ s+$datei+$korrDat+;
… und zu guter Letzt natürlich auszugeben:
print $zeile;
Damit naht der große Augenblick: Ab ins Testverzeichnis und das Skript ohne weitere Parameter, aber vielleicht besser durch less “gepipet” auf die dortigen Dateien loslassen. Sieht gut aus? Dann nehmen wir noch schnell das Kommentarzeichen aus der Zeile
# $^I = ".bak";
heraus, und schon legt cgks die konvertierten Dateien unter ihren alten Namen ab, während die Ursprungsversion als Backupdatei mit der Endung .bak einen Vergleich ermöglicht.
Listing 2
cgks
als Ganzes
#!/usr/bin/perl -w
$^I = ".bak";
@ARGV = <*.html>;
$rootDir = "/home/pjung/LU/LU1001/answergirl";
while ( $zeile = <> ){
$_ = $zeile;
@dateien = m/<A HREF=\"(.*?)\">/ig;
foreach $datei ( @dateien ){
$korrDat = "";
if ( ! /(ftp|http):\/\//i ){
if ( ! open( DATEI, $datei ) ){
if ( $datei =~ s$^/$$ ){
$praefix = $rootDir;
$korrDat = "/";
} else {
$praefix = "./";
}
@teile = split( /\//, $datei );
foreach $teil ( @teile ){
if ( $teil eq "." || $teil eq ".." ){
$korrDat .= $teil . "/" ;
} else {
opendir (VERZ, $praefix . $korrDat ) || last ;
@auswahl = grep ( /^$teil$/i , readdir( VERZ ) );
closedir( VERZ );
if ( $#auswahl < 0 ){
$korrDat = ("<!-- " . $datei . " nicht auffindbar! -->" );
last;
} elsif ( $#auswahl > 0 ){
$korrDat = ("<!-- " . $datei . " nicht eindeutig! -->" );
last;
} else {
$korrDat .= $auswahl[0];
}
$korrDat .= "/";
}
}
$korrDat =~ s+/$++;
$zeile =~ s+$datei+$korrDat+;
}
close DATEI;
}
}
print $zeile;
}
Kasten 1: Manöverkritik
Sicher gibt es immer elegantere Möglichkeiten, ein Skript zu basteln. So ließe sich beispielsweise eine bei großen Datenmengen schnellere Variante schreiben, die sich jedes überprüfte Verzeichnis merkt, sodass nicht bei jedem Link jedes Verzeichnis neu betrachtet werden muss.
Viel kritischer für den Einsatz ist jedoch, dass das vorgestellte Programm dann versagt, wenn der HTML-Anker A HREF und die Linkquelle auf verschiedenen Zeilen stehen. Dieses Handicap in Kauf nehmen oder noch mehr Komplexität speziell bei den regulären Ausdrücken einzubauen, war die Alternative.
Für Gelegenheitsperlianer eher unbekannt ist jedoch die Tatsache, dass im “Comprehensive Perl Archiv Network” CPAN jede Menge Module lagern, die ähnlich Bibliotheken bei Sprachen wie C, C++ oder Java Unmengen Funktionen für alle möglichen und unmöglichen Einsatzgebiete enthalten.
Im Fall unseres Skripts hätten wir uns die Arbeit mit den regulären Ausdrücken, um mühselig Links zu erkennen, sparen können, wenn wir das bei vielen Distributionen bereits per Default installierte HTML::Parser-Modul (bei SuSE im Paket perl-HTML-Parser, bei Caldera in perl-modules) benutzt hätten.
Allerdings hat dieses Modul einen großen Nachteil: Wir bewegen uns damit in objektorientierten Perl-Gefilden und dürfen uns damit herumschlagen, was ein Parser ist. Für Perl-Noviz(inn)en und Programmmieranfänger(innen) vermutlich ein etwas zu ambitioniertes Projekt.
Glossar
- A HREF
- Um in einer Webseite mit einem Hyperlink auf eine andere Datei oder Internet-Ressource zu verweisen, schreibt man einen “Anker” in den Text. Dieser wird um die “Hyperreferenz” ergänzt, die angibt, worauf der Link verweist, z. B. A HREF=”http://www.linux-user.de/”. Damit diese Angabe für die Leser(innen) der Seite nicht im Text erscheint, klammert man sie in spitze Klammern (<A HREF=”http://www.linux-user.de/”>). Anschließend folgt der Text, auf den “geklickt” werden soll, um zum Verweis zu gelangen. Zu guter Letzt folgt das Ende-Tag </A>, mit dem der Anker abgeschlossen wird: <A HREF=”http://www.linux-user.de/”>LinuxUser</A>.
- Pfad
- Der von Verzeichnissen gepflasterte Weg zu einer Datei. Absolute Pfade beginnen an der durch / gekennzeichneten Wurzelspitze des Dateisystems, relative Pfade im aktuellen Verzeichnis.
- Pipes
- Eine “Rohrleitung”, durch die die Ausgabe eines Kommandozeilenprogramms durchgeleitet wird und am “Leitungsende” als Eingabe für ein zweites Werkzeug dient. Sie wird durch einen senkrechten Strich | symbolisiert: kommando1 | kommando2.
- regulären Ausdrücke
- Von verschiedenen Standard-Unix-Tools verwendete Möglichkeit, Muster auszudrücken. So steht ein Punkt für ein beliebiges Zeichen oder ein Buchstabe für sich selbst. Folgt ein Sternchen, darf das, was vom vorangegangenen Muster abgedeckt wird, beliebige Male, auch keinmal auftreten. Ein Fragezeichen bedeutet hingegen, dass das, worauf es sich bezieht, genau null- oder einmal vorkommt.
- Python
- Eine objekt-orientierte Skript-Sprache. Programme, die mit Skript-Sprachen geschrieben werden, müssen nicht extra kompiliert werden, sondern können mit einem Interpreter direkt aus dem Source-Code ausgeführt werden. Oft (z. B. bei Perl) kompiliert zwar der Interpreter ein “internes” Binärprogramm, um die Ausführungsgeschwindigkeit zu erhöhen, doch davon merken Anwender(innen) im Normalfall nichts. Da der Aufruf eines Compilers entfällt, eignen sich interpretierte Sprachen besonders gut für kleine Programme, die “schnell hingeschrieben” eine Aufgabe lösen sollen und gar nicht für den Gebrauch durch Dritte bestimmt sind.
- Tcl
- Eine Skript-Sprache, die meist im Zusammenhang mit dem GUI-Toolkit Tk zum Schreiben grafischer Anwendungen verwendet wird. Sie lässt sich aber auch ohne Tk einsetzen.
- Suchpfad
- Gibt man ein Kommando ein, sucht die Shell die in der Umgebungsvariablen PATH gespeicherten Verzeichnisse der Reihe nach nach einer gleichnamigen ausführbaren Datei ab. Die erste Fundstelle wird benutzt; wird die Shell nicht fündig, so gibt sie die Fehlermeldung command not found aus, selbst wenn das Kommando irgendwo anders im Dateisystem existiert.
- Entities
- Hier: HTML-Kodierung für Zeichen, die im ASCII-Zeichensatz nicht auftauchen. Eine Entität beginnt in HTML immer mit einem kaufmännischen Und (&) und endet auf ein Semikolon, z. B. steht ü für ü, < für < und > für >.
Infos
[1] “Völlig link”, “Answer Girl” im Linux-Magazin 10/1999: http://www.linux-magazin.de/ausgabe/1999/10/Answergirl/answergirl4.html
[2] Bjellis Perlwelt: http://perlwelt.horus.at/
[3] Viele Webseiten ändern: http://perlwelt.horus.at/beginning/MachSieAlle/home.html






