Transformer une chaîne de caractères en tableau
Cette opération de transformation d'une chaîne de caractères en tableau est souvent disponible dans les languages de programmation sous la forme d'une fonction souvent appelée « split ».
Sous bash, on peut utiliser les fonctions de manipulation des chaînes de caractères et des tableaux pour transformer directement une chaîne de caractères en tableau (ils sont décrits plus en détail dans l'article « Manipulation des chaînes de caractères et des tableaux en bash ».)
Par exemple, voici une solution possible pour assigner chaque chiffre d'une adresse IPv4 à un élément de tableau :
$ ip="192.168.42.3"
$ tableau=(${ip//./ })
$ echo ${tableau[@]}
192 168 42 3
$ echo ${tableau[2]}
42
Transformer un tableau en chaîne de caractères
À l'inverse de « split », il y a la fonction couramment appelée « join » dans plusieurs languages de programmation, qui permet de transformer une liste en chaîne de caractère. Sous bash, il y a plusieurs solutions, la plus élégante étant probablement la suivante :
$ tableau=(192 168 42 3)
$ ip="$(IFS="."; echo "${tableau[*]}")"
$ echo $ip
192.168.42.3
Pour un peu plus d'efficacité (il n'y a pas de création de sous-processus) au détriment du nombre de lignes, on peut écrire la même opération comme ceci :
$ tableau=(192 168 42 3)
$ OLD_IFS="$IFS"
$ IFS="."
$ ip="${tableau[*]}"
$ IFS="$OLD_IFS"
$ echo $ip
192.168.42.3
Dans les deux cas, il faut bien faire prendre soin d'utiliser
"${tableau[*]}"
, et non "${tableau[@]}"
pour afficher tous les éléments du
tableau, et de mettre cette expression entre guillements doubles. On tire ainsi
partie de la notation "${tableau[*]}"
. Extrait du manuel de bash à ce sujet :
(...) entre guillemets doubles,
${nom[*]}
se développe en un seul mot contenant les valeurs de chaque élément du tableau séparées par le premier caractère de la variable spécialeIFS
et${nom[@]}
développe chaque élément de nom en un mot distinct.
Dans le premier exemple ci-dessus, IFS
n'est modifiée que dans le
sous-processus créé par $()
, mais dans le second, tout se passe dans le même
processus, et on doit donc manuellement sauvegarder puis rétablir la valeur
initiale de IFS
pour qu'il n'y ait pas d'incidence sur les opérations
effectuées par la suite dans le processus exécutant le shell.
Assigner les éléments d'un tableau à des variables
Cette opération n'est pas vraiment propre aux tableaux, mais peut être pratique
quand on les utilise. Pour assigner les éléments d'un tableau à des variables,
on peut utiliser la commande read
combinée à une variante des documents en
ligne, <<<
:
$ tableau=(192 168 42 3)
$ read a b c d <<< "${tableau[*]}"
$ echo "a=$b b=$b c=$c d=$d"
a=192 b=168 c=42 d=3
Si le nombre de variables est inférieur au nombre d'éléments, la dernière
variable reçoit la liste des éléments non assignés, séparés par une espace (ou
plus précisément le premier caractère de la variable spéciale IFS
, qui est
par défaut une espace) :
$ tableau=(192 168 42 0)
$ read a b c <<< "${tableau[*]}"
$ echo "c=$c"
c=42 3
Si, à l'inverse, le nombre de variables est supérieur au nombre d'éléments du tableau, les variables surnuméraires resteront simplement vides.
Modifier tous les éléments d'un tableau en une opération
Il est possible de modifier tous les éléments d'un tableau en une seule action grâce aux fonctions internes de bash de manipulation des chaînes de caractères. Lorsqu'elles sont appliquées sur un tableau, celles-ci agissent sur tous ses éléments.
À titre d'exemple, il est ainsi possible d'effectuer une substitution sur tous les éléments d'un tableau avec cette simple commande :
$ tableau=("bender_is_smart" "bender_is_nice" "bender_rules")
$ echo "${tableau[@]/bender/flexo}"
flexo_is_smart flexo_is_nice flexo_rules
$ tableau_modifie=("${tableau[@]/bender/flexo}")
Pour connaître les autres fonctions de modifications de chaînes de caractères applicables aux tableaux, consultez la documentation « Manipulation des chaînes de caractères et des tableaux en bash ».
Opérations de base sur les listes
Un tableau indicé peut être utilisé comme une liste ordonnée et manipulé avec les opérations présentées ci-dessous à condition que chacune de ces opérations respecte ces deux règles :
-
le premier élément doit toujours se trouver à la position 0 du tableau;
-
les indices utilisés pour les éléments doivent toujours être consécutifs. On ne doit donc pas forcer les indices des éléments (comme ceux les initialisations du type
declare -a tableau=([42]="La réponse")
).
J'utilise ci-dessous le terme de liste pour désigner l'utilisation particulière d'un tableau indicé dans le respect de ces deux règles.
Ajouter un élément en début de liste
Une façon d'ajouter un élément en début de liste est de le recréer le tableau en préfixant son contenu actuel par l'élément à insérer.
$ declare -a tableau=("un" "deux")
$ tableau=("zero" "${tableau[@]}")
$ echo ${tableau[@]}
zero un deux
En recréant un tableau de même nom avec l'élément supplémentaire à son début, l'indice des anciens éléments est augmenté de un, et le nouvel élément est bien le premier, à l'indice 0.
Note: lors de la réinitialisation du tableau, les anciens éléments du tableau
doivent impérativement être insérés sous la forme "${tableau[@]}"
, avec des
guillements doubles et @
comme indice (et non *
). Sans le respect de ces
deux points, soit les éléments contenant des espaces seraient développés en
plusieurs éléments, soit tous les éléments ne formeraient qu'un seul mot et
seraient donc confondus en un seul élément.
Ajouter un élément en fin de liste
Pour ajouter un élément en fin de liste, on peut utiliser une solution similaire à l'ajout en début de liste dans laquelle on recrée le tableau en ajoutant le nouvel élément après ceux du tableau actuel :
$ declare -a tableau=("zero" "un")
$ tableau=("${tableau[@]}" "deux")
$ echo ${tableau[@]}
zero un deux
Note: comme précédemment, lors de la réinitialisation du tableau il faut
impérativement utiliser la notation "${tableau[@]}"
, avec des guillements
doubles et @
comme indice.
Une autre solution consiste à ajouter l'élément à la position fournie par le
nombre d'éléments dans la liste (qu'on obtient avec ${#tableau[@]}
).
$ declare -a tableau=("zero" "un")
$ tableau[${#tableau[@]}]="deux"
$ echo ${tableau[@]}
zero un deux
Note : la taille du tableau est bien la position après le dernier élément, puisque les tableaux sont indicés à partir de 0 et qu'on suppose dans le cadre de leur utilisation en liste que les indices sont tous consécutifs.
Supprimer le premier élément d'une liste
Selon la première règle à respecter, le premier élément de la liste est à
l'indice 0, et comme l'indique l'article « Introduction aux tableaux en
bash, la suppression d'un élément
se fait avec la commande unset
. Cependant, après la commande unset tableau[0]
, le premier élément se trouve à l'indice 1.
Pour continuer à respecter la règle du premier élément à l'indice 0, il faut changer l'indice des autres éléments pour les diminuer de un. Ce changement d'indices peut être obtenu simplement en réinitialisant le tableau avec les mêmes éléments :
$ tableau=("zero" "un" "deux")
$ unset tableau[0]
$ tableau=("${tableau[@]}")
$ echo ${tableau[*]}
un deux
$ echo ${!tableau[*]}
0 1
Note: comme plus haut, lors de la réinitialisation du tableau, il faut
impérativement utiliser la notation "${tableau[@]}"
, avec des guillements
doubles et @
comme indice.
Supprimer le dernier élément d'une liste
En supposant que le tableau respecte les règles des listes ordonnées énoncées
plus haut, le dernier élément se trouve à l'indice taille - 1. Il suffit donc
de le supprimer simplement avec unset
:
$ tableau=(zero un deux)
$ unset tableau[-1]
$ echo ${tableau[*]}
zero un
Note: cet exemple a été testé sous la version 4.3 de bash. Sauf erreur de ma
part, dans certaines versions plus anciennes, on ne pouvait semble-t-il pas
supprimer le dernier élément d'un tableau en s'y référant avec l'indice -1
.
Il fallait alors le supprimer avec une expression du type :
Merci à Grégory Roche de m'avoir signalé que la notation unset tableau[-1]
était possible et que les indices des tableaux bénéficient d'une évaluation
arithmétique (et que la forme unset tableau[$((${#tableau[*]}-1))]
que
j'employais ici était donc inutile).