Une compilation de documentations   { en , fr }

Utilisation des tableaux pour manipuler des données en bash

Étiquettes:
Créé en:
Auteur:
Xavier Béguin

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éciale IFS 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 :

unset tableau[${#tableau[*]}-1]

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).