bash Scripting: Arrays – Tutorial Teil 24

In einem Array lassen sich Massendaten verwalten. Ich habe sie schon öfters erwähnt, so dass es an der Zeit ist, sie mal genauer zu erklären.

Dr. Dirk Colbry, Michigan State
Gnu-bash-logoCreative Commons Attribution-NonCommercial-ShareAlike 4.0 International License . loading=
1Einführung
2Der bash Prompt
3key bindings
4Der Startvorgang
5Environment Variablen
6builtin commands
7Arbeiten mit Dateien
8nano
9bash Scripting
10Variablen
11Verzweigungen (if)
12Schleifen
13Kommandozeilenparameter
14exit Codes bash Tutorial Teil 14
15von der Idee zum Script
16grep
17Piping und Redirection
18tee
19alias
20Der Shebang
21Das sed Kommando
22cd Kommando
23case Verzweigungen
24Arrays

Was ist ein Array?

Ein Array ist eine Datenstruktur, in der mehrere unterschiedliche Werte abgelegt werden können.

Eine gängige Veranschaulichung ist, sich ein Array als einen Schrank mit vielen Schubladen vorzustellen, die jede einen einzelnen Wert aufnehmen können.

Ein stilisierter Schubladenschrank mit fünf Schüben
Ein stilisierter Schubladenschrank mit fünf Schüben

Der Vorteil eines Array liegt vor allem darin, dass du nicht für jeden Wert eine eigene Variable verwenden musst, so dass sich Massendaten besser verarbeiten lassen.

Array definieren

