Im Märze der Bauer…

Teil 2: Grundlagen der Bash

01.01.2001
Nachdem wir uns in der letzten Ausgabe die Metazeichen sowie die grundlegende Verwendung von Variablen angesehen haben, stoßen wir heute in das Reich der mehrdimensionalen Variablen vor und beschäftigen uns am Ende noch mit der Verarbeitung von Zeichenketten.

Beginnen wollen wir diesmal mit Variablen-Feldern, den sogenannten Arrays. Was sich so kompliziert anhört, ist in Wirklichkeit ganz einfach zu benutzen. Ein eindimensionaler Array ist nichts anderes als eine Reihe direkt hintereinander stehender Variablen, so als hätte man einen ein Kästchen breiten Streifen karriertes Papier – in jedes Kästchen kann man eine Zahl schreiben. Da in dieser Array-Variablen nun nicht mehr ein einziger Wert steht, müssen wir jedes mal sagen, welches Kästchen wir meinen. Dazu hängen wir dem Namen unseres Feldes in eckigen Klammern die Nummer des Kästchens an, das verwendet werden soll:

V[1]=Hallo
 V[2]=Welt!

In der ersten Zeile schreiben wir ins Kästchen Nummer 1 Hallo und in Kästchen Nummer 2 Welt!. Wie so oft in der Computerwelt beginnt auch die Bash mit Null zu zählen. Wir haben also vor "Hallo" noch ein Kästchen frei:

V[0]="Wir sagen:"

Für das Element Null kann übrigens die explizite Benennung über [0] sowohl bei Zuweisungen als auch der Ausgabe entfallen, wie folgende Ausgabe zeigt:

echo $V ${V[1]} ${V[2]}
 Wir sagen: Hallo Welt!

Hier ist auch schon der Pferdefuß der Arrays: Um an ein Element, also den Inhalt eines Kästchens, heranzukommen, müssen wir die geschweiften Klammern einsetzen – sonst würde die Bash denken, $V[1] wäre $V – also $V[0] – und dahinter [1] statt des Elements Nummer eins in unserem Array. Schauen wir uns doch nun einmal an, was alles in unserem Array steht. Dafür benutzen wir den Befehl typeset, mit dem man unter anderem den Status und Inhalt einer Variablen abfragen kann:

typeset -p V
 declare -a V='([0]="Wir sagen:" [1]="Hallo" [2]="Welt!")'

Die Ausgabe von typeset entspricht dem, was man eingeben müsste, um den Array von Grund auf neu einzuspeichern. Neu ist der Befehl declare -a, mit dem man Variablen anmelden kann. Mit "-a" wird explizit festgelegt, dass es sich bei V um ein Array handelt, anschließend werden die Werte der Elemente 0, 1 und 2 eingetragen. declare hat noch weitere Parameter neben "-a", auf diese werden wir bei Bedarf noch eingehen. Wie so oft können wir auch in diesem Fall auf das declare verzichten, indem wir einfach schreiben:

V=([0]="Wir sagen:" [1]="Hallo" [2]="Welt!")

An dieser Stelle ein Wort zur Programmierung im Allgemeinen. Eine Programmiersprache dient dazu, dem Computer Anweisungen in für Menschen lesbarer und verständlicher Form zu erteilen. Man könnte den Computer auch direkt mit Prozessorbefehlen füttern, nur wird man dann ernste Probleme haben, später Fehler zu beseitigen oder neue Funktionen einzubauen – man versteht nach gewisser Zeit sein eigenes Programm nicht mehr. Endstation ist dann oft der Papierkorb, verbunden mit einem kompletten Neudesign des Programms.

Deshalb ist es durchaus sinnvoll, mittels declare komplexere Strukturen wie zum Beispiel Arrays zu deklarieren. Ein Außenstehender versteht das Programm dann viel eher. Doch das reicht nicht.

