web-dev-qa-db-fra.com

Comment déclarer un tableau 2D en bash

Je me demande comment déclarer un tableau 2D dans bash, puis initialiser à 0.

En C ça ressemble à ça:

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

Et comment attribuer une valeur à un élément? Comme en C:

a[2][3] = 3;
68
Anis_Stack

Vous pouvez les simuler par exemple avec des hachages, mais vous devez vous soucier des zéros non significatifs et de bien d’autres choses. La prochaine démonstration fonctionne, mais la solution est loin d’être optimale.

#!/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

l'exemple ci-dessus crée une matrice 4x5 avec des nombres aléatoires et l'imprime transposée, avec le résultat de l'exemple

           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

Le principe est le suivant: Création d'un tableau associatif où l'index est une chaîne telle que 3,4. Les avantages:

  • il est possible d'utiliser des tableaux de toutes dimensions;) comme: 30,40,2 pour 3 dimensions.
  • la syntaxe est proche de "C" comme les tableaux ${matrix[2,3]}
60
jm666

Bash ne prend pas en charge les tableaux multidimensionnels.

Vous pouvez cependant le simuler en utilisant une expansion indirecte:

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

Les affectations sont également possibles avec cette méthode:

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

Edit 1: Pour lire un tel tableau dans un fichier, avec chaque ligne sur une ligne, et les valeurs délimitées par un espace, utilisez ceci:

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

Edit 2: Pour déclarer et initialiser a0..a3[0..4] sur 0, vous pouvez exécuter:

for i in {0..3}; do
    eval "declare -a a$i=( $(for j in {0..4}; do echo 0; done) )"
done
23
Sir Athos

Bash n'a pas de tableau multi-dimensionnel. Mais vous pouvez simuler un effet quelque peu similaire avec des tableaux associatifs. Voici un exemple de tableau associatif prétendant être utilisé comme tableau multidimensionnel:

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

Si vous ne déclarez pas le tableau comme étant associatif (avec -A), cela ne fonctionnera pas. Par exemple, si vous omettez la ligne declare -A arr, la echo imprimera 2 3 au lieu de 0 1, car 0,0, 1,0 et ainsi de suite. être considéré comme une expression arithmétique et évalué comme 0 (la valeur à droite de l'opérateur de virgule).

22
Jahid

Vous pouvez également aborder ceci de manière beaucoup moins intelligente

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

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

bien entendu, une solution ou une option de 22 lignes est probablement la meilleure solution et pourquoi ne pas saupoudrer eval partout.

5
Arcabard

Une façon de simuler des tableaux dans bash (il peut être adapté à n’importe quel nombre de dimensions d’un tableau):

#!/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 ###
################
4
user5280669

Une autre approche consiste à représenter chaque ligne sous forme de chaîne, c'est-à-dire à mapper le tableau 2D en un tableau 1D. Ensuite, tout ce que vous avez à faire est de décompresser et de remballer la chaîne de la ligne chaque fois que vous effectuez une modification:

# Init a 4x5 matrix
a=("0 0 0 0 0" "0 0 0 0 0" "0 0 0 0 0" "0 0 0 0 0")

function aset {
    IFS=' ' read -r -a tmp <<< "${a[$1]}"
    tmp[$2]=$3
    a[$1]="${tmp[@]}"
}

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

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

Les sorties:

0 0 0 0 0
0 0 0 0 0
0 0 0 3 0
0 0 0 0 0
4
Stephen Quan

Si chaque ligne de la matrice a la même taille, vous pouvez simplement utiliser un tableau linéaire et une multiplication.

C'est,

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

Alors votre a[2][3] = 3 devient

a[2*5+3] = 3

Cette approche peut valoir la peine d’être transformée en un ensemble de fonctions, mais comme vous ne pouvez pas passer de tableaux à des fonctions ou renvoyer de tableaux à partir de fonctions, vous devez utiliser un mot de passe et parfois eval. J'ai donc tendance à classer des tableaux multidimensionnels sous "tout ce que bash n'est simplement pas censé faire".

2
Mark Reed

On peut simplement définir deux fonctions à écrire ($ 4 est la valeur attribuée) et lire une matrice avec un nom arbitraire ($ 1) et des index ($ 2 et $ 3) exploitant les références eval et indirecte.

#!/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
1
Adolfo Avella

Pour simuler un tableau à 2 dimensions, je commence par charger les n premiers éléments (les éléments de la première colonne)

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

Pour ajouter la deuxième colonne, je définis la taille de la première colonne et calcule les valeurs dans une variable offset

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
0
kjell moens

Mark Reed a suggéré une très bonne solution pour les tableaux 2D (matrice)! Ils peuvent toujours être convertis dans un tableau 1D (vecteur). Bien que Bash n’ait pas de support natif pour les tableaux 2D, il n’était pas si difficile de créer un simple ADT autour du principe mentionné.

Voici un exemple simple, sans contrôle d'arguments, etc., pour que la solution reste claire: la taille du tableau est définie comme deux premiers éléments de l'instance.

#!/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
0
Oleksii Tsvietnov