Es gibt mehrere Möglichkeiten, in der bash ein Array zu definieren. Einer ist über declare -a, damit wird der „Schrank“ (um im Bild zu bleiben sozusagen aufgebaut.

#! /usr/bin/bash
# File: declare.sh

declare -a sbc
sbc[0]="Raspberry Pi"
echo "Länge sbc: ${#sbc[@]}"
echo "sbc=${sbc[@]}"
sbc[1]="Orange Pi"
echo "Länge sbc: ${#sbc[@]}"
echo "sbc=${sbc[@]}"
sbc[2]="Banana Pi"
echo "Länge sbc: ${#sbc[@]}"
echo "sbc=${sbc[@]}"

Hier wird das Array sbc erzeugt und die einzelnen „Schubladen“ mit Werten gefüllt.

Ausgabe von declare.sh
Ausgabe von declare.sh

Wie du siehst, fängt der Schrank/Schubladenvergleich jetzt schon an zu hinken, da sich das Array mit jedem neuen Element selbstständig erweitert (mein Schrank macht sowas nicht 😀 Das Verhalten ähnelt damit einer Liste in Python.

Ein weiterer Unterschied zu einem realen Schrank besteht darin, dass nicht der Wert selber im Array abgelegt wird sondern ein Verweis („Pointer“) auf den Speicherbereich, der den Wert enthält.

Du kannst einem Array auch direkt beim Anlegen Werte zuweisen. Dies geschieht ähnlich wie bei anderen Variablen. Du musst dann halt nur mehrere Werte übergeben, die in der angegebenen Reihenfolge im Array verstaut werden.

a1=(0 1 2 3 4)
family=("Marge" "Homer" "Bart" "Lisa" "Maggie")

Du erkennst, die einzelnen Werte werden durch ein Leerzeichen voneinander abgegrenzt.

Zugriff aufs Array

Wir wollen natürlich die Werte im Array wieder auslesen. Dazu musst du zunächst wissen, dass der Arrayindex 0-basiert arbeitet, wie es in der Informatik üblich ist.D.h, der erste Wert im Array ist in der „Schublade“ 0 zu finden.

Zunächst wollen wir beiden Arrays von oben ausgeben.

#! /usr/bin/bash
# File: access.sh

# Array of numbers
fib=(0 1 2 3 5 8)

#Array of Strings
family=("Marge" "Homer" "Bart" "Lisa" "Maggie")

# outputs only element 0
echo "fib=${fib}"
# output the whole array
echo "fib=${fib[@]}"

#output element by index
echo "fib[4]=${fib[4]}"

Wie bei einer einfachen Variable versuchen wir es mit echo "${fib}". Dann wird aber nur das erste Element (mit Index 0) ausgegeben. Um das komplette Array zu zeigen benutzen wir echo "fib=${fib[@]}". Der Arrayindex wird immer innerhalb von eckigen Klammern umgeben. [4] steht für das fünfte Element im Array (weil 0-basiert).Die Notation [@] referenziert immer das gesamte Array.

Ausgabe von access.sh
Ausgabe von access.sh

Das ganze sieht sehr kompliziert aus, aber mit etwas Übung gewöhnt man sich daran. Die Notation mit den eckigen Klammern unterscheidet sich kaum von anderen Programmiersprachen.

Größe eines Arrays

Sehr häufig möchte man wissen, wie viele Elemente im Array stecken. Dies geschieht mit der Notation ${#family[@]}. Hier wird wieder das gesamte Array referenziert und das vorgestellte # soll die Länge des Arrays ermitteln.

#! /usr/bin/bash
# File: length.sh

a1=(0 1 2 3 4 5)
family=("Marge" "Homer" "Bart" "Lisa" "Maggie")

echo "Größe family: ${#family[@]}"
echo "Größe a1: ${#a1[@]}"
Das Array family besitzt 5 Elemente, a1 6.
Das Array family besitzt 5 Elemente, a1 6.

Ein Array durchlaufen

Eine der wichtigsten Anwendungen für die Arraylänge ist, in einer Schleife über jedes Element im Array zu laufen.

Dazu gibt es mehrere Möglichkeiten.

for Schleife

Die for Schleife benutze ich am häufigsten. Es gibt sie in zwei Varianten. Die erste ist relativ einfach anzuwenden.

family=("Marge" "Homer" "Bart" "Lisa" "Maggie")

for name in "${family[@]}"
 do
  echo "for: name=${name}"
 done

Hier wird automatisch jedes Element im Array nacheinander durchlaufen. Das aktuelle Element wird in der Variable ${name} abgelegt.

Die zweite Variante ist mehr der Syntax aus der Programmiersprache C angelehnt. Sie arbeitet mit einem Schleifenzähler i, und prüft bei jedem Durchlauf, ob dieser kleiner als die Länge des Array ist i < len, mit i++ wird der Zähler um eins erhöht. Der Zugriff auf das aktuelle Arrayelement geschieht über den Index ${family[i]}

len=${#family[@]}

for ((i=0;i < len; i++));do
  echo "for-i: name=${family[i]},i= ${i}"
done

Ich benutze diese Variante nur selten, weil es mir zu lästig ist, die Länge des Array zu ermitteln.

while Schleife

Auch die while Schleife arbeitet mit einem Schleifenzähler counter. Dieser muss vor der Schleife explizit deklariert und auf den Startwert gesetzt werden. Im Schleifenkopf wird mit ${counter} -lt ${len} geprüft, ob der Zähler noch im zulässigen Bereich liegt. Das Hochzählen geschieht am Ende des Schleifenblocks über ((counter++))

family=("Marge" "Homer" "Bart" "Lisa" "Maggie")
len=${#family[@]}
counter=0

while [ ${counter} -lt ${len} ]; do
    echo "while: name=${family[counter]},counter=${counter}"
    ((counter++))
done

until Schleife

Die until Schleife sieht der while Schleife sehr ähnlich, hat aber einen wichtigen Unterschied in der Schleifenbedingung. Während while solange läuft, wie der Zähler kleiner als len ist, läuft until so lange bis er größer oder gleich len ist und bricht dann ab. Funktionell machen beide Schleifen das gleiche.

family=("Marge" "Homer" "Bart" "Lisa" "Maggie")
len=${#family[@]}
counter = 0
until [ $counter -ge $len ]; do
    echo "until: ${family[counter]}"
    ((counter++))
done 

Zum Testlauf habe ich alle Schleifenformen in ein Script gepackt.

#! /usr/bin/bash

#Array of Strings
family=("Marge" "Homer" "Bart" "Lisa" "Maggie")

# Einfache for Schleife

for name in "${family[@]}"
 do
  echo "for: name=${name}"
 done
echo "---"
# C-ähnliche Schleife im Index
len=${#family[@]}

for ((i=0;i < len; i++));do
  echo "for-i: name=${family[i]},i= ${i}"
done
echo "---"

# while Schleife
counter=0

while [ ${counter} -lt ${len} ]; do
    echo "while: name=${family[counter]},counter=${counter}"
    ((counter++))
done
echo "---"

# until Schleife
counter=0

until [ $counter -ge $len ]; do
    echo "until: ${family[counter]}"
    ((counter++))
done 

Slicing

Das Slicing ermöglicht es uns, Teile des Arrays herauszuschneiden und einzeln weiter zu verwenden.

#! /usr/bin/bash
# Datei: slicing.sh

family=("Marge" "Homer" "Bart" "Lisa" "Maggie" "Abe" "Herb" "Patty" "Selma")

echo "family=${family[@]}"
parents=${family[@]:0:2}
kids=${family[@]:2:2}

echo "Eltern: ${parents[@]}"
echo "Kinder: ${kids[@]}"

Dieses Script erweitert das family Array noch um ein paar Mitglieder und definiert über Slicing zwei neue Arrays. Zum einen parents und kids. Mit ${family[@]:0:2} weisen wir parents zwei Elemente zu,von Index 0 gezählt 2 Elemente. Bei kids sind es ab Index 2, 2 Elemente. Die Tanten erhalten wir ab Index 7 mit einer Länge von 2.

Allgemein ist die Syntax ${array[@]:start:länge}

assoziative arrays

Ein assoziatives Array entspricht einem Dictionary in Python. In einem assoziativen Array kannst du also bequem Key/Value-Paare ablegen. Du greifst dann nicht mehr über den Index auf einen Wert zu sondern über einen Schlüssel. Dazu formulieren wir das family Array mal um und geben jedem Familienmitglied einen Schlüssel, der seiner Rolle innerhalb der Familie entspricht.

declare -A family
family=([father]="Homer" [mother]="Marge" [son]="Bart" [daughter]="Lisa" [baby]="Maggie")

[father]="Homer" assoziiert den Schlüssel father mit dem Wert „Homer“. Zur Veranschaulichung bemühe ich nochmal die Analogie mit den Schubladen

Die Schubladen im Schrank tragen jetzt keine Nummern mehr sondern die Schlüssel.
Die Schubladen im Schrank tragen jetzt keine Nummern mehr sondern die Schlüssel.

Du kannst jetzt auf ein Arrayelement einfach mit

echo "${family[mother]}"

zugreifen, ohne dich um den Index kümmern zu müssen.

mehrdimensionale Arrays

Wenn du den Schrank um eine weitere Spalte mit Schubladen erweiterst, erhältst du ein Bild, was ein mehrdimensionales Array ist.

stilisierter Schrank mit 2*3 Schubladen
stilisierter Schrank mit 2*3 Schubladen

Damit lassen sich in einem zweidimensionalen Array hervorragend Daten aus einer Datenbanktabelle oder einer Tabellenkalkulation wie Libre Office Calc unterbringen.

Der Wermutstropfen ist allerdings, dass die bash mehrdimensionale Arrays nicht wirklich unterstützt. Während in deren Programmiersprachen die Indexierung meist in dem Syntax wie schrank[1,1] ermöglichen, müssen wir in der bash einen kleinen Trick verwenden:

Wir bauen uns aus den Indizes einen Key zusammen und verstauen unsere Daten in einem assoziativen Array.

Als Beispiel nehme ich mal meine Tabelle über die Stromaufnahme verschiedener Single Board Computer. Für jedes Board erfasse ich zwei Werte, die Aufnahme im Leerlauf und den Wert unter Last. Dadurch ergibt sich eine klassische Tabelle mit drei Spalten und n Zeilen, die bilden wir in einem mehrdimensionalen Array ab. In Spalte 0 steht der Modellname, in Spalte 1 die Stromaufnahme im idle Modus und in Spalte 2 der Volllastwert. Als WordPress Tabelle sieht das so aus:

Orange Pi Zero 2W1,014W1,99W
Banana Pi BPI-F33,6W6,8W
Raspberry Pi 16GB2,25W8,0W
Radxa Zero0,938W1,380W

Jetzt gieße ich das ganze in ein bash Array:

#! /usr/bin/bash
# Datei: power.sh

declare -A power

# 1. Zeile (Orange Pi Zero 2W), x-Index: 0

power[0,0]="Orange Pi Zero 2W"
power[0,1]="1,014"
power[0,2]="1,99"

# 2. Zeile (Banana Pi BPI-F3) x-Index: 1
power[1,0]="Banana Pi BPI-F3"
power[1,1]="3,6"
power[1,2]="6,8"

# 3. Zeile (Raspberry Pi 16GB), x-Index: 2
power[2,0]="Raspberry Pi 16GB"
power[2,1]="2,25"
power[2,2]="8,0"

# 4. Zeile (Radxa Zero), x-Index: 3
power[3,0]="Radxa Zero"
power[3,1]="0,938"
power[3,2]="1,380"
# 2. Spalte: Stromaufnahme Leerlauf

printf "%-25s %-10s %-10s\n" "SBC" "idle" "load"

#printf 'SBC\tload\tidle\n';
for ((x=0; x<4; x++)) 
do 
  printf "%-20s %-10s %-10s\n" "${power[${x},0]}" "${power[${x},1]}W" "${power[${x},2]}W"
done

Der Index wird für ein assoziatives Array mit z.B. [0,2] jeder SBC hat eine Zeile mit drei Werten (Name, idle, Last). Zur Ausgabe benutze ich hier printf(), um eine vernünftige Ausrichtung der Spalten zu erreichen.

Fazit

Mit Arrays kannst du Massendaten bequem ablegen, ohne diskrete Variablen für jeden Wert anlegen zu müssen. Natürlich liegt der Quellcode wieder im git-Repo

Schreibe einen Kommentar

Insert math as
Block
Inline
Additional settings
Formula color
Text color
#333333
Type math using LaTeX
Preview
\({}\)
Nothing to preview
Insert
Creative Commons License
Except where otherwise noted, the content on this site is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
raspithek.de - Olli Graf
WordPress Cookie Hinweis von Real Cookie Banner