Sie sollten sich unbedingt angewöhnen, ihre Programme zu dokumentieren, gleich, ob Sie sie für C oder die Bash verfassen. Der Mittelweg zwischen der spartanischen Copyright-Anmerkung und einem Kommentar zu jeder Zeile ist wie immer der beste. Eine Ausgabezeile, in der Sie lapidar einen Fehler vermelden, brauchen Sie nicht zu dokumentieren. Sie können davon ausgehen, dass ein Leser durchaus der Bash mächtig ist. Wenn Sie aber anfangen, mit externen Programmen Daten zu verarbeiten und dabei womöglich auch noch Verschachtelungen oder gar Tricks für eine schnellere Abarbeitung einbauen, gehört diese Stelle unbedingt mit deutlich mehr als einer Zeile dokumentiert. Ich werde, wenn wir in den nächsten Folgen zu entsprechenden Beispielen kommen, noch einmal darauf eingehen.

Doch zurück zu den Arrays. Nehmen wir einmal an, wir hätten Lücken in unserem Feld, wie in folgendem Beispiel:

N="Das"
 N[3]="Haus"
 N[6]="."

Auf unserem Streifen Karopapier würden wir die Kästchen 0, 3 und 6 belegen. Die Bash verwaltet ihren Speicher allerdings besser, dort gibt es keine Löcher:

typeset -p N
 declare -a N='([0]="Das" [3]="Haus" [6]=".")'

Unsere drei Einträge werden einfach hintereinander abgespeichert, zudem merkt sich die Bash ihre Element-Nummer. Auf diese Weise können wir jederzeit die noch unbenutzten Nummern im Zwischenraum füllen:

N[1]="ist"N[2]="das"N[4]="vom"N[5]="Nikolaus"typeset -p N declare -a N='([0]="Das" [1]="ist" [2]="das" [3]="Haus" [4]="vom" [5]="Nikolaus" [6]=".")'

Zum Schluss sei noch erwähnt, dass man Array-Elemente an jeder Stelle verwenden kann, wo auch eine normale (skalare) Variable stehen könnte, man muss nur meist die Schreibweise mit den geschweiften Klammern benutzen.

Spezielle Variablen

Die Bash verfügt über eine ganze Reihe spezieller Variablen, wie zum Beispiel die Parameter eines Programmaufrufs. Hier erst einmal nur die wichtigsten. Die Variablen $0, $1, $2 usw. sind die Parameter, die beim Aufruf eines Bash-Scripts (oder einer Funktion, dazu aber später mehr) vom Anwender mitgegeben wurden. Man kann mit ihnen genauso arbeiten wie mit allen anderen Variablen, allerdings kann man ihnen keine Werte zuweisen. Der Grund dafür ist, dass Variablennamen nicht mit einer Ziffer beginnen dürfen. Die Variable $0 ist stets gesetzt. Dort steht der Programmname, wie er beim Aufruf verwendet wurde. Die Anzahl der Parameter insgesamt lässt sich mit $# abrufen. Achtung, bei dieser Zählung bleibt $0 unberücksichtigt, wenn $# also "4" liefert, bedeutet das, dass es $1 bis $4 plus zusätzlich $0 gibt.

Schauen wir uns doch einmal das Beispiel "meinecho" im nachstehenden Listing an:

