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.
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[@]}"
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).
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
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
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
$ echo "$var" | grep peru
thomas johnson peru
johnny lipp peru
$ echo "$var" | sed -n -E '/(.+) (.+) peru/p'
thomas johnson peru
johnny lipp peru
$ echo "$var" | awk '{print $2}'
hansen
johnson
abu
johnny
$ echo "$var" |grep peru|grep thomas|awk '{print $2}'
johnson
Toute requête à laquelle vous pouvez penser ... super facile.
$ var=$(echo "$var"|sed "s/thomas/pete/")
$ var=$(echo "$var"|sed "/thomas/d")
$ var=$(echo "$var"|sed -E "s/(thomas) (.+) (.+)/\1 test \3/")
$ echo "$var"
kris hansen oslo
thomas test peru
bibi abu johnsonville
johnny lipp peru
$ 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!
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
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:
read
dans le manuel Bash) séparent les éléments de ces vecteurs.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.
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
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)
Je le fais en utilisant tableaux associatifs puisque bash 4 et en réglantIFS
sur 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