• BASH > Les tableaux

      Déclarer un tableau

      declare -a TAB

       

      déclarer et remplir le tableau d’un seul coup :

      declare -a TAB=(élement1 élement2 élement3)

       

      entre guillemets si les éléments contiennent des espaces :

      declare -a TAB=("élement 1" "élement 2"  "élement 3")

      Ajouter un élément au tableau

      tab[0]=val affectation du premier enregistrement de tab

      tab[5]=val affectation du sixième enregistrement de tab

      tab[${#tab[@]}]=val affectation de l’élément suivant le dernier indice de tab

      Supprimer dans un tableau

      Supprimer tous les éléments du tableau TAB : unset TAB

       

      Supprimer le 1er élément de TAB : unset TAB[0]

       

      Supprimer le 5ème élément de TAB : unset TAB[4]

       

      Supprimer tous les éléments commençant par t :

      declare -a tab1=('un' 'deux' 'trois' 'toto' 'foobar');
      declare -a tab2=( ${tab1[@]/t*/} )

      Contenu du tableau

      NOTE : Les accolades sont indispensables pour ne pas interpréter les crochets [ ] comme chaine de caractères !!!

       

      Nombre d’éléments dans un tableau

      ${#tab[*]} ou ${#tab[@]}


      afficher un élément

      ${tab[0]} ou $tab contenu du premier enregistrement de tab


      ${tab[11]} contenu du douzième enregistrement de tab


      ${tab[*]} ensemble des enregistrements de tab


      Dernier élément : ${tab[-1]} ou  $tab[@]: -1:1}

       

      Afficher le 4ème élément et le suivant : echo "${tab[@]:3:2}"

       

      Afficher les 4 premières lettres du 3ème élément : echo ${tab[2]:0:4}

       

      Afficher tous les éléments - Parcourir un tableau

      for elem in "${tab[@]}" ; do
         echo "$elem"
      done

       

      ou, avec les index des éléments :

       

      for index in "${!tab[@]}" ; do
         echo "$index = ${tab[index]}"
      done

      longueur d’un élément

      ${#tab[11]} longueur du douzième enregistrement de tab

      Chercher un élément

      Chercher un élément dans le tableau :

      VAR="toto"
      for BLA in "${tab[@]}"; do
          [[ "$VAR" == "$BLA" ]] && echo "$VAR présent dans tab"
      done
      

      connaitre l’index d’un élément

      for i in "${!tab[@]}"; do
         if [[ "${tab[$i]}" = "${value}" ]]; then
            echo "${i}";
         fi
      done

      Copier un tableau

      Copier tout le tab1 dans tab2 : tab2=("${tab1[@]}")

       

      Concaténer tab1 et tab2 dans tab3 : tab3=("${tab1[@]}" "${tab2[@]}")

       

      Convertir

      Transformer un fichier en tableau

      mapfile -t monTableau < fichier

       

      ou

       

      monTableau=( `cat "fichier" `)

      Permet de transposer chaque ligne du fichier dans un indice de monTableau

      Transformer une chaine en tableau

      Pour convertir la chaine $chaine en tableau dont les éléments sont séparer par une virgule :

      IFS=',' read -r -a tab <<< "$chaine"

       

      Par défaut, l’espace est l’élément séparateur :

      read -r -a tab <<< "$chaine"

      ${foo[@]} vs. "${foo[@]}"

      Si les éléments du tableau contiennent des espaces et séparés par des espaces !!!, il faut utiliser les doubles quotes pour traiter chaque élément comme élément séparé :

       

      foo=("le premier" "le second")
      for i in "${foo[@]}" ; do
         echo $i
      done
      renvoie :
      le premier
      le second

       

      Contrairement à :

       

      foo=("le premier" "le second")
      for i in ${foo[@]} ; do
         echo $i
      done

      renvoie :

      le
      premier
      le
      second

      # Sum of two array variables assigned to third

      tab[i]=`expr ${tab[j]} + ${tab[k]}`

       

      # array_name=([xx]=XXX [yy]=YYY …)

      tab=([17]=seventeen [24]=twenty-four)

       

      As we have seen, a convenient way of initializing an entire array is the array=( element1 element2 ... elementN ) notation.

      tab_caracteres=( {A..Z} {a..z} {0..9} + / = )

       

      Bash permits array operations on variables, even if the variables are not explicitly declared as arrays.

      echo ${#tab[@]}  # elements in the array.

       

      Remplir un tableau :

      array2=( [0]="premier elmt" [1]="second elmt" [3]="fourth element" )

       

      OPERATIONS SUR LES ELEMENTS DU TABLEAUX

      arrayZ=( un deux trois quatre )
      
      # Trailing Substring Extraction
      echo ${arrayZ[@]:0}     # un deux trois quatre (tous les elements.
      
      echo ${arrayZ[@]:1}     # deux trois quatre (tous les elements suivant elmt[0].
      
      echo ${arrayZ[@]:1:2}   # deux trois (Only the two elements after element[0].
      
      # Removes shortest match from front of string(s).
      echo ${arrayZ[@]#f*r}   # one two three five five
      #               ^       # Applied to all elements of the array.
                              # Matches "four" and removes it.
      
      # Longest match from front of string(s)
      echo ${arrayZ[@]##t*e}  # one two four five five
      #               ^^      # Applied to all elements of the array.
                              # Matches "three" and removes it.
      
      # Shortest match from back of string(s)
      echo ${arrayZ[@]%h*e}   # one two t four five five
      #               ^       # Applied to all elements of the array.
                              # Matches "hree" and removes it.
      
      # Longest match from back of string(s)
      echo ${arrayZ[@]%%t*e}  # one two four five five
      #               ^^      # Applied to all elements of the array.
                              # Matches "three" and removes it.
      
      # Replace first occurrence of substring with replacement.
      echo ${arrayZ[@]/fiv/XYZ}   # one two three four XYZe XYZe
      #               ^           # Applied to all elements of the array.
      
      # Replace all occurrences of substring.
      echo ${arrayZ[@]//iv/YY}    # one two three four fYYe fYYe
                                  # Applied to all elements of the array.
      
      # Delete all occurrences of substring.
      # Not specifing a replacement defaults to 'delete' ...
      echo ${arrayZ[@]//fi/}      # one two three four ve ve
      #               ^^          # Applied to all elements of the array.
      
      # Replace front-end occurrences of substring.
      echo ${arrayZ[@]/#fi/XY}    # one two three four XYve XYve
      #                ^          # Applied to all elements of the array.
      
      # Replace back-end occurrences of substring.
      echo ${arrayZ[@]/%ve/ZZ}    # one two three four fiZZ fiZZ
      #                ^          # Applied to all elements of the array.
      
      echo ${arrayZ[@]/%o/XX}     # one twXX three four five five
      #                ^          # Why?
      
      replacement() {
          echo -n "!!!"
      }
      
      echo ${arrayZ[@]/%e/$(replacement)}
      #                ^  ^^^^^^^^^^^^^^
      # on!!! two thre!!! four fiv!!! fiv!!!
      # The stdout of replacement() is the replacement string.
      # Q.E.D: The replacement action is, in effect, an 'assignment.'
      
      echo "------------------------------------"
      
      #  Accessing the "for-each":
      echo ${arrayZ[@]//*/$(replacement optional_arguments)}
      #                ^^ ^^^^^^^^^^^^^
      # !!! !!! !!! !!! !!! !!!
      
      #  Now, if Bash would only pass the matched string
      #+ to the function being called . . .

      RAPPEL :

      $( … ) is command substitution.

      A function runs as a sub-process. A function writes its output (if echo-ed) to stdout. Assignment, in conjunction with "echo" and command substitution, can read a function’s stdout. The name[@] notation specifies a "for-each" operation.

       

      —> Loading the contents of a script into an array

      script_contents=( $(cat "$0") )  #  Stores contents of this script ($0)
                                       #+ in an array.
      
      for element in $(seq 0 $((${#script_contents[@]} - 1)))
        do                #  ${#script_contents[@]}
                          #+ gives number of elements in the array.
                          #
                          #  Question:
                          #  Why is  seq 0  necessary?
                          #  Try changing it to seq 1.
        echo -n "${script_contents[$element]}"
                          # List each field of this script on a single line.
      # echo -n "${script_contents[element]}" also works because of ${ ... }.
        echo -n " -- "    # Use " -- " as a field separator.
      done
      
      Example 27-6. Some special properties of arrays
      declare -a colors
      echo "Enter your favorite colors (separated from each other by a space)."
      read -a colors    # Enter at least 3 colors to demonstrate features below.
      #  Special option to 'read' command allowing assignment of elements in an array.
      
      element_count=${#colors[@]}
      #
      # The "@" variable allows word splitting within quotes
      # (extracts variables separated by whitespace).
      #
      # This corresponds to the behavior of "$@" and "$*"
      # in positional parameters. 
      
      index=0
      
      while [ "$index" -lt "$element_count" ] ; do   # List all the elements in the array.
        echo ${colors[$index]}
        #    ${colors[index]} also works because it's within ${ ... } brackets.
        let "index = $index + 1"
        # Or:
        #    ((index++))
      done
      # Each array element listed on a separate line.
      # If this is not desired, use  echo -n "${colors[$index]} "
      #
      # Doing it with a "for" loop instead:
      #   for i in "${colors[@]}"
      #   do
      #     echo "$i"
      #   done
      # (Thanks, S.C.)
      
      # Again, list all the elements in the array, but using a more elegant method.
        echo ${colors[@]}          # echo ${colors[*]} also works.
      
      # The "unset" command deletes elements of an array, or entire array.
      unset colors[1]              # Remove 2nd element of array.
                                   # Same effect as   colors[1]=
      echo  ${colors[@]}           # List array again, missing 2nd element.
      
      unset colors                 # Delete entire array.
                                   #  unset colors[*] and
                                   #+ unset colors[@] also work.
      echo; echo -n "Colors gone."
      echo ${colors[@]}            # List array again, now empty.

       

      —> empty arrays and empty elements

      # Adding an element to an array.
      array3[${#array3[*]}]="new2"
      
      # Above is the 'push' ...
      height=${#array2[@]}
      
      # The 'pop' is:
      unset array2[${#array2[@]}-1]   #  Arrays are zero-based,
      height=${#array2[@]}            #+ which means first element has index 0.
      
      # List only 2nd and 3rd elements of array0.
      from=1		    # Zero-based numbering.
      to=2
      array3=( ${array0[@]:1:2} )
      echo "Elements in array3:  ${array3[@]}"
      
      # Works like a string (array of characters).
      # Try some other "string" forms.
      
      # Replacement:
      array4=( ${array0[@]/second/2nd} )
      echo "Elements in array4:  ${array4[@]}"
      
      # Replace all matching wildcarded string.
      array5=( ${array0[@]//new?/old} )
      echo "Elements in array5:  ${array5[@]}"
      
      # Just when you are getting the feel for this . . .
      array6=( ${array0[@]#*new} )
      echo # This one might surprise you.
      echo "Elements in array6:  ${array6[@]}"
      
      array7=( ${array0[@]#new1} )
      echo # After array6 this should not be a surprise.
      echo "Elements in array7:  ${array7[@]}"
      
      # Which looks a lot like . . .
      array8=( ${array0[@]/new1/} )
      echo "Elements in array8:  ${array8[@]}"
      
      #  So what can one say about this?
      
      #  The string operations are performed on
      #+ each of the elements in var[@] in succession.
      #  Therefore : Bash supports string vector operations.
      #  If the result is a zero length string,
      #+ that element disappears in the resulting assignment.
      #  However, if the expansion is in quotes, the null elements remain.
      
      zap='new*'
      array9=( ${array0[@]/$zap/} )
      echo "Number of elements in array9:  ${#array9[@]}"
      array9=( "${array0[@]/$zap/}" )
      echo "Elements in array9:  ${array9[@]}"
      # This time the null elements remain.
      echo "Number of elements in array9:  ${#array9[@]}"
      
      # Just when you thought you were still in Kansas . . .
      array10=( ${array0[@]#$zap} )
      echo "Elements in array10:  ${array10[@]}"
      # But, the asterisk in zap won't be interpreted if quoted.
      array10=( ${array0[@]#"$zap"} )
      echo "Elements in array10:  ${array10[@]}"
      # Well, maybe we _are_ still in Kansas . . .
      # (Revisions to above code block by Nathan Coulter.)
      
      #  Compare array7 with array10.
      #  Compare array8 with array9.
      
      #  Reiterating: No such thing as soft quotes!
      #  Nathan Coulter explains:
      #  Pattern matching of 'word' in ${parameter#word} is done after
      #+ parameter expansion and *before* quote removal.
      #  In the normal case, pattern matching is done *after* quote removal.
      
      exit

       

      —> Différences entre ${array_name[@]} and ${array_name[*]} :

       

      Copier un tableau :

      array2=( "${array1[@]}" ) ou array2="${array1[@]}"

      Ne marche pas si le tableau comporte des éléments absents (des trous)

      transformer un fichier texte en tableau :

      fichier texte :

      1 a b c
      2 d e f

      array1=( `cat fichier`) # Loads contents of fichier into array1.

      array1=( `cat "$filename" | tr ‘\n’ ‘ ‘`) # change linefeeds in file to spaces.# Not necessary because Bash does word splitting, changing linefeeds to spaces.

      echo ${array1[@]} # List the array.

      1 a b c 2 d e fg

      # Each whitespace-separated "word" in the file has been assigned to an element of the array.

      element_count=${#array1[*]}

      echo $element_count # 8

      —> Liste des fichiers dans un tableau

      declare -a bigOne=( /dev/* ) # All the files in /dev . . .

      echo "Number of elements in array is ${#bigOne[@]}"

      --> Tri à bulle

      #!/bin/bash

      exchange()
      {
        # Swaps two members of the array.
        local temp=${Countries[$1]} #  Temporary storage for element getting swapped out.
        Countries[$1]=${Countries[$2]}
        Countries[$2]=$temp
      
        return
      }  
      
      declare -a Countries  #  Declare array, optional here since it's initialized below.
      
      Countries=(Netherlands Ukraine Zaire Turkey Russia Yemen Syria \
      Brazil Argentina Nicaragua Japan Mexico Venezuela Greece England \
      Israel Peru Canada Oman Denmark Wales France Kenya \
      Xanadu Qatar Liechtenstein Hungary)
      
      clear                      # Clear the screen to start with. 
      
      echo "0: ${Countries[*]}"  # List entire array at pass 0.
      
      number_of_elements=${#Countries[@]}
      let "comparisons = $number_of_elements - 1"
      
      count=1 # Pass number.
      
      while [ "$comparisons" -gt 0 ]          # Beginning of outer loop
      do
      
        index=0  # Reset index to start of array after each pass.
      
        while [ "$index" -lt "$comparisons" ] # Beginning of inner loop
        do
          if [ ${Countries[$index]} \> ${Countries[`expr $index + 1`]} ]
          #  If out of order...
          #  Recalling that \> is ASCII comparison operator
          #+ within single brackets.
      
          #  if [[ ${Countries[$index]} > ${Countries[`expr $index + 1`]} ]]
          #+ also works.
          then
            exchange $index `expr $index + 1`  # Swap.
          fi
          let "index += 1"  # Or,   index+=1   on Bash, ver. 3.1 or newer.
        done # End of inner loop
      
      # ----------------------------------------------------------------------
      # Paulo Marcel Coelho Aragao suggests for-loops as a simpler altenative.
      #
      # for (( last = $number_of_elements - 1 ; last > 0 ; last-- ))
      ##                     Fix by C.Y. Hunt          ^   (Thanks!)
      # do
      #     for (( i = 0 ; i < last ; i++ ))
      #     do
      #         [[ "${Countries[$i]}" > "${Countries[$((i+1))]}" ]] \
      #             && exchange $i $((i+1))
      #     done
      # done
      # ----------------------------------------------------------------------
      
      let "comparisons -= 1" #  Since "heaviest" element bubbles to bottom,
                             #+ we need do one less comparison each pass.
      
      echo
      echo "$count: ${Countries[@]}"  # Print resultant array at end of each pass.
      echo
      let "count += 1"                # Increment pass count.
      
      done                            # End of outer loop
                                      # All done.
      
      exit 0

      SÉPARATEUR STANDARD : $IFS

      $IFS (Internal Field Separator) définit le(s) caractère(s) utilisé(s) pour délimiter les mots dans une chaîne de caractères.

      Par défaut, le séparateur est <space><tab><newLine>

      Pour afficher $IFS : echo -n « $IFS » | cat -vTE

      Ré initialisation de l’IFS

      Pour ramener $IFS à sa valeur originelle (<espace><tabulation><nouvelle ligne>) :

       

      IFS=$' \t\n'
      Noms de fichiers avec espaces

      Lors du traitement de noms de fichiers avec espaces dans une boucle par exemple, le résultat n’est pas exactement celui attendu :

       

      mkdir IFS && cd !$
      
      touch "fichier "{1,2}
      
      ls -l
      
      -rw------- 1 fhh users 0 Feb 22 12:03 fichier 1
      -rw------- 1 fhh users 0 Feb 22 12:03 fichier 2
      
      for n in $(ls) ; do echo $n ; done
      
      fichier
      1
      fichier
      2

      la raison de ce retour est que le séparateur vaut par défaut espace, tabulation ou nouvelle ligne.

      Le comportement souhaité est obtenu en modifiant le séparateur par défaut (IFS) :

       

      IFS=$'\n'; for n in $(ls) ; do echo $n ; done
      
      fichier 1
      fichier 2

      Commande split/explode

      En modifiant IFS, nous pouvons découper une chaîne de caractères en fonction d’un motif donné.

      Par exemple pour découper les lignes de « /etc/passwd » nous pourrons utiliser le séparateur « : » :

       

      IFS=: ; while read login mdp uid gid gecos home shell ; do
         echo "${gecos:=undef} > login $login (home : $home, shell : $shell)" ;
      done < /etc/passwd
      
      root > login root (home : /root, shell : /bin/bash)
      bin > login bin (home : /bin, shell : /bin/false)
      undef > login mysql (home : /var/lib/mysql, shell : /bin/false)
      ...
      FHH > login fhh (home : /users/fhh, shell : /bin/bash)
      ...

      Note I : Dans cet exemple, si le « gecos » (nom complet) de l’utilisateur n’est pas défini, il est initialisé à undef.
      Note II : « while read login reste ; do echo $login ; done < /etc/passwd" n’affichera que les logins (premier champ)

      Impacte sur les arguments de scripts

      Deux variables permettent de retourner la liste des arguments passés à un script shell : $@ et $* .

      De prime abord, ces variables ne présentent pas de différences mais elles sont interprétées différemment si elles sont placées entre double quotes :

       

      cat myifs.sh
      #!/bin/bash
      IFS=:
      echo "Liste des arguments :"
      echo "\$@ sans double quotes > "$@ ;
      echo "\$@ avec double quotes > $@" ;
      echo "\$* sans double quotes > "$* ;
      echo "\$* avec double quotes > $*" ;
      
      ./myifs.sh a b cd efg
      
      Liste des arguments :
      $@ sans double quotes > a b cd efg
      $@ avec double quotes > a b cd efg
      $* sans double quotes > a b cd efg
      $* avec double quotes > a:b:cd:efg

      Placé entre double quotes $* utilise le séparateur standard pour retourner la liste des arguments passés au script.

      Commande join/implode

      En utilisant le comportement de « $* », nous pouvons coder une fonction destinée à joindre différents éléments en utilisant un séparateur choisi (comme le fait la fonction « join » en perl ou « implode » en php).

       

      implode () {
      	local IFS=$1 && shift ;
      	echo "$*";
      }

       

      qui donne dans le shell

      implode () {
      local IFS=$1 && shift ;
      echo "$*"
      }
      
      implode : a b cd efg
      a:b:cd:efg
      
      implode "" a b cd efg
      abcdefg

       

      Cette fonction offre un comportement cohérent pour le traitement de chaînes de caractères avec espaces :

      implode , $(ls)
      fichier,1,fichier,2,myifs.sh,repertoire,avec,espace,1,repertoire,avec,espace,2
      
      IFS=$'\n' ; implode , $(ls)
      fichier 1,fichier 2,myifs.sh,repertoire avec espace 1,repertoire avec espace 2

      TRANSFORMER UNE CHAÎNE EN TABLEAU

      En utilisant le IFS (Internal Field Separator).

       

      IFS=';' read -ra TAB <<< "$IN"
      for i in "${TAB[@]}"; do
         # process "$i"
      done

       

      ; élément utilisé comme séparateur

      $IN la chaine à couper

      TAB le nom du tableau ainsi créé

       

      Stuff for processing whole of $IN, each time one line of input separated by ;:

      while IFS=';' read -ra ADDR; do
         for i in "${ADDR[@]}"; do
            # process "$i
         done
      done <<< "$IN

       

      read everything at once without using a while loop: read -r -d ‘’ -a addr <<< "$in"

      The -d ‘’ is key here, it tells read not to stop at the first newline (which is the default -d) but to continue until EOF or a NULL byte (which only occur in binary data).

       

      Setting IFS sera remis par défaut après le read (car pas de ; entre IFS= et read)

       

 

Aucun commentaire

 

Laissez un commentaire