-
BASH > Les tableaux
Déclarer un tableau
declare -a TABdé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]=valaffectation du premier enregistrement de tabtab[5]=valaffectation du sixième enregistrement de tabtab[${#tab[@]}]=valaffectation de l’élément suivant le dernier indice de tabSupprimer dans un tableau
Supprimer tous les éléments du tableau TAB :
unset TABSupprimer 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$tabcontenu 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 tabChercher 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 < fichierou
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[@]}" )ouarray2="${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 espacesLors 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$INla chaine à couperTABle 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)