So deklarieren Sie ein 2D-Array in Bash

74

Ich frage mich, wie man ein 2D-Array in Bash deklariert und dann auf 0 initialisiert.

In C sieht es so aus:

int a[4][5] = {0};

Und wie ordne ich einem Element einen Wert zu? Wie in C:

a[2][3] = 3;
Anis_Stack
quelle
4
Übrigens ist ein mehrdimensionales Array tatsächlich (tief im Inneren) ein eindimensionales Array, das etwas anders gehandhabt wird, insbesondere wenn es um den Zugriff auf seine Elemente geht. Zum Beispiel hat eine 3x4-Matrix 12 Zellen. Die "Zeilen", die Sie mit einer äußeren Schleife mit einem Schritt von 3 durchlaufen, und die "Spalten", die Sie mit einer inneren Schleife mit einem Schritt von 1
durchlaufen.

Antworten:

70

Sie können sie beispielsweise mit Hashes simulieren, müssen sich jedoch um die führenden Nullen und viele andere Dinge kümmern. Die nächste Demonstration funktioniert, ist aber alles andere als eine optimale Lösung.

#!/bin/bash
declare -A matrix
num_rows=4
num_columns=5

for ((i=1;i<=num_rows;i++)) do
    for ((j=1;j<=num_columns;j++)) do
        matrix[$i,$j]=$RANDOM
    done
done

f1="%$((${#num_rows}+1))s"
f2=" %9s"

printf "$f1" ''
for ((i=1;i<=num_rows;i++)) do
    printf "$f2" $i
done
echo

for ((j=1;j<=num_columns;j++)) do
    printf "$f1" $j
    for ((i=1;i<=num_rows;i++)) do
        printf "$f2" ${matrix[$i,$j]}
    done
    echo
done

Im obigen Beispiel wird eine 4x5-Matrix mit Zufallszahlen erstellt und mit dem Beispielergebnis transponiert gedruckt

           1         2         3         4
 1     18006     31193     16110     23297
 2     26229     19869      1140     19837
 3      8192      2181     25512      2318
 4      3269     25516     18701      7977
 5     31775     17358      4468     30345

Das Prinzip lautet: Erstellen eines assoziativen Arrays, bei dem der Index einer Zeichenfolge ähnelt 3,4. Die Vorteile:

  • Es ist möglich, für Arrays beliebiger Dimensionen zu verwenden;) wie: 30,40,2für 3-dimensionale.
  • Die Syntax ist ähnlich wie bei Arrays "C" ${matrix[2,3]}
jm666
quelle
2
Der offensichtliche Nachteil dieses Verfahrens besteht darin, dass die Länge einer Dimension nicht bekannt ist. Trotzdem funktioniert es in den meisten anderen Szenarien hervorragend! Vielen Dank!!
Bhavin Doshi
Können Sie bitte erklären, was f1und f2was?
CL22
1
@Jodes the f1und f2enthält das formatfür das printffür den schön ausgerichteten Drucken. Es könnte zum Beispiel fest codiert sein, printf "%2s"aber die Verwendung von Variablen ist flexibler - wie oben f1. Der widthvon der Zeilennummer wird als die Länge der berechneten $num_rowsVariable - zB wenn die Anzahl der Zeilen $num_rows9 ist, seine Länge ist 1das Format wird 1+1so %2s. Für den $num_rows2500 ist seine Länge 4so, dass das Format sein wird %5s- und so weiter ...
jm666
24

Bash unterstützt keine mehrdimensionalen Arrays.

Sie können es jedoch mithilfe der indirekten Erweiterung simulieren:

#!/bin/bash
declare -a a0=(1 2 3 4)
declare -a a1=(5 6 7 8)
var="a1[1]"
echo ${!var}  # outputs 6

Zuweisungen sind auch mit dieser Methode möglich:

let $var=55
echo ${a1[1]}  # outputs 55

Bearbeiten 1 : Verwenden Sie Folgendes, um ein solches Array aus einer Datei mit jeder Zeile in einer Zeile und durch Leerzeichen getrennten Werten zu lesen:

idx=0
while read -a a$idx; do
    let idx++;
done </tmp/some_file

Bearbeiten 2 : Zum Deklarieren und Initialisieren a0..a3[0..4]von 0können Sie Folgendes ausführen:

