Mehrdimensionale Arrays in Bash

Ich plane ein Skript, um einige meiner Linux-Systeme zu verwalten, und bin dabei, zu entscheiden, ob ich bash oder python verwenden möchte.

Ich würde es vorziehen, dies als Bash-Skript zu machen, einfach weil die Befehle einfacher sind, aber der eigentliche entscheidende Faktor ist die Konfiguration. Ich muss in der Lage sein, ein mehrdimensionales Array in der Konfigurationsdatei zu speichern, um dem Skript mitzuteilen, was mit ihm geschehen soll. Das Speichern einfacher key = value-Paare in Konfigurationsdateien ist mit bash einfach, aber die einzige Möglichkeit, ein mehrdimensionales Array zu erstellen, ist eine zweischichtige Parsing-Engine, etwas ähnliches

array=&d1|v1;v2;v3&d2|v1;v2;v3 

aber der marshall / unmarshall-code könnte ein bär werden und ist bei weitem nicht benutzerfreundlich für den nächsten armen sap, der das verwalten muss. Wenn ich dies nicht einfach in bash machen kann, schreibe ich einfach die configs in eine XML-Datei und schreibe das Skript in python.

Gibt es eine einfache Möglichkeit, dies in bash zu tun?

Danke an alle.

   

Bash unterstützt keine mehrdimensionalen Arrays oder Hashes und es scheint, dass Sie einen Hash wünschen, dessen Werte Arrays sind. Diese Lösung ist nicht sehr schön, eine Lösung mit einer XML-Datei sollte besser sein:

 array=('d1=(v1 v2 v3)' 'd2=(v1 v2 v3)') for elt in "${array[@]}";do eval $elt;done echo "d1 ${#d1[@]} ${d1[@]}" echo "d2 ${#d2[@]} ${d2[@]}" 

Das hat bei mir funktioniert.

 # Define each array and then add it to the main one SUB_0=("name0" "value0") SUB_1=("name1" "value1") MAIN_ARRAY=( SUB_0[@] SUB_1[@] ) # Loop and print it. Using offset and length to extract values COUNT=${#MAIN_ARRAY[@]} for ((i=0; i< $COUNT; i++)) do NAME=${!MAIN_ARRAY[i]:0:1} VALUE=${!MAIN_ARRAY[i]:1:1} echo "NAME ${NAME}" echo "VALUE ${VALUE}" done 

Es basiert auf dieser Antwort hier

Unabhängig von der verwendeten Shell (sh, ksh, bash, …) funktioniert der folgende Ansatz ziemlich gut für n-dimensionale Arrays (das Beispiel deckt ein zweidimensionales Array ab).

Im Beispiel ist der Zeilentrenner (1. Dimension) das Leerzeichen. Zur Einführung eines Feldtrenners (2. Dimension) wird das Standard-Unix-Werkzeug tr verwendet. Zusätzliche Separatoren für zusätzliche Dimensionen können auf die gleiche Weise verwendet werden.

Natürlich ist die performance dieses Ansatzes nicht sehr gut, aber wenn die performance kein Kriterium ist, ist dieser Ansatz ziemlich generisch und kann viele Probleme lösen:

 array2d="1.1:1.2:1.3 2.1:2.2 3.1:3.2:3.3:3.4" function process2ndDimension { for dimension2 in $* do echo -n $dimension2 " " done echo } function process1stDimension { for dimension1 in $array2d do process2ndDimension `echo $dimension1 | tr : " "` done } process1stDimension 

Die Ausgabe dieses Beispiels sieht folgendermaßen aus:

 1.1 1.2 1.3 2.1 2.2 3.1 3.2 3.3 3.4 

Bash hat kein mehrdimensionales Array. Aber Sie können einen ähnlichen Effekt mit assoziativen Arrays simulieren. Das folgende Beispiel zeigt ein assoziatives Array, das vorgibt, als mehrdimensionales Array verwendet zu werden:

 declare -A arr arr[0,0]=0 arr[0,1]=1 arr[1,0]=2 arr[1,1]=3 echo "${arr[0,0]} ${arr[0,1]}" # will print 0 1 

Wenn Sie das Array nicht als assoziativ deklarieren (mit -A ), funktioniert das Obige nicht. Wenn Sie beispielsweise die declare -A arr Zeile weglassen, wird das echo 2 3 anstelle von 0 1 0,0 , da 0,0 , 1,0 und so als arithmetischer Ausdruck genommen und auf 0 (der Wert rechts) ausgewertet werden des Komma-Operators).

Nach vielen Versuchen und Fehlern finde ich das beste, klarste und einfachste multidimensionale Array auf bash, um eine reguläre Variable zu verwenden. Ja.

Vorteile: Sie müssen kein großes Array durchlaufen, Sie können einfach “$ var” ausgeben und grep / awk / sed verwenden. Es ist einfach und klar und Sie können so viele Spalten haben, wie Sie möchten.

Beispiel:

 $ var=$(echo -e 'kris hansen oslo\nthomas jonson peru\nbibi abu johnsonville\njohnny lipp peru') $ echo "$var" kris hansen oslo thomas johnson peru bibi abu johnsonville johnny lipp peru 

