Aller au contenu

Administration Unix - CM séance 09

9. Approfondissements du langage bash (suite)

9.4. Commande declare

La commande interne declare (voir : help declare) permet de

  • demander au shell des informations sur les variables et les fonctions (on appelle cette capacité l'introspection) ;
  • mémoriser des attributs sur des variables ou fonctions (variable exportée, entière, liste, read-only...).

9.4.1. Introspection

$ toto="bonjour"
$ declare -p toto
declare -- toto="bonjour"           # -- signifie fin des options

$ export toto
$ declare -p toto
declare -x toto="bonjour"           # -x = attribut eXport

En fait, la commande declare affiche ce qu'il faudrait taper pour créer directement la variable :

$ declare -- titi="coucou"
$ echo "$titi"
coucou

$ declare -x tutu="byebye"
$ printenv tutu
byebye

On peut demander à declare la liste des variables :

$ declare -p | tail -n 3
declare -- titi="coucou"
declare -x toto="bonjour"
declare -x tutu="byebye"

Ceci permet d'enregistrer toutes les variables :

$ declare -p >| sauvegarde.txt

et de les réutiliser plus tard :

$ source sauvegarde.txt

Grâce à declare, on peut demander des informations sur des fonctions :

$ bingo() { echo "Vous avez gagné 1 million !" ;}

$ declare -f bingo              # affiche le nom et le corps de la fonction
bingo () 
{ 
    echo "Vous avez gagné 1 million !"
}

$ declare -F bingo              # affiche uniquement le nom si c'est 1 fct
bingo
$ declare -F | tail -n 10       # affiche toutes les fonctions
...
declare -f bingo
...

On peut aussi exporter une fonction, avec export -f ou declare -f -x.

9.4.2. Attributs

La commande declare permet d'activer une conversion automatique :

declare -u variable         # convertit en majuscule lors de l'affectation
declare -l variable         # idem en minuscule

Pour défaire, rappeler declare avec +u ou +l.

$ declare -u paf
$ paf=boum
$ echo $paf
BOUM

La commande declare permet de donner un "type" à une variable :

  • type entier :
$ n=5
$ declare -p n
declare -- n="5"            # variable ordinaire (chaîne de caractères)

$ n+=1                      # à quoi sert += ?
$ declare -p n
declare -- n="51"           # += a fait la concaténation : n="${n}1"

$ declare -i n              # on donne le type entier (Integer)
$ n+=1
$ declare -p n
declare -i n="52"           # += a fait l'addition : ((n+=1))

$ declare +i n              # pour enlever un attribut on utilise +
$ n+=1
$ declare -p n
declare -- n="521"          # c'est de nouveau une chaîne ⟶ concaténation
$ declare -a liste          # tableau indexé
$ declare -A dict           # tableau associatif

La commande declare permet aussi de créer une référence sur un nom de variable avec l'attribut nameref (c'est l'option -n) :

$ a=flip
$ declare -n b=a                # crée une référence b sur a
$ echo $b
flip
$ a=flop
$ echo $b
flop
$ b=flup
$ echo $a
flup

On peut obtenir le nom de la variable référencée :

$ echo ${!b}                    # "déréférence" ou "indirection"
a

On peut créer une référence vers une variable de tout type (y compris les tableaux indexés et associatifs).

À noter :

  • dans une fonction, on peut déclarer une variable locale de tout type, y compris une référence, en utilisant avec local les mêmes options que declare :

    local [option] nom[=valeur] ...
    
    par exemple :

    $ echanger() # a b
    {
        local -n a=$1 b=$2          # a et b sont des références
        local tmp
        tmp=$a ; a=$b; b=$tmp
    }
    
    $ c=ga ; d=bu
    $ echanger c d                  # on passe le nom des variables
    $ echo $c $d
    bu ga                           # ça les a bien échangé
    

    ⚠  ça ne marche que si les variables référencées sont accessibles (donc avec des noms différents).

  • on peut détruire une variable ou une fonction avec unset :

    unset variable
    unset -f fonction
    unset -n référence
    

9.5. Les tableaux

Caractéristiques :

  • tableau à 1 dimension, de taille illimitée ;
  • chaque case est une chaîne de caractère, de taille illimitée (on ne peut donc pas faire de tableau de tableaux).

bash gère 2 sortes de tableaux :

9.5.1. Les tableaux indexés

Particularités :

  • cases indexées par des entiers
  • tableau creux : les cases peuvent être non contigues
  • peuvent être utilisés comme des listes
  • déclaration optionnelle par : declare -a
declare -a tab              # crée un tableau tab
declare -a tab[$n]          # idem, taille ignorée

tab[$i]=valeur              # affectation, et création automatique de tab
${tab[$i]}                  # substitution par la valeur, "" si non affectée

Syntaxe : dans la substitution, les {} sont obligatoires :

$ t[0]=ga ; t[1]=bu
$ echo $t                   # incorrect
ga
$ echo $t[1]                # incorrect
ga[1]
$ echo ${t[1]}              # ok
bu

Expansion arithmétique de l'indice entre [] : même syntaxe que (()), et on peut omettre les $ :

$ t[0]=ga ; t[1]=bu ; t[2]=zo ; t[3]=meu
$ i=1; echo "${t[i+1]}"
zo

On peut exprimer les éléments d'un tableau par une liste :

tab=()                          # vide le tableau
tab=(val1 val2 .. valn)         # écrase le tableau, indices 0..n-1
tab=([2]=val1 [5]=val2 ..)      # idem, pour les indices donnés
tab+=(valx ..)                  # concatène la liste à la fin de tab

Exemple :

$ t=(ba bu)
$ t+=(zo meu)
$ declare -p t
declare -a t='([0]="ba" [1]="bu" [2]="zo" [3]="meu")'

Récupération de la liste des valeurs : ${tab[*]} ou "${tab[@]}"
⟶ même usage que $* et "$@", pour itérer avec un for.

$ t=(ga bu zo meu)
$ echo "${t[1]}"
bu
$ echo "${t[*]}"
ga bu zo meu
$ echo "${t[*]:2}"
zo meu
$ echo "${t[*]:1:2}"
bu zo

$ notes=(do "ré mi" fa)
$ for son in ${notes[*]} ; do echo "$son" ; done
do
ré
mi
fa
$ for son in "${notes[@]}" ; do echo "$son" ; done
do
ré mi
fa

Taille :

${#tab[i]}              # longueur de la case i
${#tab[*]}              # nombre de cases affectées

$ t=([2]=ga [8]=bu [5]=zo)
$ echo "${#t[*]}"
3

⚠  dans un tableau creux, taille du tableau != indice+1 du dernier élément

Liste des indices : ${!tab[*]}

$ t=([2]=ga [8]=bu [5]=zo)
$ echo ${!t[*]}
2 5 8

Boucler sur les indices d'un tableau creux :

$ for i in ${!t[*]}; do
     echo "t[$i] = ${t[i]}"
  done
t[2] = ga
t[5] = zo
t[8] = bu

Compléments :

unset tab           # suppression du tableau tab
unset tab[i]        # suppression de la case i

local -a tab        # déclaration locale d'un tableau

read -a tab         # lecture de l'entrée standard, découpe en mots,
                    # mémorisation des mots dans le tableau

args=("$@")         # mémorise les arguments dans un tableau

b=("${a[@]}")       # recopie le tableau a en le tassant

c=("${a[@]}" "${b[@]}")     # concatène 2 tableaux en les tassant

9.5.2. Les tableaux associatifs

Les tableaux associatifs sont encore appelés hash table ou dictionnaire.

Permet de représenter des données structurées, des tables de bases de données... ⟶ outil très puissant (Javascript, Php, Python, ...)

Particularités :

  • cases indexées par un mot clé
  • déclaration obligatoire par : declare -A
  • même syntaxe que pour les tableaux indexés

Exemple :

$ declare -A dico
$ dico=([lundi]=poisson [mardi]=viande)

$ echo ${dico[mardi]}
viande

$ echo ${!dico[*]}
lundi mardi                 # clés

$ echo ${dico[*]}
poisson viande              # valeurs

$ for cle in "${!dico[@]}"; do
      echo "dico[$cle] = '${dico[$cle]}'"
  done
dico[lundi] = 'poisson'
dico[mardi] = 'viande'

9.5.3. Passage de tableaux par référence

Possible en déclarant une référence de nom (nameref) avec declare -n ou local -n (depuis la version 4.3 de bash en 2014).

afficher_case () # tableau cle
{
    local -n tab="$1"           # par référence
    local cle="$2"
    echo "${tab[$cle]}"
}
$ shadoks=(ga bu zo)                                    # tableau indexé
$ afficher_case shadoks 1
bu

$ declare -A menu=([lundi]=poisson [mardi]=viande)      # tableau associatif
$ afficher_case menu mardi
viande
modifier_case () # tableau cle valeur
{
    local -n tab="$1"           # par référence
    local cle="$2" val="$3"
    tab[$cle]="$val"
}
$ modifier_case shadoks 3 meu
$ declare -p shadoks
declare -a shadoks='([0]="" [1]="bu" [2]="zo" [3]="meu")'

$ modifier_case menu jeudi œuf
$ declare -p menu
declare -A menu='([lundi]="poisson" [jeudi]="œuf" [mardi]="viande" )'

⚠  le nom du tableau passé en référence doit être différent du nom local :

$ tab=()
$ afficher_case tab 0
bash: avertissement : tab: circular name reference