8. Opérations et sujets connexes

8.1. Les opérateurs

affectation

affectation de variable

Initialiser une variable, ou changer sa valeur

=

Opérateur d'affectation à tout faire, convenant à la fois pour les affectations arithmétiques pour les chaînes de caractères.

var=27
categorie=mineraux  # Espaces non autorisés après le "=".
[Attention]

Attention

Ne confondez pas l'« opérateur d'affectation = » avec l'opérateur de test =.

#    = comme opérateur de test

if [ "$chaine1" = "$chaine2" ]
then
   commande
fi

# if [ "X$chaine1" = "X$chaine2" ] est plus sûr,
# pour empêcher un message d'erreur si une des variables devait être vide
# (les caractères "X" postfixés se neutralisent).

opérateurs arithmétiques

+

plus

-

moins

*

multiplication

/

division

**

exponentielle

# Bash, version 2.02, introduit l'opérateur exponentielle "**".

let "z=5**3"    # 5 * 5 * 5
echo "z = $z"   # z = 125
%

modulo, ou mod (renvoie le reste de la division d'un entier)

bash$ expr 5 % 3
2
              

5/3 = 1, et il reste 2

Cet opérateur trouve son utilité, entre autres choses, dans la génération de nombres compris dans un intervalle donné (voir l'Exemple 9.11, « Générer des nombres aléatoires » et l'Exemple 9.15, « Lancement d'un seul dé avec RANDOM ») et pour le formatage de la sortie d'un programme (voir l'Exemple 27.15, « Application complexe des tableaux Exploration d'une étrange série mathématique » et l'Exemple A.6, « Séries de Collatz »). Il peut même être utilisé pour générer des nombres premiers (voir Exemple A.15, « Générer des nombres premiers en utilisant l'opérateur modulo »). D'une manière assez surprenante, l'opérateur Modulo est employé fréquemment en tant qu'astuce de calcul numérique.

Exemple 8.1. Le PGCD (plus grand diviseur commun)

#!/bin/bash
# gcd.sh: plus grand diviseur commun
#         Utilise l'algorithme d'Euclide

#  Le "plus grand diviseur commun" (PGCD) de deux entiers est l'entier
#  le plus grand à diviser les deux entiers sans reste.

#  L'algorithme d'Euclide utilise des divisions successives.
#  À chaque passe,
#+ dividende <---  diviseur
#+ diviseur  <---  reste
#+ jusqu'à ce qu'il reste 0.
#+ PGCD = le dividende de la dernière passe.
#
#  Pour une excellente discussion de l'algorithme d'Euclide, voir le site
#+ de Jim Loy, http://www.jimloy.com/number/euclids.htm.


# ------------------------------------------------------
# Vérification des arguments
ARGS=2
E_MAUVAISARGS=65

if [ $# -ne "$ARGS" ]
then
  echo "Usage: `basename $0` premier_nombre deuxieme_nombre"
  exit $E_MAUVAISARGS
fi
# ------------------------------------------------------


pgcd ()
{

                                #  Affectation arbitraire.
  dividende=$1                  #  Peu importe lequel est le plus
  diviseur=$2                   #+ grand.
                                #  Pourquoi ?

  reste=1                       #  Si une variable non initialisée est utilisée
                                #+ dans la boucle,
                                                #+ cela finit en un message d'erreur lors de
                                                #+ la première passe dans la boucle.

  until [ "$reste" -eq 0 ]
  do
    let "reste = $dividende % $diviseur"
    dividende=$diviseur          #  Maintenant, répétez avec les deux plus
                                 #+ petits nombres.
    diviseur=$reste
  done                           # Algorithme d'Euclide

}                                # Le dernier $dividende est le pgcd.


pgcd $1 $2

echo; echo "Le PGCD de $1 et de $2 est : $dividende"; echo


# Exercice :
# --------
#  Vérifier les arguments de la ligne de commande pour s'assurer que
#+ ce sont bien des entiers, sinon quitter le script avec une erreur
#+ appropriée.

exit 0

+=

plus-égal (incrémente une variable par une constante) [37]

let "var += 5" renvoie dans var sa propre valeur incrémentée de 5.

-=

moins-égal (décrémente une variable par une constante)

*=

multiplication-égal (multiplie une variable par une constante)

let "var *= 4" renvoie dans var sa propre valeur multipliée par 4.

/=

division-égal (divise une variable par une constante)

%=

modulo-égal (reste de la division de la variable par une constante)

On rencontre souvent les opérateurs arithmétiques dans les expressions expr ou let.

Exemple 8.2. Utiliser les opérations arithmétiques

#!/bin/bash
# Compter jusqu'à 11 de 10 façons différentes.

n=1; echo -n "$n "

let "n = $n + 1"   # let "n = n + 1"   fonctionne aussi.
echo -n "$n "

: $((n = $n + 1))
#  ":" nécessaire, sinon Bash essaie d'interpréter
#+ "$((n = $n + 1))" comme une commande.
echo -n "$n "

(( n = n + 1 ))
#  Une alternative plus simple que la méthode ci-dessus.
#  Merci à David Lombard pour cette indication.
echo -n "$n "

n=$(($n + 1))
echo -n "$n "

: $[ n = $n + 1 ]
#  ":" nécessaire, sinon Bash essaie d'interpréter
#+ "$[ n = $n + 1 ]" comme une commande.
# Fonctionne même si "n" a été initialisé comme chaîne de caractères.
echo -n "$n "

n=$[ $n + 1 ]
#  Fonctionne même si "n" a été initialisé comme chaîne de caractères.
#+ Syntaxe à éviter cette car elle est obsolète et non portable.
#  Merci à Stephane Chazelas.
echo -n "$n "

# Maintenant des opérateurs d'incrémentation style C.
# Merci à Frank Wang pout cette indication.

let "n++"          # let "++n"  fonctionne aussi.
echo -n "$n "

(( n++ ))          # (( ++n )  fonctionne aussi.
echo -n "$n "

: $(( n++ ))       # : $(( ++n )) fonctionne aussi.
echo -n "$n "

: $[ n++ ]         # : $[ ++n ]] fonctionne aussi.
echo -n "$n "

echo

exit 0

[Note]

Note

Les variables de type entier dans les anciennes versions de Bash étaient en réalité des entiers longs signés (32-bit) allant de -2147483648 à 2147483647. Une opération prenant une variable en dehors de ces limites fournissait un résultat erroné.

echo $BASH_VERSION   # 1.14

a=2147483646
echo "a = $a"      # a = 2147483646
let "a+=1"         # Incrémente "a".
echo "a = $a"      # a = 2147483647
let "a+=1"         # Incrémente encore "a", en dehors de la limite.
echo "a = $a"      # a = -2147483648
                   #      ERREUR (hors limites)
                   # +    et le bit le plus à gauche, le bit de signe,
                   # +    a été positionné, d'où le résultat négatif.
                   # +

À partir de la version 2.05b, Bash implémente les entiers sur 64 bits.

[Attention]

Attention

Bash ne comprend pas l'arithmétique à virgule flottante. Il traite les nombres contenant un point décimal comme des chaînes de caractères.

a=1.5

let "b = $a + 1.3"  # Erreur.
# t2.sh: let: b = 1.5 + 1.3: erreur de syntaxe dans l'expression
#                            (error token is ".5 + 1.3")

echo "b = $b"       # b=1

Utilisez bc dans les scripts qui ont besoin de calculs avec des virgules ou de fonctions mathématiques.

Les opérateurs bit à bit. Les opérateurs bit à bit sont rares dans les scripts shell. Leur utilisation principale semble être la manipulation et le test de valeurs lues à partir de ports ou de sockets. Le « renversement de bit » est plus intéressant pour les langages compilés, comme le C et le C++, qui fournissent un accès direct au matériel. Cependant, l'usage ingénieux par vladz des opérateurs bit à bit dans son script base64.sh (Exemple A.54, « Encoder/décoder le Base64 ») est à connaître.

opérateurs binaires

<<

décalage d'un bit à gauche (revient à multiplier par 2 à chaque décalage)

<<=

décalage à gauche-égale

let "var <<= 2" renvoie dans var sa propre valeur décalée à gauche de 2 bits (donc multipliée par 4)

>>

décalage d'un bit à droite (revient à diviser par 2 pour chaque position du décalage)

>>=

décalage à droite-égale (l'inverse de <<=)

&

ET binaire

&=

ET binaire-égale

|

OU binaire

|=

OU binaire-égale

~

NON binaire

^

XOR binaire

^=

XOR binaire-égale

opérateurs (booléens) logiques

!

NOT

if [ ! -f $NOMFICHIER ]
then
  ...
&&

ET

if [ $condition1 ] && [ $condition2 ]
# Identique à :  if [ $condition1 -a $condition2 ]
# Renvoie vrai si condition1 et condition2 sont vraies...

if [[ $condition1 && $condition2 ]]    # Fonctionne aussi.
# Notez que l'opérateur && n'est pas autorisé dans l'expression
#+ <emphasis>entre crochets</emphasis> de la syntaxe [ ... ].
[Note]

Note

Suivant le contexte, && peut aussi être utilisé dans une liste ET, pour concaténer des commandes.

||

OU

if [ $condition1 ] || [ $condition2 ]
# Identique à:  if [ $condition1 -o $condition2 ]
# Renvoie vrai si condition1 ou condition2 est vraie...

if [[ $condition1 || $condition2 ]]    # Fonctionne aussi.
# Notez que l'opérateur || n'est pas autorisé dans l'expression
#+ <emphasis>entre crochets</emphasis> de la syntaxe [ ... ].
[Note]

Note

Bash teste l'état de sortie de chacun des instructions liées par un opérateur logique.

Exemple 8.3. Tests de conditions composées avec utilisation de && et de ||

#!/bin/bash

a=24
b=47

if [ "$a" -eq 24 ] && [ "$b" -eq 47 ]
then
  echo "Le test #1 a réussi."
else
  echo "Le test #1 a échoué."
fi

# ERREUR:  if [ "$a" -eq 24 && "$b" -eq 47 ]
#          essaie d'exécuter ' [ "$a" -eq 24 '
#          et échoue à trouver le ']' correspondant.
#
# Note :   if [[ $a -eq 24 && $b -eq 24 ]]   fonctionne
#    Le test if avec doubles crochets est plus flexible que la version
#+   avec simples crochets.
#    (Le "&&" n'a pas le même sens à la ligne 17 qu'à la ligne 6).
#    Merci à Stephane Chazelas.


if [ "$a" -eq 98 ] || [ "$b" -eq 47 ]
then
  echo "Le test #2 a réussi."
else
  echo "Le test #2 a échoué."
fi


#  Les options -a et -o apportent une alternative au test de la
#+ condition composée.
#  Merci à Patrick Callahan pour avoir remarqué ceci.


if [ "$a" -eq 24 -a "$b" -eq 47 ]
then
  echo "Le test #3 a réussi."
else
  echo "Le test #3 a échoué."
fi


if [ "$a" -eq 98 -o "$b" -eq 47 ]
then
  echo "Le test #4 a réussi."
else
  echo "Le test #4 a échoué."
fi


a=rhino
b=crocodile
if [ "$a" = rhino ] && [ "$b" = crocodile ]
then
  echo "Le test #5 a réussi."
else
  echo "Le test #5 a échoué."
fi

exit 0

Les opérateurs && et || trouvent aussi leur utilité dans un contexte arithmétique.

bash$ echo $(( 1 && 2 )) $((3 && 0)) $((4 || 0)) $((0 || 0))
1 0 1 0
              

opérateurs divers

,

Opérateur virgule

L'opérateur virgule permet d'enchaîner deux ou plusieurs opérations arithmétiques. Toutes les opérations sont évaluées (avec de possibles effets de bord. [38])

let "t1 = ((5 + 3, 7 - 1, 15 - 4))"
echo "t1 = $t1"               ^^^^^^  # t1 = 11
# Ici t1 a pour valeur le résultat de la dernière opération. Pourquoi ?

let "t2 = ((a = 9, 15 / 3))"  # Initialise "a" et calcule "t2".
echo "t2 = $t2    a = $a"     # t2 = 5    a = 9

L'opérateur virgule trouve son utilité principalement dans les boucles for. Voir l'Exemple 11.12, « Une boucle for comme en C ».



[37] Dans un contexte différent, += opère la concaténation de chaînes de caractères. Ce qui est notamment utile pour modifier les variables d'environment.

[38] Les effets de bord sont, bien sûr, inattendus et le plus souvent indésirables.