Aus Linux-Magazin 02/2010

Shellskripte aus der Stümper-Liga - Folge 5: Cat, Sed, Grep & Co. vermeiden

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
Befehle in »createHDR.sh«

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

 

Zeichen

Bedeutung

Erklärung

#

Einfaches Löschen von vorne

»x=”aabc”«: das führende »a«
entfernt »${x#a}«

##

Gieriges Löschen von vorne

»x=”aabc”«: alle führenden »a«
entfernt »${x##a}«

%

Löschen von hinten

»x=”abcc”«: das letzte »c« entfernt
»${x%c}«

%%

Gieriges Löschen von hinten

»x=”abcc”«: alle »c« von hinten
entfernt »${x%%c}«

/

Ändern eines Textes (erstes Vorkommen)

»x=”abcabc”«: »${x/b/x}« erzeugt
»axcabc«

//

Ändern eines Textes (alle Vorkommen)

»x=”abcabc”«: »${x//b/x}« erzeugt
»axcaxc«

^

Erstes Zeichen in Großbuchstaben umwandeln (Bash 4)

»x=”abcabc”« verwandelt »${x^a}« in
»Abcabc«; »${x^}« wandelt einen beliebigen
ersten Buchstaben um

^^

Alle Zeichen in Großbuchstaben umwandeln (Bash 4)

»x=”abcabc”« verwandelt »${x^^b}« in
»aBcaBc«; »${x^^}« wandelt den kompletten
String um

,

Erstes Zeichen in Kleinbuchstaben umwandeln (Bash 4)

»x=”ABCABC”« verwandelt »${x,A}« in
»aBCABC«; »${x,}« wandelt einen beliebigen
ersten Buchstaben um

,,

Alle Zeichen in Kleinbuchstaben umwandeln (Bash 4)

»x=”ABCABC”« verwandelt »${x,,B}« in
»AbCAbC«; »${x,,}« wandelt den kompletten
String um

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:
Parameter-Expansion

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.

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben