web-dev-qa-db-fra.com

Tableaux multidimensionnels dans Bash

Je prévois un script pour gérer certaines parties de mes systèmes Linux et je suis sur le point de décider si je veux utiliser bash ou python .

Je préférerais faire cela en tant que script Bash simplement parce que les commandes sont plus faciles, mais le facteur décisif est la configuration. Je dois pouvoir stocker un tableau multidimensionnel dans le fichier de configuration pour indiquer au script ce qu'il doit faire de lui-même. Stocker des paires clé/valeur simples dans des fichiers de configuration est assez facile avec bash, mais la seule façon de concevoir un tableau multidimensionnel est un moteur d'analyse à deux couches, similaire à

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

mais le code marshall/unmarshall pourrait devenir un ours et il est loin d’être convivial pour le prochain SAP pauvre qui doit l’administrer. Si je ne peux pas le faire facilement en bash, je vais simplement écrire les configs dans un fichier xml et écrire le script en python. 

Y a-t-il un moyen facile de faire cela en bash?

merci tout le monde.

39
scphantm

Bash ne prend pas en charge les tableaux multidimensionnels, ni les hachages, et il semble que vous souhaitiez un hachage dont les valeurs sont des tableaux. Cette solution n’est pas très belle, une solution avec un fichier xml devrait être meilleure:

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[@]}"
38
Nahuel Fouilleul

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 sera assimilé à une expression arithmétique et évalué à 0 (la valeur à droite de l'opérateur de virgule).

17
Jahid

C'est ce qui a fonctionné pour moi.

# 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

C'est basé sur cette réponse ici

13
Paul Sheldrake

Indépendamment du shell utilisé (sh, ksh, bash, ...), l'approche suivante fonctionne plutôt bien pour les tableaux à n dimensions (l'échantillon couvre un tableau à 2 dimensions). 

Dans l'exemple, le séparateur de lignes (1ère dimension) est le caractère d'espace. Pour introduire un séparateur de champ (2ème dimension), l'outil unix standard tr est utilisé. Des séparateurs supplémentaires pour des dimensions supplémentaires peuvent être utilisés de la même manière.

Bien sûr, la performance de cette approche n’est pas très bonne, mais si la performance n’est pas un critère, cette approche est assez générique et peut résoudre de nombreux problèmes:

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

La sortie de cet exemple ressemble à ceci:

1.1     1.2     1.3     
2.1     2.2     
3.1     3.2     3.3     3.4 
8
yaccob

Après beaucoup d'essais et d'erreurs, je trouve que le tableau multidimensionnel le meilleur, le plus clair et le plus simple sur bash consiste à utiliser un var régulier. Oui.

Avantages: Vous n'avez pas besoin de parcourir un grand tableau, vous pouvez simplement appeler "$ var" et utiliser grep/awk/sed. C'est facile et clair et vous pouvez avoir autant de colonnes que vous le souhaitez.

Exemple:

$ 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

Si vous voulez trouver tout le monde au pérou

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

Seulement grep (sed) dans le troisième champ

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

Si vous voulez seulement x champ

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

Tout le monde au pérou s'appelle thomas et retourne son nom de famille

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

Toute requête à laquelle vous pouvez penser ... super facile.

Pour changer un article:

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

Pour supprimer une ligne contenant "x"

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

Pour modifier un autre champ dans la même ligne en fonction d'une valeur provenant d'un autre élément

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

Bien sûr, la boucle fonctionne aussi si vous voulez le faire

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

La seule chose que l’on puisse trouver avec cela est que vous devez citez toujours le Var (dans l’exemple; var et i) ou les choses vont ressembler à ceci

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

et quelqu'un dira sans doute que cela ne fonctionnera pas si vous avez des espaces dans votre entrée, mais cela peut être corrigé en utilisant un autre séparateur dans votre entrée, par exemple (en utilisant un caractère utf8 maintenant pour souligner que vous pouvez choisir quelque chose que votre entrée ne permettra pas. contenir, mais vous pouvez choisir ce que vous voulez):

$ 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