Wenn Sie alle in Peru finden wollen

 $ echo "$var" | grep peru thomas johnson peru johnny lipp peru 

Nur grep (sed) im dritten Feld

 $ echo "$var" | sed -n -E '/(.+) (.+) peru/p' thomas johnson peru johnny lipp peru 

Wenn du nur x Feld willst

 $ echo "$var" | awk '{print $2}' hansen johnson abu johnny 

Jeder in Peru, der heißt Thomas und nur seinen Nachnamen zurückgeben

 $ echo "$var" |grep peru|grep thomas|awk '{print $2}' johnson 

Jede Frage, die Sie sich vorstellen können … Superschwierig.

Um einen Artikel zu ändern:

 $ var=$(echo "$var"|sed "s/thomas/pete/") 

So löschen Sie eine Zeile, die “x” enthält

 $ var=$(echo "$var"|sed "/thomas/d") 

Um ein anderes Feld in derselben Zeile basierend auf einem Wert von einem anderen Element zu ändern

 $ var=$(echo "$var"|sed -E "s/(thomas) (.+) (.+)/\1 test \3/") $ echo "$var" kris hansen oslo thomas test peru bibi abu johnsonville johnny lipp peru 

Natürlich funktioniert das Looping auch, wenn Sie das wollen

 $ for i in "$var"; do echo "$i"; done kris hansen oslo thomas jonson peru bibi abu johnsonville johnny lipp peru 

Das einzige, was man dabei herausfindet, ist, dass man immer die Variable (im Beispiel; sowohl var als auch i) zitieren muss oder die Dinge so aussehen werden

 $ for i in "$var"; do echo $i; done kris hansen oslo thomas jonson peru bibi abu johnsonville johnny lipp peru 

und jemand wird zweifellos sagen, dass es nicht funktioniert, wenn Sie Leerzeichen in Ihrer Eingabe haben, die jedoch durch Verwendung eines anderen Delimeters in Ihrer Eingabe behoben werden kann (zB mit einem utf8 Zeichen, um zu betonen, dass Sie etwas auswählen können, was Ihre Eingabe nicht tut) enthalten, aber Sie können wählen, was auch immer)

 $ var=$(echo -e 'field one☥field two hello☥field three yes moin\nfield 1☥field 2☥field 3 dsdds aq') $ for i in "$var"; do echo "$i"; done field one☥field two hello☥field three yes moin field 1☥field 2☥field 3 dsdds aq $ echo "$var" | awk -F '☥' '{print $3}' field three yes moin field 3 dsdds aq $ var=$(echo "$var"|sed -E "s/(field one)☥(.+)☥(.+)/\1☥test☥\3/") $ echo "$var" field one☥test☥field three yes moin field 1☥field 2☥field 3 dsdds aq 

Wenn Sie in Ihrer Eingabe Zeilenumbrüche speichern möchten, können Sie den Zeilenvorschub vor der Eingabe in einen anderen Wert konvertieren und bei der Ausgabe wieder zurückkonvertieren (oder verwenden Sie keine bash …). Genießen!

