Open Source im professionellen Einsatz
Linux-Magazin 01/2006
© photocase.com

© photocase.com

Workshop: Bash-Skripte mit Bordmitteln beschleunigen

Skript-Tuning

Früher fehlten den Shells programmiertechnisch einige Gänge, sie taugten kaum mehr als zum Aufruf externer Programme. Inzwischen haben die Entwickler die Bash zu einer ernst zu nehmenden Programmiersprache aufgebohrt, die schon mit Bordmitteln so manches Standardtool abhängt.

784

Viele Skripte behandeln die Bash, als könne sie nichts anderes als externe Programme aufrufen. Dabei hat die Version 2 der Standardshell einen Befehlsumfang, der mit komplexen Stringfunktionen, regulären Ausdrücken und Arrays viele nützliche Funktionen bietet, die externe Aufrufe ersetzen. Der Vorteil interner Funktionen ist, dass die Shell keinen neuen Prozess startet, was Rechenzeit und Speicher spart. Gerade wenn Sie Programme wie »grep« oder »cut« in Schleifen verwenden, steigt die Ausführungszeit Ihres Skripts.

Die folgenden Skripte messen sich stets an der Auswertung eines herkömmlichen Apache-Logs einer Website, hier ein Auszug einer Anfrage:

84.57.16.30 - - [21/Oct/2005:04:18:26 +0200] "GET /favicon.ico HTTP/1.1" 404 209 "-" "Mozilla/5.0 (X11; U; Linux i686; de-DE; rv:1.7.5) Gecko/20041122 Firefox/1.0"

Wenn Sie feststellen wollen, welche Seiten abgefragt wurden, lösen Sie den GET-String aus dem Log heraus.

Listing 1 zeigt einen Lösungsansatz, wie er häufig in Bash-Skripten anzutreffen ist. Der »cat«-Aufruf in Zeile 3 liest zunächst die Logdatei komplett ein und übergibt sie der »for«-Schleife als Parameterliste - so muss die Bash den gesamten Datei-Inhalt erst mal zwischenspeichern. Anschließend ruft Listing 1 in Zeile 5 für jeden einzelnen Log-Eintrag »cut« als externes Programm auf. Bei einem rund 600 KByte großen Apache-Log dauerte das auf einem Pentium III mit 750 MHz über 18,5 Sekunden.

Das Listing 2 hingegen ist mit gerade mal 3,3 Sekunden fast sechsmal schneller: Es öffnet die Logdatei über den File Descriptor 3 und liest in der Schleife jeweils eine Zeile in die Variable »Anfrage«. Die Bash braucht also stets nur einen Logeintrag zwischenzuspeichern. Anschließend entfernt es vom Anfang der Zeile alle Zeichen bis einschließlich »GET « und vom Ende alle Zeichen bis einschließlich » HTTP/«.

Eine weitere Zehntelsekunde lässt sich einsparen, wenn Sie vom Ende der Anfrage alle Zeichen bis einschließlich des ersten Leerzeichens entfernen, weil so der Stringvergleich der Bash-internen Funktion einfacher ist.

Ersatz-Funktionen

Auch die Programme »basename« und »dirname« lassen sich mit Bash-Mitteln sehr leicht ersetzen. Dazu dient die Stringfunktion »${Variable%Pattern}«, sie entfernt vom Ende der Zeichenkette das kürzeste zutreffende Pattern, sowie »${Variable##Pattern}«, die vom Anfang der Zeichenkette das längste zutreffende Pattern löscht. Letztlich genügt für »dirname« ein einfaches »alias«:

alias dirname=echo ${1%/*};

Die Funktionalität von »basename« ist nur unwesentlich komplexer, hier müssen Sie berücksichtigen, dass »basename« auch die Datei-Endung entfernen kann. Kombinieren Sie daher beide Stringfunktionen miteinander:

function basename() {
  B=${1##*/}
  echo ${B%$2}
}

Das Ergebnis der ersten String-Manipulation in der Variablen »B« zu speichern ist effizienter, als etwa mittels »set« erneut den ersten Parameter zu belegen und den zweiten Parameter zu übergeben. Sie sollten jedoch nicht ausnahmslos jeden externen Programmaufruf durch Bash-interne Befehle ersetzen. Ein gutes Beispiel dafür ist Listing 3, das ein rudimentäres »grep« implementiert: Obwohl das Skript nur aus wenigen Zeilen besteht und auf den ersten Blick relativ effizient aussieht, braucht es über 2 Minuten, um das 600-KByte-Log des Webservers nach dem Namen einer Datei zu durchsuchen - »grep« hingegen liefert die Ergebnisse in 0,03 Sekunden.

