Es gibt kaum ein Bash-Skript, das ohne Standard-Unix-Tools auskommt. Allerdings ist die Shell inzwischen so mächtig, dass viele verzichtbar sind.
Wenn Fotofreunde ein Motiv mit mehreren Belichtungseinstellungen fotografieren, ergeben sich mit der richtigen Software erstaunliche Resultate: High Dynamic Range nennen das die Profis [1]. Mühsam nur, wenn es viele Bilder auf einmal zu konvertieren gibt. Linux-Anwender schreiben sich für solche wiederkehrenden Aufgaben ein Skript. Listing 1 zeigt einen Ausschnitt aus einem solchen Skript, das aus Raw-Dateien automatisch HDR-Dateien erzeugt [2].
|
Listing 1: Zu viele externe |
|---|
01 i=0
02 cat "$DIR"/pfs.hdrgen | while read LINE; do
03 echo "${FILES[$i]} $LINE" | cut -d' ' -f1,3- >> "$DIR"/pfs_updated.hdrgen
04 let "i = $i +1"
05 done
|
Der Code im Beispiel ist nicht falsch, aber kompliziert und unverständlich. Auch wenn das Beispiel extrem anmutet: Man kommt nicht immer an per Pipe verbundenen Befehlsketten vorbei. Doch oft sind solche Konstrukte überflüssig und zeugen nur von mangelnder Kenntnis der Bash-Möglichkeiten. Diese Folge des Bash Bashing zeigt ein paar Wege auf, ohne externe Befehle auszukommen.
Eine Basis finden
Ein bekannter Klassiker in Skripten ist die Zuweisung »verz=$(dirname “$0”), um das Verzeichnis zu finden, in dem das Skript liegt. Dabei geht das auch einfacher mit »verz=”${0%/*}”«. Geht es um den reinen Programmnamen, hilft »prog=”${0##*/}”« statt des externen Aufrufs:
prog=`basename "$0"`
Das Syntaxelement in beiden Fällen heißt Parameter-Expansion. Der Wert einer Variablen – hier des nullten Positionsparameters – übernimmt die Bash dabei nicht einfach, wie er ist, sondern verändert ihn zusätzlich. Das Prozentzeichen löscht dabei den auf sich selbst folgenden Text von hinten, die Raute von vorne. Bei doppeltem Prozentzeichen oder Raute verfährt die Bash gierig und löscht den maximal möglichen String, der passt. In der einfachen Form bleibt die Bash konservativ und nimmt nur den ersten passenden Ausdruck.
Der Trick ohne externes Programm »dirname« klappt allerdings nicht immer, denn die Shellvariante gibt einen Punkt zurück, wenn ihr Argument beispielsweise »demo.sh« lautet, die Bash hingegen den kompletten Namen übrig lässt. Die Tabelle 1 gibt einen Überblick verschiedener Optionen und Funktionen der Parameter-Expansion.
|
Tabelle 1: |
||
|---|---|---|
|
Zeichen |
Bedeutung |
Erklärung |
|
# |
Einfaches Löschen von vorne |
»x=”aabc”«: das führende »a« |
|
## |
Gieriges Löschen von vorne |
»x=”aabc”«: alle führenden »a« |
|
% |
Löschen von hinten |
»x=”abcc”«: das letzte »c« entfernt |
|
%% |
Gieriges Löschen von hinten |
»x=”abcc”«: alle »c« von hinten |
|
/ |
Ändern eines Textes (erstes Vorkommen) |
»x=”abcabc”«: »${x/b/x}« erzeugt |
|
// |
Ändern eines Textes (alle Vorkommen) |
»x=”abcabc”«: »${x//b/x}« erzeugt |
|
^ |
Erstes Zeichen in Großbuchstaben umwandeln (Bash 4) |
»x=”abcabc”« verwandelt »${x^a}« in |
|
^^ |
Alle Zeichen in Großbuchstaben umwandeln (Bash 4) |
»x=”abcabc”« verwandelt »${x^^b}« in |
|
, |
Erstes Zeichen in Kleinbuchstaben umwandeln (Bash 4) |
»x=”ABCABC”« verwandelt »${x,A}« in |
|
,, |
Alle Zeichen in Kleinbuchstaben umwandeln (Bash 4) |
»x=”ABCABC”« verwandelt »${x,,B}« in |
Auf Echo verzichten
Will der Programmierer den Inhalt von Shellvariablen an Programme verfüttern, dann scheint der Echo-Befehl ein probates Mittel:
zeilen=`echo $foo | grep -i muster` if [ -n "$zeilen" ]; then ... fi
In manchen Shells ist dieses Konstrukt tatsächlich notwendig. Die Bash dagegen erlaubt es, den Inhalt von Shellvariablen direkt an die Standardeingabe eines Programms zu binden:
zeilen=`grep -i muster <<< "$foo"` if [ -n "$zeilen" ]; then ... fi
Alternativ darf das Kommando auch direkt hinter dem Schlüsselwort »if« stehen, denn dort sind auch andere Befehle außer »test« oder seines häufiger benutzen Alias »[« erlaubt:
if grep -qi muster <<< "$foo"; then ... fi
Die Bedingung ist in diesem Fall abhängig vom Returncode des Kommandos, das die Shell auch in »$?« ablegt.
Read spart Cut und Awk
Oft steht der Shellprogrammierer vor dem Problem, das n-te Feld aus einem String herauszuschneiden. Cut ist das Unix-Kommando der Wahl für dieses Problem. Allerdings kann Cut nicht mit einer variablen Anzahl von Trennzeichen umgehen, da sich dabei die Feldnummern verschieben:
cut -d' ' -f 3<< EOF Text1 Text2 Text3 Text1 Text2 Text3 EOF
Die erste Zeile der Ausgabe des Fragments enthält wie erwartet den String »Text3«, die zweite Zeile dagegen den String »Text2«. Für »cut« ist wegen des doppelten Leerzeichens der Eingabe in der zweiten Zeile das zweite Feld leer. Statt nun ein Awk-Skript zu schreiben, haben Bash-Fans auch die Option, dies mit Bordmitteln zu erledigen:
string="Text1 Text2 Text3" read f1 f2 f3 rest <<< "$string" echo "f2=$f2"
Andere Feldtrenner lassen sich entweder durch die Shellvariable »IFS« wie in »ip=10.0.0.1; IFS=. read a1 a2 a3 a4 <<< “$ip”« oder durch die Option »-d« des Read-Builtin in neueren Versionen der Bash umsetzen.
Ohne Sed und Grep
In komplexeren Skripten kommt der Programmierer selten ohne Sed oder Grep aus. Soll das Programm nur einfache Strings austauschen, so erledigt »${var/abc/def/}« die Arbeit des externen Kommandos »sed ‘s/abc/def/’ <<< $var«. Auf diese Weise ersetzen beide Kommandos die Zeichenfolge »abc« in der Eingabevariablen »str« durch die Zeichenfolge »def« – Suchen und Ersetzen leicht gemacht! Der gleiche Mechanismus nützt auch beim Ersatz von Grep. Die Zeilen
if [ "$str" != "${str/abc}" ]; then
...
fi
ersetzen das eingangs erläuterte externe Kommando, um die Eingabevariable »str« nach dem Vorkommen von »abc« zu durchsuchen:
if grep -q "abc" <<< "$str"; then ... fi
Der Ausdruck »”${string/abc}”« löscht »abc« aus dem String, da die Ersetzung leer ist. Wenn das Ergebnis ungleich dem Original ist, dann muss »abc« im Original vorhanden sein.
Bash 4 vertreibt »tr«
Um mehr Komfort bei interaktiven Benutzereingaben oder Konfigurationsdateien zu bieten, möchten Bash-Hacker als positive Antwort auf eine Frage sowohl »ja«, »Ja« als auch »JA« akzeptieren. Die Transformation
REPLY=`tr [:lower:] [:upper:]` <<< "$REPLY" if [ "$REPLY" = "JA" ]; then ... fi
macht den Code übersichtlicher als längliche Einzelabfragen, die per logischem Oder verknüpft sind. Seit der Version 4 der Bash hat der Programmierer aber sogar zwei eingebaute Alternativen zum Translate-Kommando [3]. Einmal kann er Variablen per se so definieren, dass bei einer Zuweisung automatisch die Umwandlung stattfindet, oder er nutzt die neuen Möglichkeiten der Parameter-Expansion (siehe Listing 2). Sie verwendet intelligent gewählte Zeichen, die die Richtung der Umwandlung grafisch andeuten. Wer nur einige Buchstaben umwandeln muss, verwendet die vollständige Form.
|
Listing 2: |
|---|
01 declare -u upVar # automatisch groß
02 declare -l lowVar # automatisch klein
03 up="${mix^^}" # $mix in Großbuchstaben wandeln
04 low="${mix,,}" # $mix in Kleinbuchstaben wandeln
|
Builtins reichen oft aus
Viele externe Kommandos in Bash-Skripten lassen sich durch Bordmittel ersetzen. Manchmal erhöht das die Übersichtlichkeit, bei Skripten mit großen Schleifen verringert es auch die Laufzeit: Ruft etwa ein Mailserver pro eingehender Nachricht ein Skript mit vielen externen Kommandos auf, schlägt das auf die Performance des Gesamtsystems merkbar durch – nicht nur wegen der eigentlichen Laufzeit der externen Kommandos, sondern weil sie den Kernel mit dem Erzeugen und Löschen der Prozesse belasten. Wenn Bash-Mittel trotzdem nicht ausreichen, dann bleibt immer noch die Wahl von mächtigeren Skriptsprachen wie Perl oder Python. (mg)
|
Infos |
|---|
|
[1] Peter Kreußel, “Foto-Mix: High-Dynamic-Range-Bilder ohne Spezialhardware”: Linux-Magazin 05/08, S. 48 [2] Edu Pérez, “Script to make HDRs with Linux”: [http://photoblog.edu-perez.com/2009/04/script-hdr-with-linux.html] [3] Bernhard Bablok, Nils Magnus, “Aufpoliert – Neue Funktionen in der Bash-Version 4”: Linux-Magazin 06/09, S. 46 |
|
Der Autor |
|---|
|
Bernhard Bablok betreut bei der Allianz Shared Infrastructure Services ein Datawarehouse mit technischen Performancemessdaten. Wenn er nicht Musik hört, mit dem Rad oder zu Fuß unterwegs ist, beschäftigt er sich mit Themen rund um Linux und Objektorientierung. |