Si vous souhaitez stocker des nouvelles lignes dans votre entrée, vous pouvez convertir la nouvelle ligne en quelque chose d'autre avant l'entrée et la reconvertir à la sortie (ou n'utilisez pas bash ...). Prendre plaisir!

4
n00p

En développant la réponse de Paul - voici ma version de l'utilisation de sous-tableaux associatifs dans 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

Cela fonctionne avec des valeurs mélangées dans le tableau principal - strings/arrays/assoc. tableaux

La clé ici consiste à envelopper les sous-tableaux entre guillemets simples et à utiliser * au lieu de @ lors du stockage d'un sous-tableau dans le tableau principal afin qu'il soit stocké sous la forme d'une seule chaîne séparée par des espaces: "${SUB_1[*]}"

Ensuite, il est facile d'analyser un tableau en dehors de cela lorsque vous parcourez des valeurs avec IFS=' ' read -a val <<< ${MAIN_ARRAY[$key]}

Le code ci-dessus affiche:

COUNT:  4
VALUE:  name1val name2val
SUBVALUE:  name1val
SUBVALUE:  name2val
VALUE:  name4val name3val
SUBVALUE:  name4val
SUBVALUE:  name3val
VALUE:  string1val
VALUE:  string2val
2
Vigintas Labakojis

Je publie ce qui suit, car il s’agit d’un moyen très simple et clair d’imiter (au moins dans une certaine mesure) le comportement d’un tableau à deux dimensions dans Bash. Il utilise un fichier here (voir le manuel Bash) et read (une commande intégrée Bash):

## 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.$$ <<EOF
Wolfgang Pauli 1900
Werner Heisenberg 1901
Albert Einstein 1879
Niels Bohr 1885
EOF
nbPhysicists=$(wc -l physicists.$$ | cut -sf 1 -d ' ')     # Number of lines of the here-file specifying the physicists.

## Extract the needed data
declare -a person     # Create an indexed array (necessary for the read command).                                                                                 
while read -ra person; do
    firstName=${person[0]}
    familyName=${person[1]}
    birthYear=${person[2]}
    echo "Physicist ${firstName} ${familyName} was born in ${birthYear}"
    # Do whatever you need with data
done < physicists.$$

## Remove the temporary file
rm physicists.$$

Sortie: 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

La façon dont cela fonctionne:

  • Les lignes du fichier temporaire créé jouent le rôle de vecteurs unidimensionnels, où les espaces (ou le caractère de séparation que vous choisissez; voir la description de la commande read dans le manuel Bash) séparent les éléments de ces vecteurs.
  • Ensuite, en utilisant la commande read avec l’option -a, nous parcourons chaque ligne du fichier (jusqu’à la fin du fichier). Pour chaque ligne, nous pouvons affecter les champs souhaités (= mots) à un tableau, que nous avons déclaré juste avant la boucle. L'option -r de la commande read empêche les barres obliques inverses d'agir en tant que caractères d'échappement, au cas où nous aurions saisi des barres obliques inverses dans le document here-document physicists.$$.

En conclusion, un fichier est créé sous forme de tableau 2D et ses éléments sont extraits à l'aide d'une boucle sur chaque ligne et de la possibilité offerte par la commande read d'attribuer des mots aux éléments d'un tableau (indexé).

Légère amélioration:

Dans le code ci-dessus, le fichier physicists.$$ est donné en entrée de la boucle while, de sorte qu'il est en fait transmis à la commande read. Cependant, j'ai constaté que cela posait des problèmes lorsque j'avais une autre commande demandant une entrée dans la boucle while. Par exemple, la commande select attend l’entrée standard et si elle est placée dans la boucle while, elle prendra l’entrée de physicists.$$, au lieu d’inviter la ligne de commande à entrer l'utilisateur .. utilisez l'option -u de read, qui permet de lire à partir d'un descripteur de fichier. Il suffit de créer un descripteur de fichier (avec la commande exec) correspondant à physicists.$$ et de le donner à l'option -u de lecture, comme dans le code suivant:

## 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.$$ <<EOF
Wolfgang Pauli 1900
Werner Heisenberg 1901
Albert Einstein 1879
Niels Bohr 1885
EOF
nbPhysicists=$(wc -l physicists.$$ | cut -sf 1 -d ' ')     # Number of lines of the here-file specifying the physicists.
exec {id_nuclei}<./physicists.$$     # Create a file descriptor stored in 'id_nuclei'.

## Extract the needed data
declare -a person     # Create an indexed array (necessary for the read command).                                                                                 
while read -ra person -u "${id_nuclei}"; do
    firstName=${person[0]}
    familyName=${person[1]}
    birthYear=${person[2]}
    echo "Physicist ${firstName} ${familyName} was born in ${birthYear}"
    # Do whatever you need with data
done

## Close the file descriptor
exec {id_nuclei}<&-
## Remove the temporary file
rm physicists.$$

Notez que le descripteur de fichier est fermé à la fin.

1
Giuseppe

Bash ne supporte pas les tableaux multidimensionnels, mais nous pouvons l'implémenter en utilisant Associer des tableaux. Ici, les index sont la clé pour récupérer la valeur. Associate Array est disponible dans bash version 4.

#!/bin/bash

declare -A arr2d
rows=3
columns=2

for ((i=0;i<rows;i++)) do
    for ((j=0;j<columns;j++)) do
        arr2d[$i,$j]=$i
    done
done


for ((i=0;i<rows;i++)) do
    for ((j=0;j<columns;j++)) do
        echo ${arr2d[$i,$j]}
    done
done
0
rashok

J'ai une solution de contournement simple mais intelligente: Il suffit de définir le tableau avec des variables dans son nom. Par exemple:

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

Je ne sais pas si cela aide, car ce n'est pas exactement ce que vous avez demandé, mais cela fonctionne pour moi. (La même chose pourrait être réalisée uniquement avec des variables sans le tableau)

0
testiner

Je le fais en utilisant tableaux associatifs puisque bash 4 et en réglantIFSsur une valeur pouvant être définie manuellement.

Le but de cette approche est d’avoir des tableaux comme valeurs de clés de tableaux associatifs.

Afin de remettre IFS à sa valeur par défaut, il suffit de le désélectionner.

  • unset IFS

Ceci est un exemple:

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

La sortie du script ci-dessous est:

default IFS
  x3:x4:x5
  x3:x4:x5
IFS=:
  x3
  x4
  x5
  x3
  x4
  x5

 or

  1
    x3
    x4
    x5
  2
    x3
    x4
    x5
0
rocksteady