Der größte Performance-Killer ist das Suchmuster: In Zeile 4 wird die aus der Datei eingelesene Zeile mittels Suchen undErsetzen komplett gelöscht, falls es eine Übereinstimmung gibt. Durch das Suchmuster »*${1}*« versucht die Bash bei jedem einzelnen Zeichen der Zeile eine Übereinstimmung zu finden. Verwenden Sie hingegen das Suchmuster »#*${1}*«, wonach das Muster am Zeilenanfang übereinstimmen muss, führt die Bash nur einen Vergleich pro Zeile durch - was die Gesamtlaufzeit des Skripts auf unter 3 Sekunden reduziert. Dennoch ist der externe Aufruf von »grep« um den Faktor 100 schneller.

IPs zerlegen

Auch wenn die Stringfunktionen im vorigen Beispiel nicht der Engpass waren, gibt es je nach Situation elegantere und schnellere Methoden, eine Zeichenkette zu zerlegen. Wenn Sie etwa die IP-Adressen sortieren wollen, von denen die Anfragen kamen, können Sie dies nicht mit Hilfe von »sort« oder einer einfachen lexikalischen Sortierung erledigen, da sonst »217.83.13.152« vor »62.104.118.59« einsortiert würde. Sie müssen die einzelnen Bytes der IP-Adressen herauslösen, in eine sortierbare Form bringen und dann sortieren und vereinzeln.

Die Listings 4 und 5 zeigen dafür zwei mögliche Ansätze, die unterschiedlich performant sind. Das Skript in Listing 4 liest zunächst die Logdatei zeilenweise ein (Zeile 3), um dann die an erster Stellen stehende IP-Adresse in Zeile 4 herauszutrennen. Die Zeilen 6 bis 10 verkürzen danach die IP-Adresse von hinten um jeweils ein Glied und speichern die Adress-Bytes als Dezimalzahlen im Array »IP«.

Der »printf«-Aufruf in Zeile 11, übrigens auch ein Bash-interner Befehl, hat die Funktion, die 4 Bytes der IP-Adresse dreistellig in Dezimalschreibweise mit führenden Nullen und durch Punkte getrennt auszugeben. Die Ausgabe wird in der letzten Zeile per Pipe dem externen Programm »sort« übergeben, anschließend entfernt »uniq« die doppelten Einträge. Für diese Aufgabe benötigte Listing 4 bei einer 600 KByte großen Apache-Logdatei rund 2,6 Sekunden.

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • Bash Bashing

    Über Klicki-Bunti und grafische Oberflächen sollten Kommandozeilen-Fans gerechterweise nur dann die Nase rümpfen, wenn sie die Effizienz steigernden Bedienungstricks der interaktiven Shell auch wirklich kennen und benutzen. Diesmal: Bash Completion.

  • Bash Bashing

    Das Kommando »cat« betört Kommandozeilen-Liebhaber mit seiner schlichten Eleganz. Für manche ist es Dateibetrachter, Textmanipulator oder das einfachste denkbare Eingabeprogramm. Gerüchteweise haben einige sogar schon Binarys damit eingetippt. Jetzt zeigt sich: Shell-Programmierer nutzen das Tool zu oft.

  • Bash Bashing

    Eigentlich ist die Bash nicht als Tool für Netzwerkprogramme bekannt, obwohl sich einfache Protokolle damit gut implementieren lassen. Meist stellen Wrapper wie Netcat die Verbindung zum Netzpartner her. Mit kluger Ausgabeumleitung und einem überraschenden Pseudodevice geht das aber auch mit Bordmitteln.

  • Bash Bashing

    Die Fehlersuche in Skripten erscheint um vieles bodenständiger als in kompilierten Programmen. Jenseits der "bash -x"- Hausmannskost hält die Bash aber einige Überraschungen auf der Debugging-Karte bereit.

  • Bash Bashing

    Soll ein Shellskript mit seinen Benutzern interagieren, braucht der zuständige Bash-Programmierer auf den Spatz nicht gleich mit der GUI-Toolkit-Kanone samt zusätzlicher Programmiersprache zu schießen. Wie das Kleinkaliber funktioniert, beschreibt dieser Artikel.

comments powered by Disqus

Ausgabe 08/2017

Digitale Ausgabe: Preis € 6,40
(inkl. 19% MwSt.)

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.