for i in {0..3}; do
    eval "declare -a a$i=( $(for j in {0..4}; do echo 0; done) )"
done
Sir Athos
quelle
Können Sie bitte zeigen, wie die obige "2d-Array-Simulation" aus einer Dateitabelle gefüllt wird? zB eine Datei mit einer zufälligen Anzahl von Zeilen und in jeder Zeile mit 5 durch Leerzeichen getrennten Zahlen.
Kobame
@kobame: Ich habe die Antwort bearbeitet, um eine Lösung für Ihre Fragen bereitzustellen. Es wird ein 2d-Array mit einer variablen Anzahl von Zeilen und einer variablen Anzahl von Spalten in a0, a1 usw. gelesen.
Sir Athos
Wie würden Sie ein anderes Trennzeichen wie ein Komma oder einen Tabulator verwenden?
MountainX
23

Bash hat kein mehrdimensionales Array. Mit assoziativen Arrays können Sie jedoch einen ähnlichen Effekt simulieren. Das Folgende ist ein Beispiel für 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 (mit -A) deklarieren , funktioniert das oben Gesagte nicht. Wenn Sie beispielsweise die declare -A arrZeile weglassen , echowird 2 3anstelle von 0 1, weil gedruckt 0,0, 1,0und dies wird als arithmetischer Ausdruck verwendet und als 0(der Wert rechts vom Kommaoperator) ausgewertet .

Jahid
quelle
5

Sie können dies auch viel weniger intelligent angehen

q=()
q+=( 1-2 )
q+=( a-b )

