Script-Programmierung

Variablen und Arrays in der bash

  1. Variablen
  2. Arrays
  3. assoziative Arrays
  4. Verarbeitung von Arrays in Schleifen
    1. Die Operatoren !, #, @ und *
    2. Achtung, Quoting!
    3. Achtung, Maximallänge einer Befehlszeile
    4. Bearbeitung von Arrays mit bekanntem Index
    5. Arrays unbekannter Größe
  5. Arrays, Arrayelemente und Variablen entfernen
  6. Arrays verbinden

Die bash unterstützt die Verwendung von Variablen und eindimensionalen Arrays. Diese müssen vor der ersten Benutzung im Gegensatz zu anderen Programmiersprachen nicht vorher deklariert werden. Die Variablen sind auch nicht typisiert, der Inhalt kann ein Zahlenwert (Integerarithmetik der bash) oder ein String sein. Das vereinfacht zum einen den Code und erleichtert zunächst auch die Programmierung, bringt aber auch Probleme mit sich, da sich durch schlampige Programmierung sehr leicht Fehler einschleichen können. Die Kommandos declare und typeset schaffen hier nur begrenzt Abhilfe. Allgemein werden Variablen durch eine Wertzuweisung deklariert, indem sie erstmals mit ihrem Namen aufgerufen werden.

  1. Variablen

    Die Verwendung von Variablen in einem Script könnte folgendermaßen aussehen:
    Script Ausgabe im Terminal
    #!/bin/bash
    TESTA=
    TESTB=1
    TESTC=/var/log/
    TESTD=Hallo
    TESTE="Hallo Oma"
    TESTF=$TESTC
    TESTG="$TESTC ist ein Pfad in der Variable \$TESTC"
    TESTH=$((TESTB+8))
    TESTI=$(date)
    TESTJ='$TESTC'
    echo "Die Variable TESTA enthält den Wert $TESTA"
    echo "Die Variable TESTB enthält den Wert $TESTB"
    echo "Die Variable TESTC enthält den Wert $TESTC"
    echo "Die Variable TESTD enthält den Wert $TESTD"
    echo "Die Variable TESTE enthält den Wert $TESTE"
    echo "Die Variable TESTF enthält den Wert $TESTF"
    echo "Die Variable TESTG enthält den Wert $TESTG"
    echo "Die Variable TESTH enthält den Wert $TESTH"
    echo "Die Variable TESTI enthält den Wert $TESTI"
    echo "Die Variable TESTJ enthält den Wert $TESTJ"
    bash-4.1$ ./test
    Die Variable TESTA enthält den Wert 
    Die Variable TESTB enthält den Wert 1
    Die Variable TESTC enthält den Wert /var/log/
    Die Variable TESTD enthält den Wert Hallo
    Die Variable TESTE enthält den Wert Hallo Oma
    Die Variable TESTF enthält den Wert /var/log/
    Die Variable TESTG enthält den Wert /var/log/ ist ein Pfad in der Variable $TESTC
    Die Variable TESTH enthält den Wert 9
    Die Variable TESTI enthält den Wert Sa 21. Jul 14:03:10 CEST 2012
    Die Variable TESTJ enthält den Wert $TESTC
    bash-4.1$ 
    

    Die Zeilen des Scripts im einzelnen haben folgende Bedeutung.
    Die Variable TESTA wird deklariert, aber es wird kein Wert zugewiesen. Die Variable enthält einen Nullstring, also eine Zeichenkette ohne Zeichen.
    Die Variable TESTB wird deklariert und es wird der Wert 1 zugewiesen. Da kein Variablentyp festgelegt wurde, entscheidet der Kontext in dem die Variable verwendet wird, ob es sich um einen String oder eine Integerzahl handelt.
    In der Variable TESTC wird der Name eines Pfades abgelegt und in TESTD das Wort Hallo.
    In die Variable TESTE soll nun ein Text mit Leerzeichen abgelegt werden. Hier ist eine Quotung des Leerzeichens undbedingt notwendig. Der Text wurde dazu in Anführungszeichen gesetzt und das Leerzeichen so vor der bash versteckt. Die Variable enthält danach nur den Text innerhalb der Anführungszeichen, da diese als Quotungszeichen vor der Wertzuweisung entfernt werden. Die bash interpretiert also auch diese Zuweisungszeilen, ähnlich, wie sie es bei Kommandoaufrufen auch tut. Siehe dazu die Erklärungen zum Quoting im Abschnitt Hallo Welt.
    Die Zuweisung der Variable TESTF ist nun eine Besonderheit. Das Zeichen $ ist ein Sonderzeichen der bash und bewirkt eine Parameterexpansion. Dabei ersetzt die bash den Code $TESTC durch den Inhalt der Variablen TESTC und dieser wird dann der Variablen TESTF zugewiesen. Die Variablen TESTF und TESTC haben damit den selben Inhalt.
    Da die Expansionen der bash ein sehr mächtiges Werkzeug ist, kann man auch die Wertzuweisungen sehr flexibel gestalten. So wird der Variable TESTG ein Text zugewiesen, der den Inhalt der Variable TESTC enthält. Das Sonderzeichen $ wird durch die doppelten Anführungszeichen nicht gequotet, sondern nur durch einfache Anführungszeichen oder den backslash.
    Bevor die Variable TESTH zugewiesen wird, führt die bash eine Arithmeticexpansion durch, dazu wird der Inhalt der Variable TESTB mit 8 addiert und das Ergebnis in TESTH gespeichert. Die bash erlaubt in der Arithmeticexpansion eine ganze Reihe von Integeroperationen. Sollte die Variable TESTB keine Zahl, sondern Text enthalten, so wird dieser als Null interpretiert.
    Bei der Zuweisung der Variable TESTI kommt nun eine weitere Expansion zum Einsatzt und zwar die Kommandosubstitution. Dabei wird durch die bash das Kommando date Kommando ausgeführt und die Ausgabe des Kommandos auf Standardout in die Variable umgeleitet. Da das Kommando date das Systemdatum ausgibt, ist dieses nun in der Variable abgelegt.
    Die hier beschriebenen Expansionen sind keine Besonderheit der Variablenzuweisung, sondern eine grundlegende Funktionalität der bash. Sie kann überall in Scripten zur Anwendung kommen.
    In den folgenden Zeilen werden die Inhalte der Variablen durch das echo-Kommando wieder ausgegeben.

    Die Expansion von Variablen durch $NAME ist nicht immer eindeutig und kann unter Umständen zu verwechslungen führen. Sinnvoller ist es, den Wert einer Variablen immer mit der Syntax ${NAME} durchzuführen.

    Script Ausgabe im Terminal
    #!/bin/bash
    TEST=50
    echo "$TEST Spieler"
    echo "$TESTSpieler"
    echo "${TEST}Spieler"
    bash-4.1$ ./test
    50 Spieler
    
    50Spieler
    bash-4.1

    Die Variable TEST enthält den Wert 50. In der Ausgabe soll der Wert der Variable in einen Ausgabetext eingefügt werden. In der ersten echo-Anweisung gelingt die Expansoin, da der Variablenname durch das Leerzeichen deutlich vom restlichen Text abgegrenzt ist, so dass die bash den Wert der Variable TEST expandieren kann. In der zweiten Zeile gibt es nur eine Ausgabe einer Leerzeile, denn die bash hat keine Möglichkeit zu erkennen, dass nur der String TEST den Variablennamen bildet und gibt deshalb den Wert der Variable TESTSpieler aus. Da diese Variable nicht deklariert ist, wird ein Leerstring ausgegeben. Übrigens wird eine solche nicht existierende Variable durch die Expansion nicht deklariert, dass bedeutet, dass keine Variable TESTSpieler erzeugt wird.
    In der dritten Zeile ist zu sehen, dass der Text richtig ausgegeben wurde. Durch die Verwendung der geschweiften Klammern wurde der Variablenname zur Expansion eindeutig gekennzeichnet.

    Im Zusammenhang mit bash-Variablen sind zwei Parametersubstitutionen besonders wichtig. Die Syntax ${#NAME} erzeugt eine Längenexpansion. Dabei wird die Anzahl der Zeichen, die die Variable NAME enthält, zurückgegeben.
    Die zweite Parametersubstitutionen ist ${!NAME*}. Hierbei handelt es sich um eine Präfix-Expansion, dabei werden die Namen aller deklarierten Variablen zurückgegeben, deren Name mit NAME beginnt.

    Script Ausgabe im Terminal
    #!/bin/bash
    TESTA=50
    TESTB=Euro     
    TESTC="sind einfach zu teuer"
    SUMME=
    echo "${#TESTA}"     
    echo "${#SUMME}"           
    echo "${#ABO}"           
    echo "${!TES*}"
    bash-4.1$ ./test
    2
    0
    0
    TESTA TESTB TESTC
    bash-4.1$

    Die erste Ausgabe ist die Expansion von ${#TESTA} und ergibt den Wert 2, denn die Variable TESTA enthält genau zwei Zeichen. In der zweiten und dritten Zeile erscheint jeweils eine Null, denn die Variable SUMME ist zwar declariert, aber leer und die Variable ABO existiert nicht.
    In der vierten Zeile werden die Namen aller Variablen zurückgegeben, deren Namen mit TES beginnen, also TESTA, TESTB und TESTC.

  2. Arrays

    Die Arbeit mit Arrays funktioniert auf ähnliche Weise, wie die Arbeit mit Variablen. Genau wie Variablen kann ein Array durch Wertzuweisung deklariert werden. Das geschieht in der Form NAME[INDEX]=WERT. Damit ist ein Array mit dem Namen NAME deklariert.
    In der bash sind solche Arrays grundsätzlich eindimensional und werden mit einem nicht negativen Integerwert indiziert. Das erste Arrayfeld wird also mit 0 (Null) und die weiteren mit 1, 2, 3... adressiert. Arrays dürfen auch "Löcher" besitzen, d.h. der Index muss nicht fortlaufend benutzt werden. Im Normalfall ist der Inhalt der einzelnen Arrayelemente typfrei, mit declare und typeset kann jedoch der Typ der Arrayelemente festgelegt werden. Besonderheiten entstehen durch die Verwendung der Sonderzeichen @, * und #. Die Zeichen @ und * stellen einen Bezug zu allen Elementen des Arrays her. Sie verhalten sich ähnlich wie bei der Verwendung der Positionsparameter der bash in der Parameterübergabe. Dadurch kann sich z.B. die Bestimmung der Länge durch # einmal auf ein einzelnes Arrayelement und einmal auf das ganze Array beziehen. Die Zuweisung des Arrayinhaltes geschieht entweder durch Zuweisung der einzelnen Arrayelemente oder durch Zuweisung des gesamten Arrays. Bei der Expansion von Werten des Arrays ist die Schreibweise in geschweiften Klammern zwingend vorgeschrieben. Eine Expansion ohne diese Klammern führt immer nur zu einem Zugriff auf das Arrayelement mit dem Index 0!!! Hier wieder ein Beispielscript:

    Script Ausgabe im Terminal
    #!/bin/bash
    FELDA[0]=
    FELDA[1]=1
    FELDA[2]=/var/log/
    FELDA[10]=Hallo
    FELDA[17]="Hallo Oma"
    FELDA[18]=${FELDA[2]}
    FELDB=(3 4 "der test" /var/spool )
    FELDC=([1]=7 23 [10]="wert" [20]=19 21 33 "$(date)" )
    echo "Das Arrayelement FELDA[0] enthält den Wert ${FELDA[0]}"
    echo "Das Arrayelement FELDA[2] enthält den Wert ${FELDA[2]}"
    echo "Das Arrayelement FELDA[3] enthält den Wert ${FELDA[3]}"
    echo "Das Array FELDA enthält die Elemente ${FELDA[*]}"
    echo "Das Array FELDB enthält die Elemente ${FELDB[*]}"
    echo "Das Arrayelement FELDC[0] enthält den Wert ${FELDC[0]}"
    echo "Das Arrayelement FELDC[1] enthält den Wert ${FELDC[1]}"
    echo "Das Arrayelement FELDC[2] enthält den Wert ${FELDC[2]}"
    echo "Das Arrayelement FELDC[3] enthält den Wert ${FELDC[3]}"
    echo "Das Arrayelement FELDC[10] enthält den Wert ${FELDC[10]}"
    echo "Das Arrayelement FELDC[10] enthält ${#FELDC[10]} Zeichen"
    echo "Das Array FELDC enthält ${#FELDC[*]} Elemente"
    bash-4.1$ ./test
    Das Arrayelement FELDA[0] enthält den Wert 
    Das Arrayelement FELDA[2] enthält den Wert /var/log/
    Das Arrayelement FELDA[3] enthält den Wert 
    Das Array FELDA enthält die Elemente  1 /var/log/ Hallo Hallo Oma /var/log/
    Das Array FELDB enthält die Elemente 3 4 der test /var/spool
    Das Arrayelement FELDC[0] enthält den Wert 
    Das Arrayelement FELDC[1] enthält den Wert 7
    Das Arrayelement FELDC[2] enthält den Wert 23
    Das Arrayelement FELDC[3] enthält den Wert 
    Das Arrayelement FELDC[10] enthält den Wert wert
    Das Arrayelement FELDC[10] enthält 4 Zeichen
    Das Array FELDC enthält 7 Elemente
    bash-4.1$ 

    Die Zeilen bedeuten im Einzelnen:
    Es werden drei Arrays FELDA, FELDB und FELDC deklariert.
    Das Arrayelement FELDA[0] wird mit einem Leerstring belegt, das Array ist damit definiert.
    In den beiden folgenden Feldern werden die Werte 1 und /var/log/ abgelegt. Dann wird das Array weiter mit Lücken definiert, d.h. es gibt freie Arrayelemente, die bei der Abfrage einen Leerstring enthalten. Die Arrayelemente 10 und 17 enthalten zwei Strings. Dem Arrayelement 18 wird der Wert aus dem Arrayelement 2 zugeordnet, beide enthalten damit den String /ver/log/.
    Das Array FELDB wird komplett und fortlaufend definiert, es werden die Arrayelemente 0 bis 3 werden mit Werten belegt. Wird ein Array auf diese Weise definiert, dann gehen alle vorher im Array gespeicherten Werte verloren. Mit dieser Methode können freie Arrayelemente also nicht nachgeladen werden.
    Das Array FELDC wird auch komplett und mit Lücken definiert, indem mit dem Index [indexnummer]= die genaue Postition der Feldelemente festgelegt wird. Dieses Array enthält damit seine Werte in den Arrayelementen 1, 2, 10, 20, 21, 22 und 23.
    Die folgenden Zeilen dienen der Ausgabe verschiedener Arrayelemente. Zur Abfrage der Arrayinhalte ist die Verwendung der geschweiften Klammer (brace) zwingend notwendig. Außerdem ist es möglich, die Länge einzelner Inhalte, sowie die Anzahl der Feldelemente im Array abzufragen.
    Wenn der Index mit dem ein Arrayelement adressiert wird keine natürliche Zahl ist, sondern ein negativer Wert (z.B.-1) oder ein Bruch (z.B. 0.5) dann führt das zu einer Fehlermeldung. Ist der Index ein String, dann wird er mit dem Wert 0 gleich gesetzt, so dass auf des Arrayelement mit dem Index 0 zugeriffen wird. Wird kein Index angegeben, also nur der Name des Arrays benutzt, so erfolgt der Zugriff ebenfalls auf das Arrayelement mit dem Index 0.
    Hier ein kleiner Auszug dazu aus einem Terminal:

    bash-4.1$ TEST[1]="hallo"
    bash-4.1$ declare -p TEST
    declare -a TEST='([1]="hallo")'
    bash-4.1$ TEST[-1]="Otto"
    bash: TEST[-1]: Falscher Feldbezeichner.
    bash-4.1$ TEST[0.5]="Otto"
    bash: 0.5: Syntaxfehler: Ungültiger arithmetischer Operator. (Fehlerverursachendes Zeichen ist \".5\").
    bash-4.1$ TEST[Kamel]="Otto"
    bash-4.1$ declare -p TEST
    declare -a TEST='([0]="Otto" [1]="hallo")'
    bash-4.1$ echo $TEST
    Otto
    bash-4.1$ TEST="peter"
    bash-4.1$ declare -p TEST
    declare -a TEST='([0]="peter" [1]="hallo")'
    bash-4.1$
    

    In der ersten Zeile wird ein Array TEST durch Wertzuweisung deklariert. Es wird das Arrayelement mit dem Index 1 erzeugt, das den String hallo enthält. Das Kommando declare -p TEST zeigt das danach auch an. Als nächstes wird nun versucht, ein Arrayelement mit dem Index -1 anzulegen, was zu einer fehlermeldung der bash führt. Ähnlich ist es mit der folgenden Zeile, in der der Index 0.5 verwendet wird.
    Damach wird als Index der String Kamel verwendet, wobei keine Fehlermeldung ausgegeben wird!!! Statt dessen wird das Arrayelement TEST[0] mit dem Wert Otto belegt, was declare -p TEST zeigt. Als letztes erfolgt eine Ausgabe und eine Wertzuweisung ohne Angabe eines Index und es ist zu erkennen, dass wieder auf das Arrayelement mit dem Index 0 zugegriffen wird.

  3. assoziative Arrays

    Die bash kann seit einiger Zeit auch mit assoziativen Arrays umgehen. Dabei handelt es sich um Arrays, deren Index ein String ist. Solche Arrays müssen in jedem Fall durch declare vorher deklariert werden. Dazu verwendet man folgende Syntax:
    declare -A NAME
    Nun kann das Array benutzt werden, wober der Index nun aus Worten oder Sätzen bestehen kann. Die einzelnen Arrayelemente werden dann durch Wertzuweisung deklariert. Das ganze kann dann z.B. so aussehen:

    Script
    #!/bin/bash
    declare -A TEST
    TEST["Stadt"]="Berlin"                         
    TEST["Die Breite"]=350
    TEST['die Zwei']="Hallo Opa"
    TEST[ein test]=34
    TEST[12]="Ganz toll!"
    TEST[Hans]="Federn"
    
    declare -p TEST
    
    echo "Das Arrayelement Stadt enthaelt den Wert ${TEST[Stadt]}"
    echo "Das Arrayelement Die Breite entaelt ${#TEST['Die Breite']}Zeichen"       
    echo "Das Array besitzt ${#TEST[*]} Elemente"        
    echo "Das Array hat die folgenden Indizes und Werte"
    for INDEX in "${!TEST[@]}"
    do
     echo "Index=\"${INDEX}\" Wert=\"${TEST[$INDEX]}\""
    done
    Ausgabe im Terminal
    bash-4.1$ ./test5
    declare -A TEST='([12]="Ganz toll!" [Stadt]="Berlin" ["Die Breite"]="350" ["die Zwei"]="Hallo Opa" ["ein test"]="34" [Hans]="Federn" )'
    Das Arrayelement Stadt enthaelt den Wert Berlin
    Das Arrayelement Die Breite entaelt 3Zeichen
    Das Array besitzt 6 Elemente
    Das Array hat die folgenden Indizes und Werte
    Index="12" Wert="Ganz toll!"
    Index="Stadt" Wert="Berlin"
    Index="Die Breite" Wert="350"
    Index="die Zwei" Wert="Hallo Opa"
    Index="ein test" Wert="34"
    Index="Hans" Wert="Federn"
    bash-4.1$

    Bei der Wahl des Index hat man einen recht großen Freiraum, der String für den Index muss nur gequotet werden, wenn es absolut notwendig ist. Natürlich sind auch Zahlen möglich.

    Achtung!
    Wird das assoziative Array nicht vorher deklariert, dann nimmt die bash einige Zuweisungen mit Textindex klaglos hin. Es wird dann aber ein normales Array angelegt und der Index immer als 0 interpretiert.

  4. Verarbeitung von Arrays in Schleifen

    1. Die Operatoren !, #, @ und *

      Kommen wir nun zu den Expansionszeichen !, #, @ und *. Mit diesen kann auf das Array in besonderer Weise zugegriffen werden und wegen ihrer Besonderheit kommen sie häufig beim sequentiellen bearbeiten von Arrays in Schleifen zum Einsatz. Dabei werden jedoch häufig Fehler gemacht, die zu unerwarteten Reaktionen von Scripten führen.
      Zuerst eine kleine Übersicht über die Wirkungen dieser Operatoren. Sie sollen hier auf ein Array mit dem Namen NAME angewendet werden.

      Syntax Erläuterungen der Expansion
      ${NAME[*]}

      Es werden alle deklarierten Arrayelemente ausgegeben. Die Inhalte der Arrayelemente werden durch jeweils ein Leerzeichen getrennt zurückgegeben.

      "${NAME[*]}"

      Es werden alle deklarierten Arrayelemente ausgegeben. Die Inhalte der Arrayelemente werden durch jeweils ein Leerzeichen getrennt zurückgegeben. Durch die Quotierung entsteht im Ergebnis ein einziger String, der die Inhalte aller deklarierten Arrayelemente enthält.

      ${NAME[@]}

      Es werden alle deklarierten Arrayelemente ausgegeben. Die Inhalte der Arrayelemente werden dabei jeweils als Parameter einzeln ausgegeben.

      "${NAME[@]}"

      Es werden alle deklarierten Arrayelemente ausgegeben. Die Inhalte der Arrayelemente werden dabei jeweils als Parameter einzeln ausgegeben und durch die Quotierungszeichen " eingeschlossen. Im Ergebnis entsteht eine Liste gequoteter Parameter, die die Werte des Arrays enthalten.

      ${#NAME[INDEX]}

      Es wird die Anzahl der Zeichen zurückgegeben, die das Arrayelement mit dem Index INDEX im Array NAME enthält. Leere oder nicht deklarierte Arrayelemente liefern den Wert 0.

      ${!TES*}

      Gibt eine Liste aller definierten Variablen und Arrays zurück, deren Namen mit TES beginnen.

      ${#NAME[*]}
      ${#NAME[@]}

      Die Operatoren * und @ führen dazu, dass alle deklarierten Elemente des Arrays gelesen werden und der Operator # bestimmt die Anzahl. Damit wird im Ergebnis in beiden Fällen die Anzahl der deklarierten Arrayelemente zurückgegeben.

      ${!NAME[*]}

      Der Operator * führt dazu, dass wieder auf alle deklarierten Elemente des Arrays zugegriffen wird. Der Operator ! gibt nun die Indizes nacheinander durch Leerzeichen getrennt aus. Damit wird im Ergebnis entsteht also ein String, der alle verwendeten Indizes durch Leerzeichen getrennt enthält.

      "${!NAME[*]}"

      Durch die Quotierung wird die Indexliste als ein einziger String behandelt.

      ${!NAME[@]}

      Der Operator @ führt dazu, dass wieder auf alle deklarierten Elemente des Arrays zugegriffen wird. Der Operator ! gibt nun die Indizes nacheinander als eine Liste von Einzelparametern zurück.

      "${!NAME[@]}"

      Durch die Quotierung werden die einzelnen Indizes mit den Quotungszeichen ".." eingeschlossen.

    2. Achtung, Quoting!

      Die Bedeutung und Funktionen, sowie die Stolperfallen sollen nun an ein paar einfachen Beispielen demonstriert werden. Die Aufgabe soll nun darin bestehen, dass ein Array definiert wurde und die Werte der Arrayelemente ausgelesen und verarbeitet werden sollen. Bei der "Verarbeitung" soll der Inhalt der Arrayelemente als Parameter an ein Kommando übergeben werden, welches dann irgentwelche Aktionen ausführt. Als Kommando werden ich in allen Fällen echo verwenden, damit wir die Parameterübergabe direkt kontrollieren können. Die Abarbeitung soll hier exemplarisch in einer for-Schleife durchgeführt werden. Die häufigste Fehlerquelle beim scripten überhaupt ist ein falsches oder fehlendes Quoting von Sonderzeichen. Da die bash die White-space Zeichen, wie z.B. das Leerzeichen und den Tabulator als Trennzeichen für das Wordsplitting benutzt, treten Probleme auf, wenn Parameter substituiert werden, die selbst diese Trennzeichen enthalten. In Scripten äußert sich das in Fehlfunktionen, wenn verarbeitete Variablen auch solche Trennzeichen enthalten. Das soll hier an einem kleinen Beispiel bei der Verarbeitung eines Arrays gezeicgt werden.
      Falsch!

      Script Ausgabe im Terminal Erläuterungen
      #!/bin/bash
      TEST[0]="Hund"
      TEST[1]="Katze"
      TEST[2]="Vogel"
      for INDEX in 0 1 2  
      do
       echo ${TEST[$INDEX]}
      done
      
      bash-4.1$ ./test
      Hund
      Katze
      Vogel
      bash-4.1$

      Ein Script, das man so oder so ähnlich an vielen Stellen finden kann und es scheint auch alles in Ordnung zu sein. Die Ausgabe ist wie erwartet, dem Kommando (echo) wurden die Inhalte der Arrayelemente einzeln übergeben und deshalb in einzelnen Zeilen wieder ausgegeben. Das Script besitzt jedoch eine extreme Schwachstelle, die häufig zu Problemen fürt und durch schlampige Programmierung entsteht. Der Parameter, der echo (oder irgend einem anderen Kommando) übergeben wurde ist nicht gequotet!! Das führt zu großen Problemen, wenn der Parameter White-space-Zeichen, also Leerzeichen, Tabulatoren und ähnliches enthält. Wenn die Variablenwerte etwa durch Usereingaben entstehen, kann es hier, ob gewollt oder nicht, zu Fehlern kommen. In diesem Fall versagt das Script komplett. Um das zu demonstrieren, füge ich in die Arrayelemente einige Zeichen ein.

      Script Ausgabe im Terminal Erläuterungen
      #!/bin/bash
      TEST[0]="Hund                Wuff"
      TEST[1]="Katze  und     Maus"
      TEST[2]="               Vogel"
      for INDEX in 0 1 2
      do
       echo ${TEST[$INDEX]}
      done
      bash-4.1$ ./test
      Hund Wuff
      Katze und Maus
      Vogel
      bash-4.1$

      Jetzt ist die Ausgabe nicht wie erwartet. Die eingefügten Leerzeichen und Tabulatoren sind verschwunden. Substitution bedeutet Ersetzung. Die bash erkennt in der Zeile
      echo ${TEST[$INDEX]}
      eine Parametersubstitution und fügt dafür den inhalt des entsprechenden Arrayelements dort ein. Danach sieht die Zeile dann z.B. so aus:

      echo Hund                Wuff
      Nun erfolgt das Wordsplittimg der bash und sie erkennt, das dem Kommando echo zwei Parameter Hund und Wuff folgen, die durch einige Leerzeichen getrennt sind. Um genau das zu verhindern, muss die Programmzeile in:
      echo "${TEST[$INDEX]}"
      geändert werden, denn dann sieht die Zeile nach der Expansion so aus:
      echo "Hund                Wuff"
      und die bash erkennt, dass es sich nur um einen Parameter handelt.

      Besser!

      Script Ausgabe im Terminal Erläuterungen
      #!/bin/bash
      TEST[0]="Hund                Wuff"
      TEST[1]="Katze  und     Maus"
      TEST[2]="               Vogel"
      for INDEX in 0 1 2
      do
       echo "${TEST[$INDEX]}"
      done
      bash-4.1$ ./test
      Hund                Wuff
      Katze   und     Maus
                      Vogel
      bash-4.1$

      Nun erscheinen alle Arrayelemente wie gewünscht auf dem Bildschirm. Jeder Arrayinhalt wird dem Kommando als ein Parameter übergeben. Bei großen Arrays wird man die Indizes auf jeden Fall durch seq erzeugen oder eine arithmetische for-Schleife verwenden.

      Das richtige Quoting ist jedoch nicht nur für den Umgang mit den Inhalten der Arrayelemente wichtig, sondern besonders bei assoziativen Arrays auch bei der Verarbeitung des Index, da dieser ebenfalls Trennzeichen enthalten kann.

    3. Achtung, Maximallänge einer Befehlszeile

      Besonders bei der Arbeit mit Arrays, wie beim Scripten überhaupt, sollte man immer im Hinterkopf behalten, dass eine Befehlszeile eine Maximallänge besitzt. Bei der Parametersubstitution werden die Inhalte der Parameter in die Befehlszeile eingefügt, wodurch ihre Länge meist zunimmt. Besonders bei der Verarbeitung von Arrays, also z.B. bei Verwendung der Parametersubstitution ${NAME[@]} oder ${NAME[*]} wird mit den Inhalten aller Arrayelemente substituiert, wodurch eine Befehlszeile, ja nach Inhalt der Arrayelemente, extrem lang werden kann. Wird die Maximallänge überschritten, so kann das sehr unangenehme Folgen haben, von der Verstümmelung der Befehlszeile bis zum blockieren der bash, auch Abstürze der bash gab es in der Vergangenheit schon. Laut POSIX ist eine Mindestlänge von 4kB vorgeschrieben, auf den meisten Linuxsystemen darf die Maximallänge 32kB betragen.

    4. Bearbeitung von Arrays mit bekanntem Index

      Der einfachste Fall ist der Zugriff auf mehrere Arrayelemente von denen der Index bekannt ist. Im Prinzip war das schon im Abschnitt zum richtigen Quoting zu sehen. Hier nun noch ein paar weitere Beispiele:

      Script Ausgabe im Terminal Erläuterungen
      #!/bin/bash
      declare -a TEST
      TEST[0]="Hallo"
      TEST[1]="Du"  
      TEST[2]="hast"
      TEST[3]=700   
      TEST[4]="Euro"
      TEST[5]="gewonnen"
      
      echo "Schleife 1"
      for INDEX in 0 1 2 3 4 5
      do
       echo "${TEST[${INDEX}]}"
      done
      
      echo
      echo "Schleife 2" 
      for INDEX in 0 2 4
      do
       echo "${TEST[${INDEX}]}"
      done
      
      echo
      echo "Schleife 3"  
      for INDEX in  1 2 5
      do
       echo "${TEST[${INDEX}]}"
      done
      
      echo
      echo "Schleife 4"
      for INDEX in  $(seq 0 5)
      do
       echo "${TEST[${INDEX}]}"
      done
      
      echo
      echo "Schleife 5"
      for INDEX in  $(seq 0 2 5)
      do
       echo "${TEST[${INDEX}]}"
      done
      
      echo
      echo "Schleife 6"
      for((INDEX=0;INDEX<=5;INDEX=INDEX+1))
      do
       echo "${TEST[${INDEX}]}"
      done
      
      echo
      echo "Schleife 7"
      for((INDEX=0;INDEX<=5;INDEX=INDEX+2))
      do
       echo "${TEST[${INDEX}]}"
      done
      
      
      bash-4.1$ ./test
      Schleife 1
      Hallo
      Du
      hast
      700
      Euro
      gewonnen
      
      Schleife 2
      Hallo
      hast
      Euro
      
      Schleife 3
      Du
      hast
      gewonnen
      
      Schleife 4
      Hallo
      Du
      hast
      700
      Euro
      gewonnen
      
      Schleife 5
      Hallo
      hast
      Euro
      
      Schleife 6
      Hallo
      Du
      hast
      700
      Euro
      gewonnen
      
      Schleife 7
      Hallo
      hast
      Euro
      bash-4.1$

      Die Beispiele 1 bis 7 unterscheiden sich nur in der Art und Weise, wie der Index der einzelnen Arrayelemente erzeugt wird. In der for-Schleife kann man die Werte der Laufvariable (hier INDEX) ja einfach durch eine Parameterliste vorgeben, oder z.B. durch Kommandosubstitution (hier seq) erzeugen. In den Beispielen 1 bis 5 wurde das auf unterschiedliche Weise getan.
      In den Beispielen 6 und 7 werden die Indexnummern durch eine arithmetische for-Schleife erzeugt, sodass die einzelnen Indexnummern durch mehr oder weniger komplizierte Rechenoperationen erzeugt werden. Diese Art des Zugriffs auf die Arrayelemente vermeidet bei sehr großen Arrays das Problem der überlangen Befehlszeilen.

      Die Beispiele 1 bis 3 lassen sich auch sehr einfach auf assoziative Arrays übertragen. Die anderen Beispiele sind eher ungeeinet, da man assoziative Arrays im Noramlfall nicht mit Zahlen indizieren wird, auch wenn das natürlich möglich ist. Hier also noch zwei Beispiele für ein assoziatives Array.

      Script Ausgabe im Terminal Erläuterungen
      #!/bin/bash
      declare -A TEST
      TEST[Anrede]="Herr"    
      TEST[Vorname]="Peter"  
      TEST[Nachname]="Meier"
      TEST[Strasse Nr]="Winkelweg 3"
      TEST[Ort]="Berlin"
      
      echo "Schleife 1"
      for INDEX in Anrede Vorname Nachname "Strasse Nr" Ort          
      do
       echo "${TEST[${INDEX}]}"
      done
      
      echo
      echo "Schleife 2"
      for INDEX in Ort Anrede Nachname
      do
       echo "${TEST[${INDEX}]}"
      done
      bash-4.1$ ./test
      Schleife 1
      Herr
      Peter
      Meier
      Winkelweg 3
      Berlin
      
      Schleife 2
      Berlin
      Herr
      Meier
      bash-4.1$

      Diese Array muss zwingend mit declare angelegt werden. Der Zugriff ist dann ähnlich wie in den Beispielen 1 bis 3 oben. Wichtig ist noch die Quotung des Index in der Parameterliste von for, wenn der index Trennzeichen enthält.

    5. Arrays unbekannter Größe

      Soll ein Array bearbeitet werden, dessen Größe unbekannt ist oder sich dynamisch ändert, dann muss diese für die korrekte Arbeit der Schleife bestimmt werden. Das kann man z.B. mit ${#NAME[*]} oder ${NAME[@]} erreichen. Das könnte dann so aussehen:

      Script Ausgabe im Terminal Erläuterungen
      #!/bin/bash
      TEST[0]="Hund                Wuff"
      TEST[1]="Katze  und     Maus"
      TEST[2]="               Vogel"
      echo "Das Array TEST besitzt ${#TEST[*]} Elemente"
      
      for((INDEX=0;INDEX<${#TEST[*]};INDEX++))   
      do
       echo "${TEST[$INDEX]}"
      done
      bash-4.1$ ./test3
      Das Arry TEST besitzt 3 Elemente
      Hund                Wuff
      Katze   und     Maus
                      Vogel
      bash-4.1$

      Die Anzahl der Arrayelemente wird mit ${#TEST[*]} bestimmt, die echo-Ausgabe in Zeile 3 dient nur der Kontrolle. Die Schleife wird, beginnend mit dem Index 0 aufsteigend so lange durchlaufen, wie der Wert der Laufvariable INDEX kleiner als die Anzahl der Arrayelemente ist.

      Diese Methode macht jedoch nur Sinn, wenn das Array auch fortlaufend mit Elementen gefüllt ist. Da ein Array auch Lücken haben kann, kann man aus der Anzahl der Arrayelemente nicht mehr den höchsten Index erkennen. an diesem Beispiel hier, beginnt das Array bei Index 0 und endet bei Index 2 mit genau drei Elementen. Deshalb ist also der höchste Index 3-1=2, da die Zählung ja bei Null beginnt.
      Besitzt das Array jedoch Lücken, dann könnten die z.B. nur die Elemente mit den Indizes 1, 70 und 512 existieren. Damit würde die Schleife jedoch wieder nur die Elemente 0, 1 und 2 ausgeben, denn aus der Anzahl der Elemente kann man keine Rückschlüsse auf die Indizes ziehen. Sinnvoller ist hier die Expansion der Indizes durch ${!NAME[@]}. Damit werden die Indizes des Arrays als Parameterliste übergeben. Wenn man
      assoziative Arrays verwendet, dann muss diese Expansion auf jeden Fall gequotet werden, denn die Indizes können dann auch Leerzeichen Enthalten!

      Script Ausgabe im Terminal Erläuterungen
      #!/bin/bash
      TEST[1]="Hund                Wuff"
      TEST[70]="Katze und     Maus"
      TEST[512]="             Vogel"
      echo "Das Array TEST besitzt ${#TEST[*]} Elemente"
      echo "Das Array besitzt die Indizes ${!TEST[@]}"
      for INDEX in "${!TEST[@]}"              
      do
       echo "${TEST[$INDEX]}"
      done
      bash-4.1$ ./test3
      Das Array TEST besitzt 3 Elemente
      Das Array besitzt die Indizes 1 70 512
      Hund                Wuff
      Katze   und     Maus
                      Vogel
      bash-4.1$

      Eine arithmetische for-Schleife macht hier natürlich keinen Sinn. Durch die Expansion von "${!TEST[@]}" wird der for-Schleife eine gequotete Liste der Indizes des Arrays übergeben, wie man an der echo-Ausgabe der Scriptzeile 5 erkennen kann. Dort tauchen die Zahlen 1, 70 und 512 auf, die die Adressen der drei Elemente des Arrays sind."

      Wenn der Index selbst nicht interessiert, dann kann man sich die Werte aller Arrayelemente auch direkt mit ${NAME[@]} expandieren lassen. Um Leerzeichen zu erhalten und die einzelnen Werte als Liste von Parametern zu übergeben, ist es wieder erforderlich, die Quotung "${NAME[@]}" zu benutzen. Dadurch enthält die Schleifenvariable dann sofort die Werte der Arrayelemente nacheinander.

      Script Ausgabe im Terminal
      #!/bin/bash
      TEST[1]="Hund                Wuff"
      TEST[70]="Katze und     Maus"
      TEST[512]="             Vogel"
      
      for VALUE in "${TEST[@]}"
      do
       echo "${VALUE}"
      done
      bash-4.1$ ./test3
      Hund                Wuff
      Katze   und     Maus
                      Vogel
      bash-4.1$

      An diesem Beispiel kann man auch sehr schön erkennen, wie wichtig die Quotung in der Zeile
      for VALUE in "${TEST[@]}"
      ist, durch sie wird durch die bash-Substitution die zeile zu:

      for VALUE in "Hund                Wuff" "Katze und     Maus" "             Vogel"

      substituiert, sodass der Schleifenvariable VALUE nacheinander die drei Elemente des Arrays als separate Parameter zugeführt werden. Entfernt man die Quotungszeichen, dann werden die Arrayelemente genauso substituiert, aber nicht gequotet und damit sieht die Substitution dann so aus:
      for VALUE in Hund                Wuff Katze und     Maus              Vogel

      damit erkennt das Wordsplitting der bash sechs Einzelparameter, die durch mehr oder weniger Leerzeichen und Tabulatoren getrennt wurden. Die Zuordnung, welche Wörter zusammen einen Arrayelement angehörten geht so vollkommen verloren und das Script gibt dann sechs Einzelworte aus.

      Script Ausgabe im Terminal
      #!/bin/bash
      TEST[1]="Hund                Wuff"
      TEST[70]="Katze und     Maus"
      TEST[512]="             Vogel"
      
      for VALUE in ${TEST[@]}  
      do
       echo "${VALUE}"
      done
      bash-4.1$ ./test
      Hund
      Wuff
      Katze
      und
      Maus
      Vogel
      bash-4.1$

      Und auch der Unterschied zwischen den Expansionen ${NAME[@]} und ${NAME[*]} wird nun klar. Tauscht man in den letzten beiden Scriptbeispielen die Expansionen aus, dann ergeben sich folgende Ausgaben des Scriptes:

      Script Ausgabe im Terminal Erläuterungen
      #!/bin/bash
      TEST[1]="Hund                Wuff"
      TEST[70]="Katze und     Maus"
      TEST[512]="             Vogel"
      
      for VALUE in "${TEST[*]}"
      do
       echo "${VALUE}"
      done
      bash-4.1$ ./test
      Hund                Wuff Katze  und     Maus            Vogel
      bash-4.1$

      Die Expansion "${TEST[*]}" gibt die Inhalte aller Arrayelemente durch Leerzeichen getrennt als String zurück. Durch die Quotungszeichen "..." wird dieser String gequotet, sodass das Wordsplitting der bash nur einen einzigen Parameter erkennt, der der Schleife übergeben wird.

      Script Ausgabe im Terminal Erläuterungen
      #!/bin/bash
      TEST[1]="Hund                Wuff"
      TEST[70]="Katze und     Maus"
      TEST[512]="             Vogel"
      
      for VALUE in ${TEST[*]}  
      do
       echo "${VALUE}"
      done
      bash-4.1$ ./test
      Hund
      Wuff
      Katze
      und
      Maus
      Vogel
      bash-4.1$

      Hier wird der durch die Expansion erzeugte String nicht gequotet, sodass das Wordsplitting wieder sechs Einzelparameter an die Schleife übergibt.

  5. Arrays, Arrayelemente und Variablen entfernen

    Das Gegenstück zum Kommando declare ist das Kommando unset. Mit diesem kommando können Variablen, Arrayelemente und ganze Arrays wieder vernichtet werden. Nur schreibgeschützte Variablen und einige wichtige Umgebungsvariablen der bash können damit nicht entfernt werden. Die syntax ist recht einfach. Um eine Variable oder ein komplettes Array mit dem Namen NAME zu entfernen, verwendet man
    unset NAME
    um ein einzelnes Arrayelement zu entfernen, verwendet man
    unset NAME[INDEX]
    um alle Arrayelemente zu entfernen, kann man
    unset NAME[@]
    unset NAME[*] oder
    NAME=()
    verwenden.

    Script Ausgabe im Terminal
    #!/bin/bash
    VAR="hallo"
    FELD=("Alle" "hier" "her")
    
    echo "Bearbeiten der Variable VAR"
    declare -p VAR
    VAR=
    declare -p VAR
    unset VAR
    declare -p VAR
    
    echo "Bearbeiten des Arrays FELD"
    declare -p FELD
    FELD[1]=
    declare -p FELD
    unset FELD[0]  
    declare -p FELD
    unset FELD
    declare -p FELD
    
    
    bash-4.1$ ./test
    Bearbeiten der Variable VAR
    declare -- VAR="hallo"
    declare -- VAR=""
    ./test8: Zeile 10: declare: VAR: Nicht gefunden.
    Bearbeiten des Arrays FELD
    declare -a FELD='([0]="Alle" [1]="hier" [2]="her")'
    declare -a FELD='([0]="Alle" [1]="" [2]="her")'
    declare -a FELD='([1]="" [2]="her")'
    ./test8: Zeile 19: declare: FELD: Nicht gefunden.
    bash-4.1$

    In diesem Beispiel ist der Unterschied zwischen dem Speichern eines Leerstrings (z.B. mit VAR= ) oder dem vernichten der Variable mit unset zu sehen. Erst nach einem entsprechenden unset meldet declare auch, dass die Variable nicht mehr existiert.

  6. Arrays verbinden

    Für integerindizierte, also nicht assoziative Arrays gibt es eine Reihe von Möglichkeiten, Arrays miteinander zu verknüpfen.
    1.Beispiel: Die Werte eines Arrays TEST sollen in ein neues Array FELD übertragen werden. Eventuelle Lücken im Array TEST sollen dabei entfernt werden und das neue Array FELD fortlaufend ab Index 0 indiziert sein.
    Code: FELD=( "${TEST[@]}" )

    Script Ausgabe im Terminal
    #!/bin/bash
    declare -a TEST
    TEST[0]="Baum"
    TEST[1]="Blume"
    TEST[7]="Auto"
    TEST[15]=700
    TEST[33]="A und B"
    
    declare -p TEST
    FELD=( "${TEST[@]}" )
    declare -p FELD
    
    bash-4.1$ ./test
    declare -a TEST='([0]="Baum" [1]="Blume" [7]="Auto" [15]="700" [33]="A und B")'
    declare -a FELD='([0]="Baum" [1]="Blume" [2]="Auto" [3]="700" [4]="A und B")'
    bash-4.1$

    Im Prinzip wird nur ein neues Array FELD deklariert, in das die gequotetet Werteliste der Arrayelemnte des Arrays TEST expandiert wird. Mit dieser Methode kann sich das Feld TEST natürlich auch selbst "zusammenschieben".

    Script Ausgabe im Terminal
    #!/bin/bash
    declare -a TEST
    TEST[0]="Baum" 
    TEST[1]="Blume"
    TEST[7]="Auto"
    TEST[15]=700
    TEST[33]="A und B"
    
    declare -p TEST
    TEST=( "${TEST[@]}" )
    declare -p TEST
    
    bash-4.1$ ./test
    declare -a TEST='([0]="Baum" [1]="Blume" [7]="Auto" [15]="700" [33]="A und B")'
    declare -a TEST='([0]="Baum" [1]="Blume" [2]="Auto" [3]="700" [4]="A und B")'
    bash-4.1$

    Und man kann auch mehrere Arrays zusammenziehen. Ich denke, das Prinzip ist klar.