Erweiterung der Antwort von Paul – hier ist meine Version des Arbeitens mit assoziativen Sub-Arrays in bash:

 declare -A SUB_1=(["name1key"]="name1val" ["name2key"]="name2val") declare -A SUB_2=(["name3key"]="name3val" ["name4key"]="name4val") STRING_1="string1val" STRING_2="string2val" MAIN_ARRAY=( "${SUB_1[*]}" "${SUB_2[*]}" "${STRING_1}" "${STRING_2}" ) echo "COUNT: " ${#MAIN_ARRAY[@]} for key in ${!MAIN_ARRAY[@]}; do IFS=' ' read -a val < << ${MAIN_ARRAY[$key]} echo "VALUE: " ${val[@]} if [[ ${#val[@]} -gt 1 ]]; then for subkey in ${!val[@]}; do subval=${val[$subkey]} echo "SUBVALUE: " ${subval} done fi done 

Es arbeitet mit gemischten Werten im Hauptarray - strings / arrays / assoc. Arrays

Der Schlüssel hier ist, die Subarrays in einfache Anführungszeichen zu setzen und * anstelle von @ wenn ein Subarray innerhalb des Hauptarrays gespeichert wird, damit es als einzelne, durch Leerzeichen getrennte Zeichenfolge gespeichert wird: "${SUB_1[*]}"

Dann macht es es einfach, ein Array daraus zu analysieren, wenn man Werte durchfährt mit IFS=' ' read -a val < << ${MAIN_ARRAY[$key]}

Der Code über den Ausgaben:

 COUNT: 4 VALUE: name1val name2val SUBVALUE: name1val SUBVALUE: name2val VALUE: name4val name3val SUBVALUE: name4val SUBVALUE: name3val VALUE: string1val VALUE: string2val 

Ich poste das Folgende, weil es ein sehr einfacher und klarer Weg ist, das Verhalten eines zweidimensionalen Arrays in Bash (zumindest bis zu einem gewissen Grad) nachzuahmen. Es verwendet eine Here-Datei (siehe das Bash-Handbuch) und read (einen Bash-Befehl):

 ## Store the "two-dimensional data" in a file ($$ is just the process ID of the shell, to make sure the filename is unique) cat > physicists.$$ <  

Ausgabe: Physicist Wolfgang Pauli was born in 1900 Physicist Werner Heisenberg was born in 1901 Physicist Albert Einstein was born in 1879 Physicist Niels Bohr was born in 1885

So funktioniert es:

  • Die Zeilen in der erstellten temporären Datei spielen die Rolle von eindimensionalen Vektoren, wobei die leeren Bereiche (oder das von Ihnen gewählte Trennzeichen; siehe die Beschreibung des read im Bash-Handbuch) die Elemente dieser Vektoren trennen.
  • Mit dem Befehl read mit der Option -a durchlaufen wir jede Zeile der Datei (bis zum Ende der Datei). Für jede Zeile können wir einem Array die gewünschten Felder (= Wörter) zuweisen, die wir kurz vor der Schleife deklariert haben. Die Option -r für den read verhindert, dass Backslashes als Escape-Zeichen fungieren, falls wir Backslashes in den hier dokumentierten physicists.$$ eingegeben haben.

Als Schlussfolgerung wird eine Datei als 2D-Array erstellt, und ihre Elemente werden mithilfe einer Schleife über jede Zeile extrahiert, wobei die Fähigkeit des read , um den Elementen eines (indizierten) Arrays Wörter zuzuordnen.

Leichte Verbesserung:

Im obigen Code werden die physicists.$$ als Eingabe für die while Schleife angegeben, so dass sie tatsächlich an den read . Ich habe jedoch festgestellt, dass dies Probleme verursacht, wenn ich einen anderen Befehl habe, der nach einer Eingabe in der while Schleife fragt. Zum Beispiel wartet der select Befehl auf die Standardeingabe, und wenn er in die while Schleife gesetzt wird, nimmt er die Eingabe von physicists.$$ , statt in der Kommandozeile zur Benutzereingabe aufzufordern. Um dies zu korrigieren, verwende ich die Option -u von read , die es erlaubt, von einem Dateideskriptor zu lesen. Wir müssen nur einen Dateideskriptor (mit dem Befehl exec ) erstellen, der den physicists.$$ und ihn der Option -u des Lesens geben, wie im folgenden Code:

 ## Store the "two-dimensional data" in a file ($$ is just the process ID of the shell, to make sure the filename is unique) cat > physicists.$$ <  

Beachten Sie, dass der Dateideskriptor am Ende geschlossen ist.

Ich benutze dafür assoziative Arrays seit bash 4 und setze IFS auf einen Wert, der manuell definiert werden kann.

Der Zweck dieses Ansatzes besteht darin, Arrays als Werte von assoziativen Array-Schlüsseln zu haben.

Um IFS auf den Standardwert zurückzusetzen, heben Sie es einfach auf.

  • unset IFS

Dies ist ein Beispiel:

 #!/bin/bash set -euo pipefail # used as value in asscciative array test=( "x3:x4:x5" ) # associative array declare -A wow=( ["1"]=$test ["2"]=$test ) echo "default IFS" for w in ${wow[@]}; do echo " $w" done IFS=: echo "IFS=:" for w in ${wow[@]}; do for t in $w; do echo " $t" done done echo -e "\n or\n" for w in ${!wow[@]} do echo " $w" for t in ${wow[$w]} do echo " $t" done done unset IFS unset w unset t unset wow unset test 

Die Ausgabe des folgenden Skripts lautet:

 default IFS x3:x4:x5 x3:x4:x5 IFS=: x3 x4 x5 x3 x4 x5 or 1 x3 x4 x5 2 x3 x4 x5 

Ich habe eine ziemlich einfache, aber kluge Workaround: Definieren Sie einfach das Array mit Variablen in seinem Namen. Beispielsweise:

 for (( i=0 ; i< $(($maxvalue + 1)) ; i++ )) do for (( j=0 ; j<$(($maxargument + 1)) ; j++ )) do declare -a array$i[$j]=((Your rule)) done done 

Ich weiß nicht, ob das hilft, weil es nicht genau das ist, wonach du gefragt hast, aber es funktioniert für mich. (Das gleiche könnte nur mit Variablen ohne das Array erreicht werden)

Bash unterstützt kein multidimensionales Array, aber wir können das Array Associate verwenden. Hier sind die Indizes der Schlüssel zum Abrufen des Werts. Associate-Array ist in der bash Version 4 verfügbar.

 #!/bin/bash declare -A arr2d rows=3 columns=2 for ((i=0;i 
 echo "Enter no of terms" read count for i in $(seq 1 $count) do t=` expr $i - 1 ` for j in $(seq $t -1 0) do echo -n " " done j=` expr $count + 1 ` x=` expr $j - $i ` for k in $(seq 1 $x) do echo -n "* " done echo "" done