Les versions récentes de Bash supportent les tableaux à une dimension. Les éléments du tableau devraient être initialisés avec la notation variable[xx]. Autrement, un script peut introduire le tableau entier par une instruction explicite declare -a variable. Pour déréférencer (trouver le contenu d') un élément du tableau, utilisez la notation à accolade, c'est-à-dire ${variable[xx]}.
Exemple 26.1. Utilisation d'un tableau simple
#!/bin/bash aire[11]=23 aire[13]=37 aire[51]=UFOs # Les membres d'un tableau peuvent ne pas être consécutifs ou contigus. # Certains membres peuvent rester non initialisés. # Les trous dans le tableau sont OK. # En fait, les tableaux avec des données "écartées" sont utiles dans les tableurs. echo -n "aire[11] = " echo ${aire[11]} # {accolades} nécessaires. echo -n "aire[13] = " echo ${aire[13]} echo "Le contenu de aire[51] est ${aire[51]}." # Le contenu d'une variable non initialisée d'un tableau n'affiche rien (variable nulle). echo -n "aire[43] = " echo ${aire[43]} echo "(aire[43] non affecté)" echo # Somme de deux variables tableaux affectée à une troisième. aire[5]=`expr ${aire[11]} + ${aire[13]}` echo "aire[5] = aire[11] + aire[13]" echo -n "aire[5] = " echo ${aire[5]} aire[6]=`expr ${aire[11]} + ${aire[51]}` echo "aire[6] = aire[11] + aire[51]" echo -n "aire[6] = " echo ${aire[6]} # Ceci échoue car ajouter un entier à une chaîne de caractères n'est pas permis. echo; echo; echo # ----------------------------------------------------------------- # Autre tableau, "aire2". # Autre façon d'affecter les variables d'un tableau... # nom_tableau=( XXX YYY ZZZ ... ) aire2=( zero un deux trois quatre ) echo -n "aire2[0] = " echo ${aire2[0]} # Aha, indexage commençant par 0 (le premier élément du tableau est [0], et non # pas [1]). echo -n "aire2[1] = " echo ${aire2[1]} # [1] est le deuxième élément du tableau. # ----------------------------------------------------------------- echo; echo; echo # ----------------------------------------------- # Encore un autre tableau, "aire3". # Encore une autre façon d'affecter des variables de tableau... # nom_tableau=([xx]=XXX [yy]=YYY ...) aire3=([17]=dix-sept [24]=vingt-quatre) echo -n "aire3[17] = " echo ${aire3[17]} echo -n "aire3[24] = " echo ${aire3[24]} # ----------------------------------------------- exit 0
Comme nous l'avons vu, une façon agréable d'initialiser un tableau complet est la notation array=( element1 element2 ... elementN).
Exemple 26.2. Formatage d'un poème
#!/bin/bash # poem.sh : affiche joliment un des poèmes préférés de l'auteur du document. # Lignes d'un poème (simple stanza). Ligne[1]="I do not know which to prefer," Ligne[2]="The beauty of inflections" Ligne[3]="Or the beauty of innuendoes," Ligne[4]="The blackbird whistling" Ligne[5]="Or just after." # Attribution. Attrib[1]=" Wallace Stevens" Attrib[2]="\"Thirteen Ways of Looking at a Blackbird\"" # Ce poème est dans le domaine public (copyright expiré). echo for index in 1 2 3 4 5 # Cinq lignes. do printf " %s\n" "${Ligne[index]}" done for index in 1 2 # Deux lignes. do printf " %s\n" "${Attrib[index]}" done echo exit 0 # Exercice : # --------- # Modifiez ce script pour afficher joliment un poème à partir d'un fichier de # données au format texte.
Les variables tableau ont une syntaxe propre, et même les commandes standards Bash et les opérateurs ont des options spécifiques adaptées à l'utilisation de tableaux.
Exemple 26.3. Opérations de chaînes sur des tableaux
#!/bin/bash # array-strops.sh : Opérations sur des chaînes comprises dans des tableaux. # Script de Michael Zick. # Utilisé avec sa permission. # En général, toute opération sur des chaînes avec la notation ${nom ... } #+ peut être appliquée aux éléments de type chaîne de caractères d'un tableau #+ en utilisant la notation ${nom[@] ... } ou ${nom[*] ...}. tableauZ=( un deux trois quatre cinq cinq ) echo # Extraction de la dernière sous-chaîne echo ${tableauZ[@]:0} # un deux trois quatre cinq cinq # Tous les éléments. echo ${tableauZ[@]:1} # deux trois quatre cinq cinq # Tous les éléments après element[0]. echo ${tableauZ[@]:1:2} # deux trois # Seulement les deux éléments après element[0]. echo "-----------------------" # Suppression d'une sous-chaîne # Supprime la plus petite correspondance au début de(s) chaîne(s), #+ la sous-chaîne étant une expression rationnelle. echo ${tableauZ[@]#q*e} # un deux trois cinq cinq # Appliqué à tous les éléments du tableau. # Correspond à "quatre" et le supprime. # Correspondance la plus longue au début d'une chaîne echo ${tableauZ[@]##t*s} # un deux quatre cinq cinq # Appliqué à tous les éléments du tableau. # Correspond à "trois" et le supprime. # Plus petite correspondance à partir de la fin de(s) chaîne(s) echo ${tableauZ[@]%r*s} # un deux t quatre cinq cinq # Appliqué à tous les éléments du tableau. # Correspond à "rois" et le supprime. # Plus longue correspondance à partir de la fin des chaînes. echo ${tableauZ[@]%%t*s} # un deux quatre cinq cinq # Appliqué à tous les éléments du tableau. # Correspond à "trois" et le supprime. echo "-----------------------" # Remplacement de sous-chaînes # Remplace la première occurence d'une sous-chaîne echo ${tableauZ[@]/cin/XYZ} # un deux trois quatre XYZq XYZq # Appliqué à tous les éléments de la sous-chaîne. # Remplace toutes les occurences de la sous-chaîne echo ${tableauZ[@]//in/YY} # un deux trois quatre cYYq cYYq # Appliqué à tous les éléments de la sous-chaîne. # Supprime toutes les occurences de la sous-chaîne # Ne pas spécifier un remplacement suppose une 'suppression'. echo ${tableauZ[@]//ci/} # un deux trois quatre nq nq # Appliqué à tous les éléments de la sous-chaîne. # Remplace le début des occurences de la sous-chaîne echo ${tableauZ[@]/#ci/XY} # un deux trois quatre XYnq XYnq # Appliqué à tous les éléments de la sous-chaîne. # Remplace la fin des occurences de la sous-chaînes echo ${tableauZ[@]/%nq/ZZ} # un deux trois quatre ciZZ ciZZ # Appliqué à tous les éléments de la sous-chaîne. echo ${tableauZ[@]/%u/XX} # XX deXX trois qXXXXX cinq cinq # Pourquoi ? echo "-----------------------" # Avant de regarder awk (ou autre chose) # Rappel : # $( ... ) est une substitution de commande. # Les fonctions sont lancées en tant que sous-processus. # Les fonctions écrivent leur propre sortie vers stdout. # Les affectations lisent le stdout de la fonction. # La notation nom[@] spécifie une opération "for-each". nouvellechaine() { echo -n "!!!" } echo ${tableauZ[@]/%u/$(nouvellechaine)} # !!!n de!!!x trois q!!!atre cinq cinq # Q.E.D: L'action de remplacement est une 'affectation'. # Accéder au "For-Each" echo ${tableauZ[@]//*/$(nouvellechaine arguments_optionnels)} # Maintenant, si Bash passait juste la chaîne correspondante comme $0 à la #+ fonction appelée... echo exit 0
La substitution de commandes peut construire les éléments individuels d'un tableau.
Exemple 26.4. Charger le contenu d'un script dans un tableau
#!/bin/bash # script-array.sh : Charge ce script dans un tableau. # Inspiré d'un e-mail de Chris Martin (merci !). contenu_script=( $(cat "$0") ) # Enregistre le contenu de ce script ($0) #+ dans un tableau. for element in $(seq 0 $((${#contenu_script[@]} - 1))) do # ${#contenu_script[@]} #+ donne le nombre d'éléments dans le tableau. # # Question: # Pourquoi seq 0 est-il nécessaire ? # Essayez de le changer en seq 1. echo -n "${contenu_script[$element]}" # Affiche tous les champs de ce script sur une seule ligne. echo -n " -- " # Utilise " -- " comme séparateur de champs. done echo exit 0 # Exercice : # --------- # Modifiez ce script de façon à ce qu'il s'affiche lui-même dans son format #+ original, entier avec les espaces blancs, les retours ligne, etc.
Dans un contexte de tableau, quelques commandes intégrées Bash ont une signification légèrement modifiée. Par exemple, unset supprime des éléments du tableau, voire un tableau entier.
Exemple 26.5. Quelques propriétés spéciales des tableaux
#!/bin/bash declare -a couleurs # Toutes les commandes suivantes dans ce script traiteront #+ la variable "couleurs" comme un tableau. echo "Entrez vos couleurs favorites (séparées par une espace)." read -a couleurs # Entrez au moins trois couleurs pour démontrer les #+ fonctionnalités ci-dessous. # Option spéciale pour la commande 'read' #+ permettant d'affecter les éléments dans un tableau. echo nb_element=${#couleurs[@]} # Syntaxe spéciale pour extraire le nombre d'éléments d'un tableau. # nb_element=${#couleurs[*]} fonctionne aussi. # # La variable "@" permet de diviser les mots compris dans des guillemets #+ (extrait les variables séparées par des espaces blancs). # # Ceci correspond au comportement de "$@" et "$*" #+ dans les paramètres de positionnement. index=0 while [ "$index" -lt "$nb_element" ] do # Liste tous les éléments du tableau. echo ${couleurs[$index]} let "index = $index + 1" # ou index+=1 avec Bash 3.1 et suivants done # Chaque élément du tableau est listé sur une ligne séparée. # Si ceci n'est pas souhaité, utilisez echo -n "${couleurs[$index]} " # # Pour le faire avec une boucle "for": # for i in "${couleurs[@]}" # do # echo "$i" # done # (Thanks, S.C.) echo # Encore une fois, liste tous les éléments d'un tableau, mais en utilisant une #+ méthode plus élégante. echo ${couleurs[@]} # echo ${couleurs[*]} fonctionne aussi. echo # La commande "unset" supprime les éléments d'un tableau ou un tableau entier. unset couleurs[1] # Supprime le deuxième élément d'un tableau. # Même effet que couleurs[1]= echo ${couleurs[@]} # Encore un tableau liste, dont le deuxième # élément est manquant. unset couleurs # Supprime le tableau entier. # unset couleurs[*] et #+ unset couleurs[@] fonctionnent aussi. echo; echo -n "couleurs parties." echo ${couleurs[@]} # Affiche le tableau une nouvelle fois, maintenant #+ vide. exit 0
Comme vu dans l'exemple précédent, soit ${nom_tableau[@]} soit ${nom_tableau[*]} fait réfèrence à tous les éléments du tableau. De même, pour obtenir le nombre d'éléments dans un tableau, utilisez soit ${#nom_tableau[@]} soit ${#nom_tableau[*]}. ${#nom_tableau} est la longueur (nombre de caractères) de ${nom_tableau[0]}, le premier élément du tableau.
Exemple 26.6. Des tableaux vides et des éléments vides
#!/bin/bash # empty-array.sh # Merci à Stephane Chazelas pour l'exemple original #+ et à Michael Zick et Omair Eshkenazi pour son extension. # Un tableau vide n'est pas la même chose qu'un tableau composé d'éléments #+ vides. tableau0=( premier deuxieme troisieme ) tableau1=( '' ) # "tableau1" consiste en un élément vide. tableau2=( ) # Pas d'éléments . . . "tableau2" est vide. tableau3=( ) # Que peut-on dire de ce tableau ? echo AfficheTableau() { echo echo "Éléments de tableau0 : ${tableau0[@]}" echo "Éléments de tableau1 : ${tableau1[@]}" echo "Éléments de tableau2 : ${tableau2[@]}" echo "Éléments de tableau3 : ${tableau3[@]}" echo echo "Longueur du premier élément du tableau0 = ${#tableau0}" echo "Longueur du premier élément du tableau1 = ${#tableau1}" echo "Longueur du premier élément du tableau2 = ${#tableau2}" echo "Longueur du premier élément du tableau3 = ${#tableau3}" echo echo "Nombre d'éléments du tableau0 = ${#tableau0[*]}" # 3 echo "Nombre d'éléments du tableau1 = ${#tableau1[*]}" # 1 (Surprise !) echo "Nombre d'éléments du tableau2 = ${#tableau2[*]}" # 0 echo "Nombre d'éléments du tableau3 = ${#tableau3[*]}" # 0 } # =================================================================== AfficheTableau # Essayons d'étendre ces tableaux. # Ajouter un élément à un tableau. tableau0=( "${tableau0[@]}" "nouveau1" ) tableau1=( "${tableau1[@]}" "nouveau1" ) tableau2=( "${tableau2[@]}" "nouveau1" ) tableau3=( "${tableau3[@]}" "nouveau1" ) AfficheTableau # ou tableau0[${#tableau0[*]}]="nouveau2" tableau1[${#tableau1[*]}]="nouveau2" tableau2[${#tableau2[*]}]="nouveau2" tableau3[${#tableau3[*]}]="nouveau2" AfficheTableau # Lors d'un ajout comme ci-dessus ; les tableaux sont des piles ('stacks') # La commande ci-dessus correspond à un 'push' # La hauteur de la pile est : hauteur=${#tableau2[@]} echo echo "Hauteur de pile pour tableau2 = $hauteur" # L'opération 'pop' est : unset tableau2[${#tableau2[@]}-1] # L'index des tableaux commence à zéro, hauteur=${#tableau2[@]} #+ ce qui signifie que le premier élément se #+ trouve à l'index 0. echo echo "POP" echo "Nouvelle hauteur de pile pour tableau2 = $hauteur" AfficheTableau # Affiche seulement les 2è et 3è éléments de tableau0. de=1 # Numérotation débutant à zéro. a=2 # tableau3=( ${tableau0[@]:1:2} ) echo echo "Éléments de tableau3 : ${tableau3[@]}" # Fonctionne comme une chaîne (tableau de caractères). # Essayez les autres formes de "chaînes". # Remplacement : tableau4=( ${tableau0[@]/deuxieme/2è} ) echo echo "Éléments de tableau4 : ${tableau4[@]}" # Remplacez toutes les chaînes correspondantes. tableau5=( ${tableau0[@]//nouveau?/ancien} ) echo echo "Éléments de tableau5 : ${tableau5[@]}" # Juste quand vous commencez à vous habituer... tableau6=( ${tableau0[@]#*nouveau} ) echo # Ceci pourrait vous surprendre. echo "Éléments du tableau6 : ${tableau6[@]}" tableau7=( ${tableau0[@]#nouveau1} ) echo # Après tableau6, ceci ne devrait plus être une surprise. echo "Éléments du tableau7 : ${tableau7[@]}" # Qui ressemble beaucoup à... tableau8=( ${tableau0[@]/nouveau1/} ) echo echo "Éléments du tableau8 : ${tableau8[@]}" # Donc, que pouvez-vous conclure de ceci ? # Les opérations sur des chaînes sont réalisées sur chaque éléments #+ de var[@] à la suite. # Donc : Bash supporte les opérations vectorielles sur les chaînes. # Si le résultat est une chaîne de longueur vide, #+ l'élément disparaît dans l'affectation résultante. # Question, ces chaînes sont-elles entre simples ou doubles guillemets ? zap='nouveau*' tableau9=( ${tableau0[@]/$zap/} ) echo echo "Éléments du tableau9 : ${tableau9[@]}" # Juste au moment où vous pensiez être toujours en pays connu... tableau10=( ${tableau0[@]#$zap} ) echo echo "Éléments du tableau10 : ${tableau10[@]}" # Comparez le tableau7 avec le tableau10. # Comparez le tableau8 avec le tableau9. # Réponse : Cela doit être des simples guillemets. exit 0
La relation entre ${nom_tableau[@]} et ${nom_tableau[*]} est analogue à celle entre $@ et $*. Cette notation de tableau très puissante a un certain nombre d'intérêts.
# Copier un tableau. tableau2=( "${tableau1[@]}" ) # ou tableau2="${tableau1[@]}" # # However, this fails with "sparse" arrays, #+ arrays with holes (missing elements) in them, #+ as Jochen DeSmet points out. # ------------------------------------------ array1[0]=0 # array1[1] not assigned array1[2]=2 array2=( "${array1[@]}" ) # Copy it? echo ${array2[0]} # 0 echo ${array2[2]} # (null), should be 2 # ------------------------------------------ # Ajout d'un élément dans un tableau. array=( "${array[@]}" "nouvel élément" ) # or array[${#array[*]}]="nouvel élément" # Merci, S.C.
L'opération d'initialisation tableau=( element1 element2 ... elementN ), avec l'aide de la substitution de commandes, rend possible de charger le contenu d'un fichier texte dans un tableau.
#!/bin/bash nomfichier=fichier_exemple # cat fichier_exemple # # 1 a b c # 2 d e fg declare -a tableau1 tableau1=( `cat "$nomfichier" `) # Charge le contenu # de $nomfichier dans tableau1. # affiche le fichier sur stdout. # tableau1=( `cat "$nomfichier" | tr '\n' ' '`) # modifie les retours chariots en espace. # Non nécessaire car Bash réalise le découpage des mots, modifiant les # changements de ligne en espaces. echo ${tableau1[@]} # Affiche le tableau. # 1 a b c 2 d e fg # # Chaque "mot" séparé par une espace dans le fichier a été affecté à un #+ élément du tableau. nb_elements=${#tableau1[*]} echo $nb_elements # 8
Une écriture intelligente de scripts rend possible l'ajout d'opérations sur les tableaux.
Exemple 26.7. Initialiser des tableaux
#! /bin/bash # array-assign.bash # Les opérations sur les tableaux sont spécifiques à Bash, #+ d'où le ".bash" dans le nom du script. # Copyright (c) Michael S. Zick, 2003, All rights reserved. # License: Unrestricted reuse in any form, for any purpose. # Version: $ID$ # # Clarification et commentaires supplémentaires par William Park. # Basé sur un exemple de Stephane Chazelas #+ qui est apparu dans le livre : Advanced Bash Scripting Guide. # Format en sortie de la commande 'times' : # CPU Utilisateur <espace> CPU système # CPU Utilisateur du fils mort <space> CPU système du fils mort # Bash a deux versions pour l'affectation de tous les éléments d'un tableau #+ vers une nouvelle variable tableau. # Les deux jetent les éléments à référence nulle #+ avec Bash version 2.04, 2.05a et 2.05b. # Une affectation de tableau supplémentaire qui maintient les relations de #+ [sousscript]=valeur pour les tableaux pourrait être ajoutée aux nouvelles #+ versions. # Construit un grand tableau en utilisant une commande interne, #+ mais tout ce qui peut construire un tableau de quelques milliers d'éléments #+ fera l'affaire. declare -a grandElement=( /dev/* ) # Tous les fichiers de /dev... echo echo 'Conditions : Sans guillemets, IFS par défaut, Tout élément' echo "Le nombre d'éléments dans le tableau est ${#grandElement[@]}" # set -vx echo echo '- - test: =( ${array[@]} ) - -' times declare -a grandElement2=( ${grandElement[@]} ) # Notez les parenthèses ^ ^ times echo echo '- - test: =${array[@]} - -' times declare -a grandElement3=${grandElement[@]} # Pas de parenthèses cette fois-ci. times # Comparer les nombres montre que la deuxième forme, indiquée par #+ Stephane Chazelas, est de trois à quatre fois plus rapide. # # Comme William Park l'explique : #+ Le tableau grandElement2 est affecté élément par élément à cause des parenthèses #+ alors que grandElement3 est affecté en une seule chaîne. # Donc, en fait, vous avez : # grandElement2=( [0]="..." [1]="..." [2]="..." ... ) # grandElement3=( [0]="... ... ..." ) # # Vérifiez ceci avec : echo ${grandElement2[0]} # echo ${grandElement3[0]} # Je continuerais à utiliser la première forme dans mes descriptions d'exemple #+ parce que je pense qu'il s'agit d'une meilleure illustration de ce qu'il se #+ passe. # Les portions réutilisables de mes exemples contiendront réellement la #+ deuxième forme quand elle est appropriée en ce qui concerne sa rapidité. # MSZ : Désolé à propos de ce survol précédent. # Note : # ----- # Les instructions "declare -a" des lignes 31 et 43 ne sont pas strictement # nécessaires car c'est implicite dans l'appel de Array=( ... ) # Néanmoins, éliminer ces déclarations ralentit l'exécution des sections # suivantes du script. # Essayez et voyez ce qui se passe. exit 0
Ajouter une instruction superflue declare -a pour la déclaration d'un tableau pourrait accélérer l'exécution des opérations suivantes sur le tableau.
Exemple 26.8. Copier et concaténer des tableaux
#! /bin/bash # CopieTableau.sh # # Ce script a été écrit par Michael Zick. # Utilisé ici avec sa permission. # Guide pratique "Passage par nom & Retour par nom" #+ ou "Construire votre propre instruction d'affectation". CopieTableau_Mac() { # Constructeur d'instruction d'affectation echo -n 'eval ' echo -n "$2" # Nom de la destination echo -n '=( ${' echo -n "$1" # Nom de la source echo -n '[@]} )' # Cela peut être une seule commande. # Une simple question de style. } declare -f CopieTableau # Fonction "Pointeur". CopieTableau=CopieTableau_Mac # Constructeur d'instruction. Hype() { # Hype le tableau nommé $1. # (L'ajoute au tableau contenant "Really Rocks".) # Retour dans le tableau nommé $2. local -a TMP local -a hype=( Really Rocks ) $($CopieTableau $1 TMP) TMP=( ${TMP[@]} ${hype[@]} ) $($CopieTableau TMP $2) } declare -a avant=( Advanced Bash Scripting ) declare -a apres echo "Tableau avant = ${avant[@]}" Hype avant apres !!!!!!!!!!!!!!!!!!!!!!!!! echo "Tableau après = ${apres[@]}" # Trop de 'hype' ? echo "Qu'est-ce que ${apres[@]:3:2}?" declare -a modeste=( ${apres[@]:2:1} ${apres[@]:3:2} ) # -- extraction de la sous-chaine -- echo "Tableau modeste = ${modeste[@]}" # Qu'arrive-t'il à 'avant' ? echo "Tableau avant = ${avant[@]}" exit 0
Exemple 26.9. Plus sur la concaténation de tableaux
#! /bin/bash # array-append.bash # Copyright (c) Michael S. Zick, 2003, All rights reserved. # License: Unrestricted reuse in any form, for any purpose. # Version: $ID$ # # Légèrement modifié au niveau formatage par M.C. # Les opérations sur les tableaux sont spécifiques à Bash. # Le /bin/sh de l'UNIX standard n'a pas d'équivalent. # Envoyer la sortie de ce script à 'more' #+ de façon à ce que le terminal affiche page par page. # Sous-script imbriqué. declare -a tableau1=( zero1 un1 deux1 ) # Sous-script léger ([1] n'est pas défini). declare -a tableau2=( [0]=zero2 [2]=deux2 [3]=trois3 ) echo echo '- Confirmez que ce tableau est vraiment un sous-script. -' echo "Nombre d'éléments : 4" # Codé en dur pour illustration. for (( i = 0 ; i < 4 ; i++ )) do echo "Élément [$i] : ${tableau2[$i]}" done # Voir aussi l'exemple de code plus général dans basics-reviewed.bash. declare -a dest # Combinez (ajoutez) deux tableaux dans un troisième. echo echo 'Conditions : Sans guillemets, IFS par défaut, opérateur tous-éléments-de' echo '- Éléments indéfinis non présents, sous-scripts non maintenus. -' # # Les éléments indéfinis n'existent pas ; ils ne sont pas réellement supprimés. dest=( ${tableau1[@]} ${tableau2[@]} ) # dest=${tableau1[@]}${tableau2[@]} # Résultats étranges, probablement un bogue. # Maintenant, affiche le résultat. echo echo "- - Test de l'ajout du tableau - -" cpt=${#dest[@]} echo "Nombre d'éléments : $cpt" for (( i = 0 ; i < cpt ; i++ )) do echo "Élément [$i] : ${dest[$i]}" done # Affecte un tableau sur un élément d'un tableau simple (deux fois). dest[0]=${tableau1[@]} dest[1]=${tableau2[@]} # Affiche le résultat. echo echo '- - Test du tableau modifié - -' cpt=${#dest[@]} echo "Nombre d'éléments : $cpt" for (( i = 0 ; i < cpt ; i++ )) do echo "Élément [$i] : ${dest[$i]}" done # Examine le deuxième élément modifié. echo echo '- - Réaffecte et affiche le deuxième élément - -' declare -a sousTableau=${dest[1]} cpt=${#sousTableau[@]} echo "Nombre d'éléments: $cpt" for (( i = 0 ; i < cpt ; i++ )) do echo "Element [$i] : ${sousTableau[$i]}" done # L'affectation d'un tableau entier sur un seul élément d'un autre tableau #+ utilisant l'opérateur d'affectation de tableau '=${ ... }' a converti le #+ tableau en cours d'affectation en une chaîne de caractères, les éléments #+ étant séparés par une espace (le premier caractère de IFS). # Si les éléments originaux ne contenaient pas d'espace blanc ... # Si la tableau original n'est pas un sous-script ... # Alors nous pouvons récupérer la structure du tableau original. # Restaurer à partir du second élément modifié. echo echo "- - Affichage de l'élément restauré - -" declare -a sousTableau=( ${dest[1]} ) cpt=${#sousTableau[@]} echo "Nombre d'éléments : $cpt" for (( i = 0 ; i < cpt ; i++ )) do echo "Élément [$i] : ${sousTableau[$i]}" done echo '- - Ne dépends pas de ce comportement - -' echo '- - Ce comportement est sujet à modification - -' echo '- - dans les versions de Bash ultérieures à la version 2.05b - -' # MSZ : Désolé pour la confusion précédente. exit 0
--
Les tableaux permettent de déployer de bons vieux algorithmes familiers en scripts shell. Que ceci soit réellement une bonne idée est laissé à l'appréciation du lecteur.
Exemple 26.10. Le tri bulle : Bubble Sort
#!/bin/bash # bubble.sh : Tri bulle, en quelque sorte. # Rappelle l'algorithme de tri bulle. Enfin, une version particulière... # À chaque itération successive à travers le tableau à trier, compare deux #+ éléments adjacents et les échange s'ils ne sont pas ordonnés. # À la fin du premier tour, l'élémennt le "plus lourd" est arrivé tout en bas. # À la fin du deuxième tour, le "plus lourd" qui suit est lui-aussi à la fin #+ mais avant le "plus lourd". # Et ainsi de suite. # Ceci signifie que chaque tour a besoin de se balader sur une partie de plus #+ en plus petite du tableau. # Vous aurez donc noté une accélération à l'affichage lors des derniers tours. echange() { # Échange deux membres d'un tableau local temp=${Pays[$1]} # Stockage temporaire #+ pour les éléments à échanger. Pays[$1]=${Pays[$2]} Pays[$2]=$temp return } declare -a Pays # Déclaration d'un tableau, #+ optionnel ici car il est initialisé tout de suite après. # Est-il permis de diviser une variable tableau sur plusieurs lignes en #+ utilisant un caractère d'échappement ? # Oui. Pays=(Hollande Ukraine Zaire Turquie Russie Yémen Syrie \ Brésil Argentine Nicaragua Japon Mexique Vénézuela Grèce Angleterre \ Israël Pérou Canada Oman Danemark France Kenya \ Xanadu Qatar Liechtenstein Hongrie) # "Xanadu" est la place mythique où, selon Coleridge, #+ Kubla Khan a décrété un summum de plaisir. clear # Efface l'écran pour commencer. echo "0: ${Pays[*]}" # Liste le tableau entier lors du premier tour. nombre_d_elements=${#Pays[@]} let "comparaisons = $nombre_d_elements - 1" index=1 # Nombre de tours. while [ "$comparaisons" -gt 0 ] # Début de la boucle externe. do index=0 # Réinitialise l'index pour commencer au début du tableau à chaque #+ tour. while [ "$index" -lt "$comparaisons" ] # Début de la boucle interne. do if [ ${Pays[$index]} \> ${Pays[`expr $index + 1`]} ] # Si non ordonné... # Rappelez-vous que \> est un opérateur de comparaison ASCII à l'intérieur #+ de simples crochets. # if [[ ${Pays[$index]} > ${Pays[`expr $index + 1`]} ]] #+ fonctionne aussi. then echange $index `expr $index + 1` # Échange. fi let "index += 1" # Ou index+=1 sur Bash 3.1 et suivants done # Fin de la boucle interne. # ---------------------------------------------------------------------- # Paulo Marcel Coelho Aragao suggère les boucles for comme alternative simple. # # for (( dernier = $nombre_d_elements - 1 ; dernier > 0 ; dernier-- )) ## Corrigé par C.Y. Hunt ^ (Merci) # do # for (( i = 0 ; i < dernier ; i++ )) # do # [[ "${Pays[$i]}" > "${Pays[$((i+1))]}" ]] \ # && echange $i $((i+1)) # done # done # ---------------------------------------------------------------------- let "comparaisons -= 1" # Comme l'élément le "plus lourd" est tombé en bas, #+ nous avons besoin de faire une comparaison de moins #+ à chaque tour. echo echo "$index: ${Pays[@]}" # Affiche le tableau résultat à la fin de chaque tour echo let "index += 1" # Incrémente le compteur de tour. done # Fin de la boucle externe. # Fini. exit 0
--
Est-il possible d'imbriquer des tableaux dans des tableaux ?
#!/bin/bash # Tableaux imbriqués. # Michael Zick a fourni cet exemple, #+ avec quelques corrections et clarifications de William Park. UnTableau=( $(ls --inode --ignore-backups --almost-all \ --directory --full-time --color=none --time=status \ --sort=time -l ${PWD} ) ) # Commandes et options. # Les espaces ont une signification... et ne mettez pas entre guillemets quoi #+ que ce soit ci-dessus. SousTableau=( ${UnTableau[@]:11:1} ${UnTableau[@]:6:5} ) # Ce tableau a six éléments : #+ SousTableau=( [0]=${UnTableau[11]} [1]=${UnTableau[6]} [2]=${UnTableau[7]} # [3]=${UnTableau[8]} [4]=${UnTableau[9]} [5]=${UnTableau[10]} ) # # Les tableaux en Bash sont des listes liées (circulaires) de type chaîne de #+ caractères (char *). # Donc, ce n'est pas réellement un tableau imbriqué mais il fonctionne de la #+ même manière. echo "Répertoire actuel et date de dernière modification :" echo "${SousTableau[@]}" exit 0
--
Les tableaux imbriqués combinés avec des références indirectes créent quelques possibilités fascinantes.
Exemple 26.11. Tableaux imbriqués et références indirectes
#!/bin/bash # embedded-arrays.sh # Tableaux intégrés et références indirectes. # Script de Dennis Leeuw. # Utilisé avec sa permission. # Modifié par l'auteur du document. TABLEAU1=( VAR1_1=valeur11 VAR1_2=valeur12 VAR1_3=valeur13 ) TABLEAU2=( VARIABLE="test" CHAINE="VAR1=valeur1 VAR2=valeur2 VAR3=valeur3" TABLEAU21=${TABLEAU1[*]} ) # TABLEAU1 intégré dans ce deuxième tableau. function affiche () { OLD_IFS="$IFS" IFS=$'\n' # Pour afficher chaque élément du tableau #+ sur une ligne séparée. TEST1="TABLEAU2[*]" local ${!TEST1} # Voir ce que se passe si vous supprimez cette ligne. # Référence indirecte. # Ceci rend accessible les composants de $TEST1 à cette fonction. # Voyons où nous en sommes arrivés. echo echo "\$TEST1 = $TEST1" # Simplement le nom de la variable. echo; echo echo "{\$TEST1} = ${!TEST1}" # Contenu de la variable. # C'est ce que fait une référence #+ indirecte. echo echo "-------------------------------------------"; echo echo # Affiche la variable echo "Variable VARIABLE : $VARIABLE" # Affiche un élément de type chaîne IFS="$OLD_IFS" TEST2="CHAINE[*]" local ${!TEST2} # Référence indirecte (comme ci-dessus). echo "Élément chaîne VAR2 : $VAR2 à partir de CHAINE" # Affiche un élément du tableau TEST2="TABLEAU21[*]" local ${!TEST2} # Référence indirecte (comme ci-dessus). echo "Élément du tableau VAR1_1 : $VAR1_1 à partir de TABLEAU21" } affiche echo exit 0 # Comme l'indique l'auteur du script, #+ "vous pouvez facilement l'étendre pour créer des hashs nommés en bash." # Exercice (difficile) pour le lecteur : l'implémenter.
--
Les tableaux permettent l'implémentation d'une version script shell du Crible d'Ératosthene. Bien sûr, une application intensive en ressources de cette nature devrait être réellement écrite avec un langage compilé tel que le C. Il fonctionne très lentement en tant que script.
Exemple 26.12. Crible d'Ératosthene
#!/bin/bash # sieve.sh (ex68.sh) # Crible d'Ératosthene # Ancien algorithme pour trouver les nombres premiers. # Ceci s'exécute bien moins rapidement que le programme équivalent écrit en C. LIMITE_BASSE=1 # Commençant avec 1. LIMITE_HAUTE=1000 # Jusqu'à 1000. # (Vous pouvez augmenter cette valeur... si vous avez du temps devant vous.) PREMIER=1 NON_PREMIER=0 let DIVISE=LIMITE_HAUTE/2 # Optimisation : # Nécessaire pour tester les nombres à mi-chemin de la limite supérieure (pourquoi ?). declare -a Premiers # Premiers[] est un tableau. initialise () { # Initialise le tableau. i=$LIMITE_BASSE until [ "$i" -gt "$LIMITE_HAUTE" ] do Premiers[i]=$PREMIER let "i += 1" done # Assume que tous les membres du tableau sont coupables (premiers) avant d'être # reconnus innocent. } affiche_premiers () { # Affiche les membres du tableau Premiers[] indiqués comme premiers. i=$LIMITE_BASSE until [ "$i" -gt "$LIMITE_HAUTE" ] do if [ "${Premiers[i]}" -eq "$PREMIER" ] then printf "%8d" $i # 8 espaces par nombre rend l'affichage joli, avec colonne. fi let "i += 1" done } examine () # Examine minutieusement les non premiers. { let i=$LIMITE_BASSE+1 # Nous savons que 1 est premier, donc commençons avec 2. until [ "$i" -gt "$LIMITE_HAUTE" ] do if [ "${Premiers[i]}" -eq "$PREMIER" ] # Ne nous embêtons pas à examiner les nombres déjà examinés (indiqués comme #+ non premiers). then t=$i while [ "$t" -le "$LIMITE_HAUTE" ] do let "t += $i " Premiers[t]=$NON_PREMIER # Indiqué comme non premier tous les multiples. done fi let "i += 1" done } # ========================================================= # main () # Appeler les fonctions séquentiellement. initialise examine affiche_premiers # C'est ce qu'ils appelent de la programmation structurée. # ========================================================= echo exit 0 # --------------------------------------------------------------------------- # # Le code ci-dessous ne sera pas exécuté à cause du exit ci-dessus. # Cette version améliorée de Sieve, par Stephane Chazelas, # s'exécute un peu plus rapidement. # Doit être appelé avec un argument en ligne de commande (limite des premiers). LIMITE_HAUTE=$1 # À partir de la ligne de commande. let DIVISE=LIMITE_HAUTE/2 # Mi-chemin du nombre max. Premiers=( '' $(seq $LIMITE_HAUTE) ) i=1 until (( ( i += 1 ) > DIVISE )) # A besoin de vérifier à mi-chemin. do if [[ -n $Premiers[i] ]] then t=$i until (( ( t += i ) > LIMITE_HAUTE )) do Premiers[t]= done fi done echo ${Premiers[*]} exit $?
Exemple 26.13. Crible d'Ératosthene, optimisé
#!/bin/bash # Optimized Sieve of Eratosthenes # Script by Jared Martin, with very minor changes by ABS Guide author. # Used in ABS Guide with permission (thanks!). # Based on script in Advanced Bash Scripting Guide. # http://tldp.org/LDP/abs/html/arrays.html#PRIMES0 (ex68.sh). # http://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf (reference) # Check results against http://primes.utm.edu/lists/small/1000.txt # Necessary but not sufficient would be, e.g., # (($(sieve 7919 | wc -w) == 1000)) && echo "7919 is the 1000th prime" UPPER_LIMIT=${1:?"Need an upper limit of primes to search."} Primes=( '' $(seq ${UPPER_LIMIT}) ) typeset -i i t Primes[i=1]='' # 1 is not a prime. until (( ( i += 1 ) > (${UPPER_LIMIT}/i) )) # Need check only ith-way. do # Why? if ((${Primes[t=i*(i-1), i]})) # Obscure, but instructive, use of numeric eval in subscript. then until (( ( t += i ) > ${UPPER_LIMIT} )) do Primes[t]=; done fi done # echo ${Primes[*]} echo # Change to original script for pretty-printing (80-col. display). printf "%8d" ${Primes[*]} echo; echo exit $?
Comparez ces générateurs de nombres premiers basés sur les tableaux avec un autre ne les utilisant pas, l'Exemple A.16, « primes: Générer des nombres premiers en utilisant l'opérateur modulo ».
--
Les tableaux tendent eux-même à émuler des structures de données pour lesquelles Bash n'a pas de support natif.
Exemple 26.14. Émuler une pile
#!/bin/bash # stack.sh : simulation d'une pile place-down # Similaire à la pile du CPU, une pile "place-down" enregistre les éléments #+ séquentiellement mais les récupère en ordre inverse, le dernier entré étant #+ le premier sorti. BP=100 # Pointeur de base du tableau de la pile. # Commence à l'élément 100. SP=$BP # Pointeur de la pile. # Initialisé à la base (le bas) de la pile. Donnees= # Contenu de l'emplacement de la pile. # Doit être une variable globale à cause de la limitation #+ sur l'échelle de retour de la fonction. declare -a pile place() # Place un élément dans la pile. { if [ -z "$1" ] # Rien à y mettre ? then return fi let "SP -= 1" # Déplace le pointeur de pile. pile[$SP]=$1 return } recupere() # Récupère un élément de la pile. { Donnees= # Vide l'élément. if [ "$SP" -eq "$BP" ] # Pile vide ? then return fi # Ceci empêche aussi SP de dépasser 100, #+ donc de dépasser la capacité du tampon. Donnees=${pile[$SP]} let "SP += 1" # Déplace le pointeur de pile. return } rapport_d_etat() # Recherche ce qui se passe { echo "-------------------------------------" echo "RAPPORT" echo "Pointeur de la pile = $SP" echo "\""$Donnees"\" juste récupéré de la pile." echo "-------------------------------------" echo } # ======================================================= # Maintenant, amusons-nous... echo # Voyons si nous pouvons récupérer quelque chose d'une pile vide. recupere rapport_d_etat echo place garbage recupere rapport_d_etat # Garbage in, garbage out. valeur1=23; place $valeur1 valeur2=skidoo; place $valeur2 valeur3=FINAL; place $valeur3 recupere # FINAL rapport_d_etat recupere # skidoo rapport_d_etat recupere # 23 rapport_d_etat # dernier entré, premier sorti ! # Remarquez comment le pointeur de pile décrémente à chaque insertion et #+ incrémente à chaque récupération. echo exit 0 # ======================================================= # Exercices : # ---------- # 1) Modifier la fonction "place()" pour permettre le placement de plusieurs # + éléments sur la pile en un seul appel. # 2) Modifier la fonction "recupere()" pour récupérer plusieurs éléments de la # + pile en un seul appel de la fonction. # 3) Ajouter une vérification des erreurs aux fonctions critiques. # C'est-à-dire, retournez un code d'erreur # + dépendant de la réussite ou de l'échec de l'opération, # + et réagissez en effectuant les actions appropriées. # 4) En utilisant ce script comme base, écrire une calculatrice 4 fonctions # + basée sur une pile.
--
Des manipulations amusantes de tableaux pourraient nécessiter des variables intermédiaires. Pour des projets le nécessitant, considérez encore une fois l'utilisation d'un langage de programmation plus puissant comme Perl ou C.
Exemple 26.15. Application complexe des tableaux Exploration d'une étrange série mathématique
#!/bin/bash # Les célèbres "Q-series" de Douglas Hofstadter : # Q(1) = Q(2) = 1 # Q(n) = Q(n - Q(n-1)) + Q(n - Q(n-2)), pour n>2 # C'est une série chaotique d'entiers avec un comportement étrange et non #+ prévisible. # Les 20 premiers termes de la série étaient : # 1 1 2 3 3 4 5 5 6 6 6 8 8 8 10 9 10 11 11 12 # Voir le livre d'Hofstadter, _Goedel, Escher, Bach: An Eternal Golden Braid_, #+ p. 137, ff. LIMITE=100 # Nombre de termes à calculer. LONGUEURLIGNE=20 # Nombre de termes à afficher par ligne. Q[1]=1 # Les deux premiers termes d'une série sont 1. Q[2]=1 echo echo "Q-series [$LIMITE termes] :" echo -n "${Q[1]} " # Affiche les deux premiers termes. echo -n "${Q[2]} " for ((n=3; n <= $LIMITE; n++)) # Conditions de boucle style C. do # Q[n] = Q[n - Q[n-1]] + Q[n - Q[n-2]] for n>2 # Nécessaire de casser l'expression en des termes intermédiaires #+ car Bash ne gère pas très bien l'arithmétique des tableaux complexes. let "n1 = $n - 1" # n-1 let "n2 = $n - 2" # n-2 t0=`expr $n - ${Q[n1]}` # n - Q[n-1] t1=`expr $n - ${Q[n2]}` # n - Q[n-2] T0=${Q[t0]} # Q[n - Q[n-1]] T1=${Q[t1]} # Q[n - Q[n-2]] Q[n]=`expr $T0 + $T1` # Q[n - Q[n-1]] + Q[n - Q[n-2]] echo -n "${Q[n]} " if [ `expr $n % $LONGUEURLIGNE` -eq 0 ] # Formate la sortie. then # ^ Opérateur modulo echo # Retour chariot pour des ensembles plus jolis. fi done echo exit 0 # C'est une implémentation itérative de la Q-series. # L'implémentation récursive plus intuitive est laissée comme exercice. # Attention : calculer cette série récursivement prend BEAUCOUP de temps.
--
Bash supporte uniquement les tableaux à une dimension. Néanmoins, une petite astuce permet de simuler des tableaux à plusieurs dimensions.
Exemple 26.16. Simuler un tableau à deux dimensions, puis son test
#!/bin/bash # twodim.sh : Simuler un tableau à deux dimensions. # Un tableau à une dimension consiste en une seule ligne. # Un tableau à deux dimensions stocke les lignes séquentiellement. Lignes=5 Colonnes=5 # Tableau de 5 sur 5. declare -a alpha # char alpha [Lignes] [Colonnes]; # Déclaration inutile. Pourquoi ? charge_alpha () { local rc=0 local index for i in A B C D E F G H I J K L M N O P Q R S T U V W X Y do # Utilisez des symbôles différents si vous le souhaitez. local ligne=`expr $rc / $Colonnes` local colonne=`expr $rc % $Lignes` let "index = $ligne * $Lignes + $colonne" alpha[$index]=$i # alpha[$ligne][$colonne] let "rc += 1" done # Un peu plus simple #+ declare -a alpha=( A B C D E F G H I J K L M N O P Q R S T U V W X Y ) #+ mais il manque néanmoins le "bon goût" d'un tableau à deux dimensions. } affiche_alpha () { local ligne=0 local index echo while [ "$ligne" -lt "$Lignes" ] # Affiche dans l'ordre des lignes - do # les colonnes varient # tant que ligne (la boucle externe) reste # identique local colonne=0 echo -n " " while [ "$colonne" -lt "$Colonnes" ] do let "index = $ligne * $Lignes + $colonne" echo -n "${alpha[index]} " # alpha[$ligne][$colonne] let "colonne += 1" done let "ligne += 1" echo done # Un équivalent plus simple serait # echo ${alpha[*]} | xargs -n $Colonnes echo } filtrer () # Filtrer les index négatifs du tableau. { echo -n " " # Apporte le tilt. # Expliquez comment. if [[ "$1" -ge 0 && "$1" -lt "$Lignes" && "$2" -ge 0 && "$2" -lt "$Colonnes" ]] then let "index = $1 * $Lignes + $2" # Maintenant, l'affiche après rotation. echo -n " ${alpha[index]}" # alpha[$ligne][$colonne] fi } rotate () # Bascule le tableau de 45 degrés { #+ (le "balance" sur le côté gauche en bas). local ligne local colonne for (( ligne = Lignes; ligne > -Lignes; ligne-- )) do # Traverse le tableau en sens inverse. Pourquoi ? for (( colonne = 0; colonne < Colonnes; colonne++ )) do if [ "$ligne" -ge 0 ] then let "t1 = $colonne - $ligne" let "t2 = $colonne" else let "t1 = $colonne" let "t2 = $colonne + $ligne" fi filtrer $t1 $t2 # Filtre les index négatifs du tableau. # Que se passe-t'il si vous ne le faites pas ? done echo; echo done # Rotation du tableau inspirée par les exemples (pp. 143-146) de #+ "Advanced C Programming on the IBM PC", par Herbert Mayer #+ (voir bibliographie). # Ceci ne fait que montrer que ce qui est fait en C peut aussi être fait avec #+ des scripts shell. } #----- Maintenant, que le spectacle commence. --------# charge_alpha # Charge le tableau. affiche_alpha # L'affiche. rotate # Le fait basculer sur 45 degrés dans le sens contraire des # aiguilles d'une montre. #-----------------------------------------------------# exit 0 # C'est une simulation assez peu satisfaisante. # # Exercices : # ---------- # 1) Réécrire le chargement du tableau et les fonctions d'affichage # d'une façon plus intuitive et élégante. # # 2) Comprendre comment les fonctions de rotation fonctionnent. # Astuce : pensez aux implications de l'indexage arrière du tableau. # # 3) Réécrire ce script pour gérer un tableau non carré, tel qu'un 6 sur 4. # Essayez de minimiser la distorsion lorsque le tableau subit une rotation.
Un tableau à deux dimensions est essentiellement équivalent à un tableau à une seule dimension mais avec des modes d'adressage supplémentaires pour les références et les manipulations d'éléments individuels par la position de la ligne et de la colonne.
Pour un exemple encore plus élaboré de simulation d'un tableau à deux dimensions, voir l'Exemple A.10, « « life : Jeu de la Vie » ».
Pour des scripts intéressants utilisant les tableaux, voir :