for set in ${q[@]};
do
echo ${set%%-*}
echo ${set##*-}
done

Natürlich ist eine 22-Zeilen-Lösung oder Indirektion wahrscheinlich der bessere Weg, und warum nicht überall eval streuen.

James
quelle
Wo verwendet die 22-Zeilen-Lösung die Indirektion? Was werden Sie für Ihre Lösung tun, wenn Sie ein Skript schreiben, für das E / A erforderlich ist und ein Benutzer ein -in das 'Array' eingeben möchte ? Auch wenn Sie ein Array simulieren möchten, ist dies wahrscheinlich sinnvoller als echo ${set//-/ }Ihre beiden.
Setzen Sie Monica bitte
Das war mein Fehler, den ich verpasst habe. Ich denke, dass $ {set // - /} wahrscheinlich ein besserer Weg ist (ich weiß nichts über die Portabilitätsprobleme von %% und ##, obwohl ich Ihnen glaube). Was ist, wenn es eine sehr gefährliche Frage ist? Wenn Sie sie zu oft stellen, werden Sie feststellen, dass Sie KI für Ihren Optionsparser benötigen: {p
James

1
Ich verstehe nicht, wie das relevant ist oder hilft, die Frage zu beantworten. $ {set // - /} würde das '-' entfernen und die Werte zusammenführen. Das heißt, das Echo führt zu 'ab', während der ursprüngliche Code entweder 'a' oder 'b' zurückgibt, je nachdem, welche Seite des '-' Sie möchten.
MrPotatoHead
5

Ein anderer Ansatz besteht darin, dass Sie jede Zeile als Zeichenfolge darstellen können, dh das 2D-Array einem 1D-Array zuordnen. Dann müssen Sie nur noch die Zeichenfolge der Zeile entpacken und neu packen, wenn Sie eine Bearbeitung vornehmen:

# Init a 4x5 matrix
a=("00 01 02 03 04" "10 11 12 13 14" "20 21 22 23 24" "30 31 32 33 34")

aset() {
  row=$1
  col=$2
  value=$3
  IFS=' ' read -r -a tmp <<< "${a[$row]}"
  tmp[$col]=$value
  a[$row]="${tmp[@]}"
}

# Set a[2][3] = 9999
aset 2 3 9999

# Show result
for r in "${a[@]}"; do
  echo $r
done

Ausgänge:

00 01 02 03 04
10 11 12 13 14
20 21 22 9999 24
30 31 32 33 34
Stephen Quan
quelle
4

Eine Möglichkeit, Arrays in Bash zu simulieren (kann für eine beliebige Anzahl von Dimensionen eines Arrays angepasst werden):

#!/bin/bash

## The following functions implement vectors (arrays) operations in bash:
## Definition of a vector <v>:
##      v_0 - variable that stores the number of elements of the vector
##      v_1..v_n, where n=v_0 - variables that store the values of the vector elements

VectorAddElementNext () {
# Vector Add Element Next
# Adds the string contained in variable $2 in the next element position (vector length + 1) in vector $1

    local elem_value
    local vector_length
    local elem_name

    eval elem_value=\"\$$2\"
    eval vector_length=\$$1\_0
    if [ -z "$vector_length" ]; then
        vector_length=$((0))
    fi

    vector_length=$(( vector_length + 1 ))
    elem_name=$1_$vector_length

    eval $elem_name=\"\$elem_value\"
    eval $1_0=$vector_length
}

VectorAddElementDVNext () {
# Vector Add Element Direct Value Next
# Adds the string $2 in the next element position (vector length + 1) in vector $1

    local elem_value
    local vector_length
    local elem_name

    eval elem_value="$2"
    eval vector_length=\$$1\_0
    if [ -z "$vector_length" ]; then
        vector_length=$((0))
    fi

    vector_length=$(( vector_length + 1 ))
    elem_name=$1_$vector_length

    eval $elem_name=\"\$elem_value\"
    eval $1_0=$vector_length
}

VectorAddElement () {
# Vector Add Element
# Adds the string contained in the variable $3 in the position contained in $2 (variable or direct value) in the vector $1

    local elem_value
    local elem_position
    local vector_length
    local elem_name

    eval elem_value=\"\$$3\"
    elem_position=$(($2))
    eval vector_length=\$$1\_0
    if [ -z "$vector_length" ]; then
        vector_length=$((0))
    fi

    if [ $elem_position -ge $vector_length ]; then
        vector_length=$elem_position
    fi

    elem_name=$1_$elem_position

    eval $elem_name=\"\$elem_value\"
    if [ ! $elem_position -eq 0 ]; then
        eval $1_0=$vector_length
    fi
}

VectorAddElementDV () {
# Vector Add Element
# Adds the string $3 in the position $2 (variable or direct value) in the vector $1

    local elem_value
    local elem_position
    local vector_length
    local elem_name

    eval elem_value="$3"
    elem_position=$(($2))
    eval vector_length=\$$1\_0
    if [ -z "$vector_length" ]; then
        vector_length=$((0))
    fi

    if [ $elem_position -ge $vector_length ]; then
        vector_length=$elem_position
    fi

    elem_name=$1_$elem_position

    eval $elem_name=\"\$elem_value\"
    if [ ! $elem_position -eq 0 ]; then
        eval $1_0=$vector_length
    fi
}

VectorPrint () {
# Vector Print
# Prints all the elements names and values of the vector $1 on sepparate lines

    local vector_length

    vector_length=$(($1_0))
    if [ "$vector_length" = "0" ]; then
        echo "Vector \"$1\" is empty!"
    else
        echo "Vector \"$1\":"
        for ((i=1; i<=$vector_length; i++)); do
            eval echo \"[$i]: \\\"\$$1\_$i\\\"\"
            ###OR: eval printf \'\%s\\\n\' \"[\$i]: \\\"\$$1\_$i\\\"\"
        done
    fi
}

VectorDestroy () {
# Vector Destroy
# Empties all the elements values of the vector $1

    local vector_length

    vector_length=$(($1_0))
    if [ ! "$vector_length" = "0" ]; then
        for ((i=1; i<=$vector_length; i++)); do
            unset $1_$i
        done
        unset $1_0
    fi
}

##################
### MAIN START ###
##################

## Setting vector 'params' with all the parameters received by the script:
for ((i=1; i<=$#; i++)); do
    eval param="\${$i}"
    VectorAddElementNext params param
done

# Printing the vector 'params':
VectorPrint params

read temp

## Setting vector 'params2' with the elements of the vector 'params' in reversed order:
if [ -n "$params_0" ]; then
    for ((i=1; i<=$params_0; i++)); do
        count=$((params_0-i+1))
        VectorAddElement params2 count params_$i
    done
fi

# Printing the vector 'params2':
VectorPrint params2

read temp

## Getting the values of 'params2'`s elements and printing them:
if [ -n "$params2_0" ]; then
    echo "Printing the elements of the vector 'params2':"
    for ((i=1; i<=$params2_0; i++)); do
        eval current_elem_value=\"\$params2\_$i\"
        echo "params2_$i=\"$current_elem_value\""
    done
else
    echo "Vector 'params2' is empty!"
fi

read temp

## Creating a two dimensional array ('a'):
for ((i=1; i<=10; i++)); do
    VectorAddElement a 0 i
    for ((j=1; j<=8; j++)); do
        value=$(( 8 * ( i - 1 ) + j ))
        VectorAddElementDV a_$i $j $value
    done
done

## Manually printing the two dimensional array ('a'):
echo "Printing the two-dimensional array 'a':"
if [ -n "$a_0" ]; then
    for ((i=1; i<=$a_0; i++)); do
        eval current_vector_lenght=\$a\_$i\_0
        if [ -n "$current_vector_lenght" ]; then
            for ((j=1; j<=$current_vector_lenght; j++)); do
                eval value=\"\$a\_$i\_$j\"
                printf "$value "
            done
        fi
        printf "\n"
    done
fi

################
### MAIN END ###
################

quelle
2

Wenn jede Zeile der Matrix dieselbe Größe hat, können Sie einfach ein lineares Array und eine Multiplikation verwenden.

Das ist,

a=()
for (( i=0; i<4; ++i )); do
  for (( j=0; j<5; ++j )); do
     a[i*5+j]=0
  done
done

Dann a[2][3] = 3wird

a[2*5+3] = 3

Es könnte sich lohnen, diesen Ansatz in eine Reihe von Funktionen umzuwandeln. Da Sie jedoch keine Arrays an Funktionen übergeben oder von diesen zurückgeben können, müssen Sie manchmal die Namensübergabe verwenden eval. Daher neige ich dazu, mehrdimensionale Arrays unter "Dinge, die Bash einfach nicht zu tun hat" abzulegen.

Mark Reed
quelle
1

Man kann einfach zwei Funktionen definieren, die geschrieben werden sollen ($ 4 ist der zugewiesene Wert) und eine Matrix mit einem beliebigen Namen ($ 1) und Indizes ($ 2 und $ 3) lesen, wobei eval und indirekte Referenzierung ausgenutzt werden.

#!/bin/bash

matrix_write () {
 eval $1"_"$2"_"$3=$4
 # aux=$1"_"$2"_"$3          # Alternative way
 # let $aux=$4               # ---
}

matrix_read () {
 aux=$1"_"$2"_"$3
 echo ${!aux}
}

for ((i=1;i<10;i=i+1)); do
 for ((j=1;j<10;j=j+1)); do 
  matrix_write a $i $j $[$i*10+$j]
 done
done

for ((i=1;i<10;i=i+1)); do
 for ((j=1;j<10;j=j+1)); do 
  echo "a_"$i"_"$j"="$(matrix_read a $i $j)
 done
done
Adolfo Avella
quelle
2
Hallo, fügen Sie zusammen mit dem Code eine Erklärung hinzu, da dies zum Verständnis Ihres Codes beiträgt. Nur Code-Antworten sind verpönt.
Bhargav Rao
1

2D-Array kann in Bash erreicht werden, indem 1D-Array deklariert wird, und dann kann mit auf Elemente zugegriffen werden (r * col_size) + c). Die folgende Logik löscht 1D-Array ( str_2d_arr) und druckt als 2D-Array.

col_size=3
str_2d_arr=()
str_2d_arr+=('abc' '200' 'xyz')
str_2d_arr+=('def' '300' 'ccc')
str_2d_arr+=('aaa' '400' 'ddd')

echo "Print 2D array"
col_count=0
for elem in ${str_2d_arr[@]}; do
    if [ ${col_count} -eq ${col_size} ]; then
        echo ""
        col_count=0
    fi
    echo -e "$elem \c"
    ((col_count++))
done
echo ""

Ausgabe ist

Print 2D array
abc 200 xyz 
def 300 ccc
aaa 400 ddd

Die folgende Logik ist sehr nützlich, um jede Zeile aus dem oben deklarierten 1D-Array abzurufen str_2d_arr.

# Get nth row and update to 2nd arg
get_row_n()
{
    row=$1
    local -n a=$2
    start_idx=$((row * col_size))
    for ((i = 0; i < ${col_size}; i++)); do
        idx=$((start_idx + i))
        a+=(${str_2d_arr[${idx}]})
    done
}

arr=()
get_row_n 0 arr
echo "Row 0"
for e in ${arr[@]}; do
    echo -e "$e \c"
done
echo ""

Ausgabe ist

Row 0
abc 200 xyz
Rashok
quelle
0

Um ein zweidimensionales Array zu simulieren, lade ich zuerst die ersten n-Elemente (die Elemente der ersten Spalte).

local pano_array=()  

i=0

for line in $(grep  "filename" "$file")
do 
  url=$(extract_url_from_xml $line)
  pano_array[i]="$url"
  i=$((i+1))
done

Um die zweite Spalte hinzuzufügen, definiere ich die Größe der ersten Spalte und berechne die Werte in einer Versatzvariablen

array_len="${#pano_array[@]}"

i=0

while [[ $i -lt $array_len ]]
do
  url="${pano_array[$i]}"
  offset=$(($array_len+i)) 
  found_file=$(get_file $url)
  pano_array[$offset]=$found_file

  i=$((i+1))
done
kjell moens
quelle
0

Der folgende Code funktioniert auf jeden Fall, vorausgesetzt, Sie arbeiten auf einem Mac mit Bash-Version 4. Sie können nicht nur 0 deklarieren, sondern dies ist eher ein universeller Ansatz zum dynamischen Akzeptieren von Werten.

2D-Array

declare -A arr
echo "Enter the row"
read r
echo "Enter the column"
read c
i=0
j=0
echo "Enter the elements"
while [ $i -lt $r ]
do
  j=0
  while [ $j -lt $c ]
  do
    echo $i $j
    read m
    arr[${i},${j}]=$m
    j=`expr $j + 1`
  done
  i=`expr $i + 1`
done

i=0
j=0
while [ $i -lt $r ]
do
  j=0
  while [ $j -lt $c ]
  do
    echo -n ${arr[${i},${j}]} " "
    j=`expr $j + 1`
  done
  echo ""
  i=`expr $i + 1`
done
Priyanka Karmakar
quelle
0

Mark Reed schlug eine sehr gute Lösung für 2D-Arrays (Matrix) vor! Sie können immer in ein 1D-Array (Vektor) konvertiert werden. Obwohl Bash keine native Unterstützung für 2D-Arrays bietet, ist es nicht so schwierig, eine einfache ADT nach dem genannten Prinzip zu erstellen.

Hier ist ein Barebone-Beispiel ohne Argumentprüfungen usw., um die Lösung klar zu halten: Die Größe des Arrays wird als zwei erste Elemente in der Instanz festgelegt (Dokumentation für das Bash-Modul, das eine Matrix-ADT implementiert, https://github.com) /vorakl/bash-libs/blob/master/src.docs/content/pages/matrix.rst )

#!/bin/bash

matrix_init() {
    # matrix_init instance x y data ...

    declare -n self=$1                                                          
    declare -i width=$2 height=$3                                                
    shift 3;                                                                    

    self=(${width} ${height} "$@")                                               
}                                                                               

matrix_get() {                                                                  
    # matrix_get instance x y

    declare -n self=$1                                                          
    declare -i x=$2 y=$3                                                        
    declare -i width=${self[0]} height=${self[1]}                                

    echo "${self[2+y*width+x]}"                                                 
}                                                                               

matrix_set() {                                                                  
    # matrix_set instance x y data

    declare -n self=$1                                                          
    declare -i x=$2 y=$3                                                        
    declare data="$4"                                                           
    declare -i width=${self[0]} height=${self[1]}                                

    self[2+y*width+x]="${data}"                                                 
}                                                                               

matrix_destroy() {                                                                     
    # matrix_destroy instance

    declare -n self=$1                                                          
    unset self                                                                  
}

# my_matrix[3][2]=( (one, two, three), ("1 1" "2 2" "3 3") )
matrix_init my_matrix \                                                         
        3 2 \                                                               
        one two three \                                                     
        "1 1" "2 2" "3 3"

# print my_matrix[2][0]
matrix_get my_matrix 2 0

# print my_matrix[1][1]
matrix_get my_matrix 1 1

# my_matrix[1][1]="4 4 4"
matrix_set my_matrix 1 1 "4 4 4"                                                

# print my_matrix[1][1]
matrix_get my_matrix 1 1                                                        

# remove my_matrix
matrix_destroy my_matrix
vorakl
quelle