#!/bin/bash
 echo [$#]: $1 $2 $3 $4 $5 $6 $7 $8 $9 ${10} ${11} ${12}

Die geschweiften Klammern ab Parameter zehn sind übrigens notwendig, sonst würde die Bash, wie von den Arrays bekannt, zuerst $1 einsetzen und dann eine Null anhängen, usw. Das kleine Programm schreiben Sie einfach in einem Texteditor, zum Beispiel kedit, mcedit oder gar emacs, und speichern es unter dem Namen meinecho ab. Anschließend müssen Sie die Datei mit dem Befehl chmod a+x meinecho noch ausführbar machen, und rufen es zwei mal auf:

./meinecho 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
 [15]: 1 2 3 4 5 6 7 8 9 10 11 12./meinecho "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15"
 [1]: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Das unterschiedliche Ergebnis der beiden Aufrufe liegt in den Anführungszeichen begründet. Während wir beim ersten mal laut $# 15 Parameter haben, handelt es sich beim zweiten Aufruf nur noch um einen. Die Bash interpretiert Leerzeichen als Trenner zwischen zwei Parametern bei Programmaufrufen, bei den 15 durch Leerzeichen getrennten Zahlen handelt es sich folglich auch um 15 Parameter. Wie schon im ersten Teil angerissen, müssen wir Parameter, die Leerzeichen enthalten, entweder komplett quoten oder die Leerzeichen escapen. Im zweiten Aufruf hat die Bash also nur noch einen Parameter, bestehend aus allen 15 durch Leerzeichen getrennten Zahlen, $2 bis ${12} aus unserem Programm bleiben unbenutzt.

Um eine beliebige Anzahl von Parametern ausgeben zu können, müssten wir alle Parameter-Variablen von $1 bis zum Ende durchgehen und einzeln ausgeben, zum Beispiel in einer Schleife. Die beiden Spezial-Variablen $* und $@ nehmen uns die Arbeit ab, hier am Beispiel unseres veränderten meinecho:

#!/bin/bash
 echo [$#]: $*

Der Unterschied zwischen $* und $@ wird nur deutlich, wenn man beide in Anführungszeichen setzt. Dann wird bei vier Parametern aus "$*" nämlich "$1 $2 $3 $4", während "$@" zu "$1" "$2" "$3" "$4" wird. Auf den ersten Blick scheint der Unterschied belanglos zu sein. Das liegt aber an der Voreinstellung der Bash-internen Variablen IFS. Das erste Zeichen dieser Variablen wird bei "$*" zur Unterteilung der einzelnen Parameter benutzt. Normal enthält IFS drei Zeichen, nämlich das Leerzeichen, den Tabulator und Enter. Folgendes Beispiel zeigt den Unterschied, indem wir den Standard-Zeichen in IFS noch ein Komma voranstellen:

#!/bin/bash
 IFS=",$IFS"
 echo [$#]: "$*"
 echo [$#]: "$@"

Die dritte Zeile listet uns nun alle Zahlen durch ein Komma getrennt auf, die vierte hingegen wird immer noch durch Leerzeichen getrennt. Anders herum ist $@ auch nicht überflüssig, wir nehmen für dieses Beispiel ein neues Programm mit Namen welchedateien:

#!/bin/bash
 ls -l "$@"
 echo —
 ls -l "$*"
 echo —
 ls -l $@
 echo —
 ls -l $*

Vergessen Sie bitte nicht, mit chmod a+x welchedateien das Programm auch ausführbar zu machen. Jetzt brauchen wir noch zwei Dateien, eine mit Leerzeichen im Namen. Anschließend rufen wir welchedateien auf und geben – Vorsicht mit dem Leerzeichen – beide Dateien als Parameter an:

echo "Hallo Welt" > "hallo welt.txt"
 echo "Das ist das Haus vom Nikolaus" > nikolaus.txt
 ./welchedateien "hallo welt.txt" nikolaus.txt

Das Ergebnis bedarf einer etwas umfangreicheren Erläuterung. Wir haben hier alle vier Schreibweisen von $* und $@ hintereinander gesetzt, die echo-Anweisungen dienen nur der besseren Übersicht als Trennzeichen. Der erste ls-Aufruf zeigte uns korrekt beide Dateien, hallo welt.txt und nikolaus.txt. Wie erwartet wurde "$@" zu "hallo welt.txt" und "nikolaus.txt", ls bekam also zwei Parameter und zeigte beide Dateien an. Das zweite ls beschwerte sich zu Recht, keine "hallo welt.txt nikolaus.txt" finden zu können. Der Grund ist die Eigenschaft von "$*", alle Parameter durch ein Zeichen getrennt in Anführungszeichen gesetzt abzuliefern. Dementsprechend hat ls auch nur einen Parameter erhalten, nämlich "hallo welt.txt nikolaus.txt". Die Aufrufe drei und vier liefern das gleiche Ergebnis; hier werden die beiden Parameter durch ein Leerzeichen getrennt dem ls mitgegeben. Durch das zweite Leerzeichen in hallo welt.txt waren es für ls drei Parameter, hallo und welt.txt wurden also korrekt beanstandet, diese beiden Dateien gibt es nicht.

Zuletzt seien noch die Variablen $? und $! erwähnt, sie dienen der Abfrage von Programm-Rückgabewerten, dem sogenannten exit status. Im Moment sind sie für uns noch nicht von Bedeutung, eine Kurzbeschreibung finden Sie in der Tabelle "Spezialvariablen der Bash".

String-Verarbeitung

In den meisten Scriptsprachen, wie auch in der Bash, geht es in erster Linie um die Verarbeitung von Zeichenketten, auch "Strings" genannt. Darunter versteht man eine Folge von Buchstaben, Zahlen, Sonder- und Steuerzeichen, zum Beispiel das Kapitel eines Buches. Falsch wäre es, nur Buchstaben, Zahlen und etwaige Sonderzeichen bei den Zeichenketten zu berücksichtigen, denn ein String kann durchaus mehrere Zeilen lang sein und andere Formatierungszeichen wie Tabulatoren oder einen Seitenvorschub enthalten.

Diese Klarstellung ist wichtig – wir müssen uns stets bewusst sein, dass sich in einer solchen String-Variablen durchaus ein ganzer Roman oder auch eine Datei befinden kann.

Die Zuweisung und Ausgabe einer Zeichenkette haben wir an vielen Beispielen gezeigt. Nun geht es um die Möglichkeiten, etwas über den in der Variablen steckenden Text zu erfahren und ihn manipulieren zu können. Im Gegensatz zur Version 1.1 der Bash stehen uns heute recht umfangreiche Funktionen zur Verfügung, doch fangen wir einmal mit etwas ganz Banalem an.

Wie bekommt man heraus, dass eine Variable leer ist? Nun, eine Möglichkeit wäre, den Inhalt mit einem leeren String zu vergleichen (damit beschäftigen wir uns bei den Kontrollstrukturen), oder aber einfach die Länge des Strings zu bestimmen:

a=""echo ${#a}
 0a="vier"echo ${#a}
 4

Die Anführungszeichen bei der Zuweisung können wir weglassen, auch wenn es bei der ersten Zeile etwas ungewöhnlich aussehen würde.

Wir können sogar – ohne bisher Kontrollstrukturen eingeführt zu haben – auf einen nicht gesetzten Parameter mit einer Fehlermeldung reagieren:

#!/bin/bash
 a="Hallo"
 : ${a:?'ja'}
 echo 'nein'

Das kleine Programm speichern Sie unter ist-a-leer ab und rufen es auf, nachdem Sie es mit chmod ausführbar gemacht haben:

./ist-a-leer
 nein

Das Programm antwortet ordnungsgemäß mit "nein", was nicht weiter verwunderlich ist, schließlich haben wir es ja mit echo ausgeben lassen. Entfernen Sie nun Hallo aus der zweiten Zeile, so dass a nun leer ist, und rufen Sie das Programm noch einmal auf:

./ist-a-leer
 ./ist-a-leer: a: ja

Deutlich zu sehen ist, dass unser echo 'nein' aus der letzten Zeile des Programms nicht mehr ausgeführt wurde. Das ist das Ergebnis der dritten Zeile. Den Doppelpunkt-Befehl kennen wir aus der letzten Folge des Programming Corner: Er macht gar nichts. Die Parameter hinter dem Doppelpunkt werden aber noch betrachtet, und dort ist die Auswertung versteckt. Die Anweisung ${variable:?fehlermeldung} sieht zunächst nach, ob variable leer ist oder überhaupt nicht existiert. Wenn ja, wird die hinter dem Fragezeichen stehende Fehlermeldung ausgegeben und das Programm abgebrochen. Das war der Grund, wieso das echo aus der vierten Zeile gar nicht erst zum Zuge kam. Bei Ausgabe der Fehlermeldung hat Bash in üblicher Manier noch den Programmnamen und die Stelle, an der der Fehler auftrat, vorne angefügt.

Für die nächsten zwei Anweisungen wandeln wir unser Script welchedateien von vorhin ab:

#!/bin/bash
 ls -1 ${1:-$HOME}

Die Funktionsweise ist nun, dass wir entweder das angegebene Verzeichnis, oder aber den Inhalt unseres Heimat-Verzeichnisses $HOME aufgelistet bekommen:

./welchedateien /bin
 arch
 bash
 cat
 …./welchedateien
 dead.letter
 mail …

Die Anweisung ${variable:-string} bewirkt, dass dort bei leerer oder nicht existenter variable string eingesetzt wird. In unserem Fall der Inhalt der Variablen $HOME, ansonsten der Inhalt von $variable. Im Programm lassen wir den ersten Parameter prüfen. Haben wir ein Verzeichnis angegeben, ist $1 gefüllt und die Anweisung liefert den Inhalt. Andernfalls wird $HOME eingesetzt.

Fast die gleiche Wirkung hat ${variable:=string}, jedoch wird hier string zusätzlich der variable zugewiesen, falls sie leer ist oder nicht existiert:

#!/bin/bash
 Verzeichnis="$1"
 : ${Verzeichnis:=$HOME}
 echo "Zeige $Verzeichnis"
 ls -1 "$Verzeichnis"

Ähnlich wie die beiden vorangegangenen Anweisungen wirkt ${variable:+string}. Der Unterschied besteht darin, dass string immer dann eingesetzt wird, wenn variable etwas enthält. Ist sie leer, wird nichts eingesetzt. Diese Anweisung wird in der Praxis nur recht selten verwendet, weshalb ich auch kein sinnvolles Beispiel dafür gefunden habe.

Im nächsten Teil des Programming Corner werden wir uns noch das Herauslösen von Teil-Strings sowie Suchen/Ersetzen mit regulären Ausdrücken in der Bash ansehen. Für dieses mal bleibt mir nur noch, Ihnen eine angenehme Weihnacht und einen guten Start in das bevorstehende neue Jahrtausend zu wünschen.

Begriffe

  • Zeichenkette, String: Eine Folgen von Buchstaben, Zahlen, Sonderzeichen und Steuerzeichen. Zeichenketten können durchaus mehrere Zeilen enthalten, auch kann der Inhalt aus einer Programm- oder Bilddatei bestehen.
  • Feld, Array: Besteht aus mehreren Elementen, die über Nummern angesprochen werden. In eindimensionalen Arrays ist auch nur eine Nummer für die Auswahl eines Elements erforderlich, bei zweidimensionalen zwei, usw.
  • Element eines Arrays: Wird über seine Position angesprochen. Die Elemente können wie reguläre Variablen verwendet werden, es können Werte gespeichert und abgerufen werden.
  • Rückgabewert, exit status: Wert, der dem Aufrufer zurückgeliefert wird, wenn ein Programm endet. Die Rückgabe ist vom System garantiert. Nicht zu verwechseln mit Meldungen auf dem Bildschirm. Bei erfolgreicher Ausführung des Programms wird in aller Regel 0 zurückgegeben, traten Fehler oder Probleme auf, ist der Wert größer als 0. Oft kann man anhand des Rückgabewerts den Fehler bestimmen, die Hinweise, zur Bedeutung finden sich meist in der Programmdokumentation.

Spezialvariablen der Bash

$* Alle dem Programm übergebenen Parameter, durch Leerzeichen getrennt. $*=$1 $2 $3 $4 …
"$*" Alle dem Programm übergebenen Parameter in Anführungszeichen, durch das 1. Zeichen (c) der Variablen IFS getrennt. "$*"="$1c$2c$3c$4" …
$@ Alle dem Programm übergebenen Parameter, durch Leerzeichen getrennt. $@=$1 $2 $3 $4
"$@" Alle dem Programm übergebenen Parameter, einzeln in Anführungszeichen eingeschlossen und durch Leerzeichen getrennt. "$@"="$1" "$2" "$3" "$4" …
$# Anzahl der übergebenen Parameter
$? Rückgabewert des letzten Befehls
$$ Prozess-ID (PID) des laufenden Programms
$! Prozess-ID (PID) des zuletzt im Hintergrund gestarteten Programms
$ Letzter Parameter des zuletzt aufgerufenen Programms

LinuxCommunity kaufen

Einzelne Ausgabe
 
Abonnements
 

Ähnliche Artikel

  • Einführung in die Bash-Programmierung
    Skripte sollen meist wiederkehrende oder lästige Arbeiten automatisieren. Die Standard-Shell Bash stellt Ihnen dazu eine ganze Reige von Funktionen bereit. Dieser Artikel erklärt, wie Sie ihre eigenen Shell-Skripte schreiben.
  • Teil 1: Grundlagen der BASH
    Haben Sie auch schon einmal am Computer gesessen und sich über immer wiederkehrende Arbeit geärgert, die man mit einem kleinen Programm vereinfachen könnte? Im ersten Teil unseres Programming Corners steigen wir in die Welt der Programmierung ein.
  • Teil 3: String-Verarbeitung und Reguläre Ausdrücke
    Die Grundlagen der Zeichenketten haben wir in der letzten Ausgabe gelegt. Diesmal wollen wir uns aber nicht mit einfachen Leer-Tests oder Längenanzeigen begnügen, sondern unsere Strings kräftig durcheinander wirbeln.
  • Teil 5: Kontrollstrukturen die Zweite
    Nach der Einführung in die Kontrollstrukturen und Vorstellung von einfachen Vergleichsmöglichkeiten im letzten Teil beschäftigen wir uns diesmal mit Reihenvergleichen, Schleifen, Tastatureingaben und kleinen Auswahlmenüs.
  • Im Innern der Muschel
    Mit der Version 4 erhält die Bourne Again Shell (Bash) neue Funktionen, die sie einmal mehr als leistungsfähige Programmierschnittstelle zum System und zu Applikationen qualifiziert.
Kommentare

Infos zur Publikation

LU 11/2014: VIDEOS BEARBEITEN

Digitale Ausgabe: Preis € 4,95
(inkl. 19% MwSt.)

Mit der Zeitschrift LinuxUser sind Sie als Power-User, Shell-Guru oder Administrator im kleinen Unternehmen monatlich auf dem aktuelle Stand in Sachen Linux und Open Source.

Sie sind sich nicht sicher, ob die Themen Ihnen liegen? Im Probeabo erhalten Sie drei Ausgaben zum reduzierten Preis. Einzelhefte, Abonnements sowie digitale Ausgaben erwerben Sie ganz einfach in unserem Online-Shop.

NEU: DIGITALE AUSGABEN FÜR TABLET & SMARTPHONE

HINWEIS ZU PAYPAL: Die Zahlung ist auch ohne eigenes Paypal-Konto ganz einfach per Kreditkarte oder Lastschrift möglich!       

Tipp der Woche

Schnell Multi-Boot-Medien mit MultiCD erstellen
Schnell Multi-Boot-Medien mit MultiCD erstellen
Tim Schürmann, 24.06.2014 12:40, 0 Kommentare

Wer mehrere nützliche Live-Systeme auf eine DVD brennen möchte, kommt mit den Startmedienerstellern der Distributionen nicht besonders weit: Diese ...

Aktuelle Fragen

Artikelsuche
Erwin Ruitenberg, 09.10.2014 07:51, 1 Antworten
Ich habe seit einige Jahre ein Dugisub LinuxUser. Dann weiß ich das irgendwann ein bestimmtes Art...
Windows 8 startet nur mit externer Festplatte
Anne La, 10.09.2014 17:25, 6 Antworten
Hallo Leute, also, ich bin auf folgendes Problem gestoßen: Ich habe Ubuntu 14.04 auf meiner...
Videoüberwachung mit Zoneminder
Heinz Becker, 10.08.2014 17:57, 0 Antworten
Hallo, ich habe den ZONEMINDER erfolgreich installiert. Das Bild erscheint jedoch nicht,...
internes Wlan und USB-Wlan-Srick
Gerhard Blobner, 04.08.2014 15:20, 2 Antworten
Hallo Linux-Forum: ich bin ein neuer Linux-User (ca. 25 Jahre Windows) und bin von WIN 8 auf Mint...
Server antwortet mit falschem Namen
oin notna, 21.07.2014 19:13, 1 Antworten
Hallo liebe Community, Ich habe mit Apache einen Server aufgesetzt. Soweit, so gut. Im Heimnet...