12.8. Commandes mathématiques

<< Compter >>

factor

Décompose un entier en nombre premiers.

bash$ factor 27417
 27417: 3 13 19 37
 

bc

Bash ne peut traiter les calculs en virgule flottante et n'intègre pas certaines fonctions mathématiques importantes. Heureusement, bc est là pour nous sauver.

bc n'est pas simplement une calculatrice souple à précision arbitraire, elle offre aussi beaucoup de facilités disponibles dans un langage de programmation.

La syntaxe de bc ressemble vaguement à celle du C.

bc est devenu un outil UNIX assez puissant pour être utilisé via un tube et est manipulable depuis des scripts.

Ceci est un simple exemple utilisant bc pour calculer la valeur d'une variable. Il utilise lasubstitution de commande.

 variable=$(echo "OPTIONS; OPERATIONS" | bc)
 

Exemple 12-32. Paiement mensuel sur une hypothèque

#!/bin/bash
# monthlypmt.sh: Calculates monthly payment on a mortgage.


# This is a modification of code in the "mcalc" (mortgage calculator) package,
# by Jeff Schmidt and Mendel Cooper (yours truly, the author of this document).
#   http://www.ibiblio.org/pub/Linux/apps/financial/mcalc-1.6.tar.gz  [15k]

echo
echo "Given the principal, interest rate, and term of a mortgage,"
echo "calculate the monthly payment."

bottom=1.0

echo
echo -n "Enter principal (no commas) "
read principal
echo -n "Enter interest rate (percent) "  # If 12%, enter "12", not ".12".
read interest_r
echo -n "Enter term (months) "
read term


 interest_r=$(echo "scale=9; $interest_r/100.0" | bc) # Convert to decimal.
                 # "scale" determines how many decimal places.
  

 interest_rate=$(echo "scale=9; $interest_r/12 + 1.0" | bc)
 

 top=$(echo "scale=9; $principal*$interest_rate^$term" | bc)

 echo; echo "Please be patient. This may take a while."

 let "months = $term - 1"
# ==================================================================== 
 for ((x=$months; x > 0; x--))
 do
   bot=$(echo "scale=9; $interest_rate^$x" | bc)
   bottom=$(echo "scale=9; $bottom+$bot" | bc)
#  bottom = $(($bottom + $bot"))
 done
# -------------------------------------------------------------------- 
#  Rick Boivie pointed out a more efficient implementation
#+ of the above loop, which decreases computation time by 2/3.

# for ((x=1; x <= $months; x++))
# do
#   bottom=$(echo "scale=9; $bottom * $interest_rate + 1" | bc)
# done


#  And then he came up with an even more efficient alternative,
#+ one that cuts down the run time by about 95%!

# bottom=`{
#     echo "scale=9; bottom=$bottom; interest_rate=$interest_rate"
#     for ((x=1; x <= $months; x++))
#     do
#          echo 'bottom = bottom * interest_rate + 1'
#     done
#     echo 'bottom'
#     } | bc`       # Embeds a 'for loop' within command substitution.

# ==================================================================== 

 # let "payment = $top/$bottom"
 payment=$(echo "scale=2; $top/$bottom" | bc)
 # Use two decimal places for dollars and cents.
 
 echo
 echo "monthly payment = \$$payment"  # Echo a dollar sign in front of amount.
 echo


 exit 0

 # Exercises:
 #   1) Filter input to permit commas in principal amount.
 #   2) Filter input to permit interest to be entered as percent or decimal.
 #   3) If you are really ambitious,
 #      expand this script to print complete amortization tables.

Exemple 12-33. Conversion de base

:
##########################################################################
# Script shell:	base.sh - affiche un nombre en différentes bases (Bourne Shell)
# Auteur      :	Heiner Steven (heiner.steven@odn.de)
# Date        :	07-03-95
# Categorie   :	Desktop
# $Id: base.sh,v 1.4 2003/12/08 20:57:22 guillaume Exp $
##########################################################################
# Description
#
# Modifications
# 21-03-95 stv	correction d'une erreur arrivant avec 0xb comme entrée (0.2)
##########################################################################

# ==> Utilisé dans ce document avec la permission de l'auteur du script.
# ==> Commentaires ajoutés par l'auteur du document.

NOARGS=65
PN=`basename "$0"`			           # Nom du programme
VER=`echo '$Revision: 1.4 $' | cut -d' ' -f2`  # ==> VER=1.2

Usage () {
    echo "$PN - Affiche un nombre en différentes bases, $VER (stv '95)
usage: $PN [nombre ...]

Si aucun nombre n'est donné, les nombres sont lus depuis l'entrée standard.
Un nombre peut être
    binaire (base 2)		commençant avec 0b (i.e. 0b1100)
    octal (base 8)		commençant avec 0  (i.e. 014)
    hexadécimal (base 16)	commençant avec 0x (i.e. 0xc)
    décimal			autrement (c'est-à-dire 12)" >&2
    exit $NOARGS 
}   # ==> Fonction pour afficher le message d'usage.

Msg () {
    for i   # ==> [liste] manquante.
    do echo "$PN: $i" >&2
    done
}

Fatal () { Msg "$@"; exit 66; }

AfficheBases () {
    # Détermine la base du nombre
    for i      # ==> [liste] manquante...
    do         # ==> donc opère avec le(s) argument(s) en ligne de commande.
	case "$i" in
	    0b*)		ibase=2;;	# binaire
	    0x*|[a-f]*|[A-F]*)	ibase=16;;	# hexadécimal
	    0*)			ibase=8;;	# octal
	    [1-9]*)		ibase=10;;	# décimal
	    *)
		Msg "nombre illégal $i - ignoré"
		continue;;
	esac

	#  Suppression du préfixe, conversion des nombres hexadécimaux en
	#+ majuscule (bc a besoin de cela)
	number=`echo "$i" | sed -e 's:^0[bBxX]::' | tr '[a-f]' '[A-F]'`
	# ==> Utilise ":" comme séparateur sed, plutôt que "/".

	# Conversion des nombres en décimal
	dec=`echo "ibase=$ibase; $number" | bc`  # ==> 'bc' est un utilitaire de
	                                         #+ calcul.
	case "$dec" in
	    [0-9]*)	;;			 # nombre ok
	    *)		continue;;		 # erreur: ignore
	esac

	# Affiche toutes les conversions sur une ligne.
	# ==> le 'document en ligne' remplit la liste de commandes de 'bc'.
	echo `bc <<!
	    obase=16; "hex="; $dec
	    obase=10; "dec="; $dec
	    obase=8;  "oct="; $dec
	    obase=2;  "bin="; $dec
!
    ` | sed -e 's: :	:g'

    done
}

while [ $# -gt 0 ]
do
    case "$1" in
	--)	shift; break;;
	-h)	Usage;;                 # ==> Message d'aide.
	-*)	Usage;;
	*)	break;;			# premier nombre
    esac   #  ==> Plus de vérification d'erreur pour des entrées illégales
           #+ serait utile.
    shift
done

if [ $# -gt 0 ]
then
    AfficheBases "$@"
else					#  lit à partir de l'entrée standard
                                        #+ stdin
    while read ligne
    do
	PrintBases $ligne
    done
fi

Une autre façon d'utiliser bc serait d'utiliser des documents en ligne embarqués dans un bloc de substitution de commandes. Cette méthode est très intéressante lorsque le script passe un grand nombre de commandes à bc

variable=`bc >> CHAINE_LIMITE
 options
 instructions
 operations
 CHAINE_LIMITE
 `

 ...or...


 variable=$(bc >> CHAINE_LIMITE
   options
   instructions
   operations
   CHAINE_LIMITE
   )

Exemple 12-34. Une autre façon d'invoquer bc

#!/bin/bash
# Appelle 'bc' en utilisant la substitution de commandes
# en combinaison avec un 'document en ligne'.


var1=`bc << EOF
18.33 * 19.78
EOF
`
echo $var1       # 362.56


# La notation $( ... ) fonctionne aussi.
v1=23.53
v2=17.881
v3=83.501
v4=171.63

var2=$(bc << EOF
scale = 4
a = ( $v1 + $v2 )
b = ( $v3 * $v4 )
a * b + 15.35
EOF
)
echo $var2       # 593487.8452


var3=$(bc -l << EOF
scale = 9
s ( 1.7 )
EOF
)
# Renvoie le sinus de 1,7 radians.
# L'option "-l" appelle la bibliothèque mathématique de 'bc'.
echo $var3       # .991664810


# Maintenant, essayez-la dans une fonction...
hyp=             # Déclarez une variable globale.
hypotenuse ()    # Calculez l'hypoténuse d'un triangle à angle droit.
{
hyp=$(bc -l << EOF
scale = 9
sqrt ( $1 * $1 + $2 * $2 )
EOF
)
#  Malheureusement, on ne peut pas renvoyer de valeurs à virgules flottantes à
#+ partir d'une fonction Bash.
}

hypotenuse 3.68 7.31
echo "hypoténuse = $hyp"    # 8.184039344


exit 0

Exemple 12-35. Calculer PI

#!/bin/bash
# cannon.sh: Approximation de PI en tirant des balles de canon.

# C'est une très simple instance de la simulation "Monte Carlo", un modèle
#+ mathématique d'un événement réel, en utilisant des nombres pseudo-aléatoires
#+ pour émuler la chance.

#  Considérez un terrain parfaitement carré, de 10000 unités par côté.
#  Ce terrain comprend un lac parfaitement circulaire en son centre d'un
#+ diamètre de 10000 units.
#  Ce terrain ne comprend donc que de l'eau sauf dans ses quatre coins.
#  (un carré comprenant un cercle.)
#
#  Tirons des balles de canon à partir d'un vieux canon situé sur un des côtés
#+ du terrain.
#  Tous les tirs créent des impacts quelque part sur le terrain, soit dans le
#+ lac soit dans un des coins secs.
#  Comme le lac prend la majorité de l'espace disponible sur ce terrain, la
#+ plupart des tirs va tomber dans l'eau.
#  Seuls quelques tirs tomberont sur un sol rigide compris dans les coins du
#+ terrain.
#
#  Si nous prenons assez de tirs non visés et au hasard,
#+ alors le ratio des coups dans l'eau par rapport au nombre total sera
#+ approximativement de PI/4.
#  Exercice: Expliquez pourquoi.
#
#  Théoriquement, plus de tirs sont réalisés, plus cela correspondra.
#  Néanmoins, un script shell, contrairement à un langage compilé avec un
#+ support des calculs à virgule flottante, nécessite quelques compromis.
#  Ceci tend à rendre la simulation moins précise malheureusement.


DIMENSION=10000  # Longueur de chaque côté du terrain.
                 # Initialise aussi le nombre d'entiers générés au hasard.

NB_TIRS_MAX=1000 # Tire ce nombre de fois.
                 # 10000 ou plus serait mieux mais prendrait bien plus de temps.
PMULTIPLIEUR=4.0 # Facteur d'échelle pour l'approximation de PI.

au_hasard ()
{
RECHERCHE=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }')
HASARD=$RECHERCHE                    # Du script d'exemple "seeding-random.sh"
let "rnum = $HASARD % $DIMENSION"    # Echelle plus petite que 10000.
echo $rnum
}

distance=        # Déclaration de la variable globale.
hypotenuse ()    # Calcule de l'hypoténuse d'un triangle à angle droit.
{                # A partir de l'exemple "alt-bc.sh".
distance=$(bc -l << EOF
scale = 0
sqrt ( $1 * $1 + $2 * $2 )
EOF
)
#  Initiale "scale" à zéro fait que le résultat sera une valeur entière, un
#+ compris nécessaire dans ce script.
#  Ceci diminue l'exactitude de la simulation malheureusement.
}


# main() {

# Initialisation des variables.
tirs=0
dans_l_eau=0
sur_terre=0
Pi=0

while [ "$tirs" -lt  "$NB_TIRS_MAX" ]        # Boucle principale.
do

  xCoord=$(au_hasard)                        # Obtenir les coordonnées X et Y au
                                             # hasard.
  yCoord=$(au_hasard)
  hypotenuse $xCoord $yCoord                 #  Hypoténuse du triangle =
                                             #+ distance.
  ((tirs++))

  printf "#%4d   " $tirs
  printf "Xc = %4d  " $xCoord
  printf "Yc = %4d  " $yCoord
  printf "Distance = %5d  " $distance         #  Distance à partir du centre
                                              #+ du lac

  if [ "$distance" -le "$DIMENSION" ]
  then
    echo -n "Dans l'eau !  "
    ((dans_l_eau++))
  else
    echo -n "Sur terre !   "
    ((sur_terre++))
  fi

  Pi=$(echo "scale=9; $PMULTIPLIEUR*$dans_l_eau/$tirs" | bc)
  # Multipliez le ratio par 4.0.
  echo -n "PI ~ $Pi"
  echo

done

echo
echo "Après $tirs tirs, PI ressemble approximativement à $Pi."
# Tend à être supérieur.
# Probablement dû aux erreurs d'arrondi et au hasard perfectible de $RANDOM.
echo

# }

exit 0

#  On peut se demander si un script shell est approprié pour une application
#+ aussi complexe et aussi intensive en calcul.
#
#  Il existe au moins deux justifications.
#  1) La preuve du concept: pour montrer que cela est possible.
#  2) Pour réaliser un prototype et tester les algorithmes avant de le réécrire
#+    dans un langage compilé de haut niveau.
dc

L'utilitaire dc (desk calculator) est orienté pile et utilise la << notation polonaise inversée >> (RPN). Commebc, Comme bc, il possède les bases d'un langage de programmation.

La plupart des gens évitent dc, parce qu'il nécessite de saisir les entrées en RPN, ce qui n'est pas très intuitif. Toutefois, cette commande garde son utilité.

Exemple 12-36. Convertir une valeur décimale en hexadecimal

#!/bin/bash
# hexconvert.sh: Convertit un nombre décimal en héxadecimal.

BASE=16     # Héxadecimal.

if [ -z "$1" ]
then
  echo "Usage: $0 nombre"
  exit $E_SANSARGS
  # A besoin d'un argument en ligne de commande.
fi
# Exercice: ajouter une vérification de la validité de l'argument.


hexcvt ()
{
if [ -z "$1" ]
then
  echo 0
  return    # "Renvoie" 0 si aucun argument n'est passé à la fonction.
fi

echo ""$1" "$BASE" o p" | dc
#                   "o" demande une sortie en base numérique.
#                   "p" Affiche le haut de la pile.
# Voir 'man dc' pour plus d'options.
return
}

hexcvt "$1"

exit 0

L'étude de la page d'info de la commande dc donne une idée de sa complexité. Il semble cependant qu'une poignée de connaisseurs de dc se délectent de pouvoir exiber leur maîtrise de cet outil puissant mais mystérieux.

Exemple 12-37. Factoring

#!/bin/bash
# factr.sh: Factorise un nombre

MIN=2       # Ne fonctionnera pas pour des nombres plus petits que celui-ci.
E_SANSARGS=65
E_TROPPETIT=66

if [ -z $1 ]
then
  echo "Usage: $0 nombre"
  exit $E_SANSARGS
fi

if [ "$1" -lt "$MIN" ]
then
  echo "Le nombre à factoriser doit être supérieur ou égal à $MIN."
  exit $E_TROPPETIT
fi  

#  Exercice: Ajouter une vérification du type (pour rejeter les arguments non
#+ entiers).

echo "Les facteurs de $1:"
# ---------------------------------------------------------------------------------
echo "$1[p]s2[lip/dli%0=1dvsr]s12sid2%0=13sidvsr[dli%0=1lrli2+dsi!>.]ds.xd1<2" | dc
# ---------------------------------------------------------------------------------
# La ligne de code ci-dessus a été écrit par Michel Charpentier <charpov@cs.unh.edu>.
# Utilisé avec sa permission (merci).

 exit 0
awk

Une autre façon d'utiliser les nombres à virgule flottante est l'utilisation des fonctions internes de la commande awk dans un emballage shell .

Exemple 12-38. Calculer l'hypoténuse d'un triangle

#!/bin/bash
# hypotenuse.sh: Renvoie l'"hypoténuse" d'un triangle à angle droit,
#               (racine carrée de la somme des carrés des côtés)

ARGS=2                # Le script a besoin des côtés du triangle.
E_MAUVAISARGS=65          # Wrong number of arguments.

if [ $# -ne "$ARGS" ] # Teste le nombre d'arguments du script.
then
  echo "Usage: `basename $0` cote_1 cote_2"
  exit $E_MAUVAISARGS
fi


SCRIPTAWK=' { printf( "%3.7f\n", sqrt($1*$1 + $2*$2) ) } '
#            commande(s) / paramètres passés à awk


echo -n "Hypoténuse de $1 et $2 = "
echo $1 $2 | awk "$SCRIPTAWK"

exit 0
ARGUMENTS_ATTENDUS=2 E_MAUVAISARGS=65 if [ $# -ne $ARGUMENTS_ATTENDUS ] # Vérifie le bon nombre d'arguments en ligne de commande. then echo "Usage: `basename $0` téléphone# fichier-texte" exit $E_MAUVAISARGS fi if [ ! -f "$2" ] then echo "Le fichier $2 n'est pas un fichier texte" exit $E_MAUVAISARGS fi fax make $2 # Crée des fichiers formatés pour le fax à partir de #+ fichiers texte. for fichier in $(ls $2.0*) # Concatène les fichiers convertis. # Utilise le caractère joker dans la liste des variables. do fic="$fic $fichier" done efax -d /dev/ttyS3 -o1 -t "T$1" $fic # Fait le boulot. # Comme S.C. l'a indiqué, la boucle for peut être supprimée avec # efax -d /dev/ttyS3 -o1 -t "T$1" $2.0* # mais ce n'est pas aussi instructif [grin]. exit 0
while

Cette construction teste une condition au début de la boucle et continue à boucler tant que la condition est vraie (renvoie un 0 code de sortie). Par opposition à une boucle for, une boucle while trouve son utilité dans des situations où le nombre de répétitions n'est pas connu dès le départ.

while [condition]
do
 commande...
done

Comme c'est le cas avec les boucles for/in, placez le do sur la même ligne que le test de la condition nécessite un point virgule.

while [condition] ; do

Notez que certaines boucles while spécialisées, comme par exemple une construction getopts, dévie quelque peu du modèle standard donné ici.

Exemple 10-14. Simple boucle while

#!/bin/bash

var0=0
LIMITE=10

while [ "$var0" -lt "$LIMITE" ]
do
  echo -n "$var0 "        # -n supprime le retour chariot.
  var0=`expr $var0 + 1`   # var0=$(($var0+1)) fonctionne aussi.
done

echo

exit 0

Exemple 10-15. Une autre boucle while

#!/bin/bash

echo

while [ "$var1" != "fin" ]     # while test "$var1" != "end"
do                             # fonctionne aussi.
  echo "Variable d'entrée #1 (quitte avec fin) "
  read var1                    # pas de 'read $var1' (pourquoi?).
  echo "variable #1 = $var1"   # A besoin des guillemets à cause du "#".
  # Si l'entrée est 'end', l'affiche ici.
  # Ne teste pas la condition de fin avant de revenir en haut de la boucle.
  echo
done  

exit 0

Une boucle while peut avoir de multiples conditions. Seule la condition finale détermine quand la boucle se termine. Ceci nécessite une syntaxe de boucle légèrement différente, malgré tout.

Exemple 10-16. Boucle while avec de multiples conditions

#!/bin/bash

var1=unset
precedent=$var1

while echo "Variable précédente = $precedent"
      echo
      precedent=$var1
      [ "$var1" != fin ] # Garde trace de ce que $var1 valait précédemment.
      #  Quatre conditions sur "while", mais seule la dernière contrôle la
      #+ boucle.
      # Le *dernier* code de sortie est celui qui compte.
do
  echo "Variable d'entrée #1 (quitte avec fin) "
  read var1
  echo "variable #1 = $var1"
done  

# Essayez de comprendre comment cela fonctionne.
# il y a un peu d'astuces.

exit 0

Comme pour une boucle for, une boucle while peut employer une syntaxe identique à C en utilisant la construction avec des parenthèses doubles (voir aussi Exemple 9-28).

Exemple 10-17. Syntaxe à la C pour une boucle while

#!/bin/bash
# wh-loopc.sh: Compter jusqu'à 10 dans une boucle "while".

LIMITE=10
a=1

while [ "$a" -le $LIMITE ]
do
  echo -n "$a "
  let "a+=1"
done           # Pas de surprises, jusqu'ici.

echo; echo

# +=================================================================+

# Maintenant, de nouveau mais avec une syntaxe C.

((a = 1))      # a=1
#  Les double parenthèses permettent les espaces pour initialiser une variable,
#+ comme en C.

while (( a <= LIMITE )) # Double parenthèses, et pas de "$" devant la variable.
do
  echo -n "$a "
  ((a += 1))   # let "a+=1"
  # Oui, en effet.
  #  Les double parenthèses permettent d'incrémenter une varibale avec une
  #+ syntaxe style C.
done

echo

# Maintenant, les programmeurs C se sentent chez eux avec Bash.

exit 0

Note

Une boucle while peut avoir son stdin redirigé vers un fichier par un < à la fin.

until

Cette construction teste une condition au début de la boucle et continue à boucler tant que la condition est fausse (l'opposé de la boucle while).

until [condition-est-vraie]
do
 commande...
done

Notez qu'une boucle until teste la condition de fin au début de la boucle, contrairement aux constructions similaires dans certains langages de programmation.

Comme c'est la cas avec les boucles for/in, placez do sur la même ligne que le test de la condition nécessite un point virgule.

until [condition-est-vraie] ; do

Exemple 10-18. Boucle until

#!/bin/bash

until [ "$var1" = fin ] # Condition du test ici, en haut de la boucle.
do
  echo "Variable d'entrée #1 "
  echo "(fin pour sortir)"
  read var1
  echo "variable #1 = $var1"
done  

exit 0
>

$SECONDS

Le nombre de secondes pendant lequel le script s'exécutait.

#!/bin/bash

LIMITE_TEMPS=10
INTERVALLE=1

echo
echo "Tapez sur Control-C pour sortir avant $LIMITE_TEMPS secondes."
echo

while [ "$SECONDES" -le "$LIMITE_TEMPS" ]
do
  if [ "$SECONDES" -eq 1 ]
  then
    unites=seconde
  else  
    unites=secondes
  fi

  echo "Ce script tourne depuis $SECONDES $unites."
  #  Sur une machine lente, le script peut laisser échapper un élément du
  #+ comptage quelque fois dans la boucle while.
  sleep $INTERVALLE
done

echo -e "\a"  # Beep!

exit 0

$SHELLOPTS

la liste des options activées du shell, une variable en lecture seule
bash$ echo $SHELLOPTS
braceexpand:hashall:histexpand:monitor:history:interactive-comments:emacs
	      

$SHLVL

Niveau du shell, comment Bash est imbriqué. Si, à la ligne de commande, $SHLVL vaut 1, alors, dans un script, il sera incrémenté et prendra la valeur 2.

$TMOUT

Si la variable d'environnement $TMOUT est initialisée à une valeur différente de zéro appelée time, alors l'invite shell dépassera son délai au bout de time secondes. Ceci causera une déconnexion.

Note

Malheureusement, ceci fonctionne seulement lors de l'attente d'une saisie sur une invite de la console ou dans un xterm. Bien qu'il serait sympathique de spéculer sur l'utilité de cette variable interne pour des saisies avec expiration de délai, par exemple en combinaison avec read, $TMOUT ne fonctionnera pas dans ce contexte et est virtuellement inutile pour l'écriture de scripts shell. (Une information semble indiquer qu'un read avec délai fontionne sur ksh.)

Implémenter une saisie avec délai dans un script est certainement possible, mais nécessiterait un code complexe. Une méthode est de configurer une boucle avec délai pour signaler au script lorsque le délai se termine. Ceci nécessite aussi une routine de gestion du signal pour récupérer (voir Exemple 30-5) l'interruption générée par la boucle de délai (ouf!).

Exemple 9-2. Saisie avec délai

#!/bin/bash
# timed-input.sh

# TMOUT=3            inutile dans un script

LIMITETEMPS=3  # Trois secondes dans cette instance, peut être configuré avec
               #+ une valeur différente.

AfficheReponse()
{
  if [ "$reponse" = TIMEOUT ]
  then
    echo $reponse
  else       # ne pas mixer les deux interfaces.
    echo "Votre légume favori est le $reponse"
    kill $!  #  Kill n'est plus nécessaire pour la fonction TimerOn lancé en
             #+ tâche de fond.
             # $! est le PID du dernier job lancé en tâche de fond.
  fi

}  



TimerOn()
{
  sleep $LIMITETEMPS && kill -s 14 $$ &
  # Attend 3 secondes, puis envoie sigalarm au script.
}  

VecteurInt14()
{
  reponse="TIMEOUT"
  AfficheReponse
  exit 14
}  

trap VecteurInt14 14   # Interruption de temps (14) détournée pour notre but.

echo "Quel est votre légume favori?"
TimerOn
read reponse
AfficheReponse


#  C'est une implémentation détournée de l'entrée de temps,
#+ néanmoins l'option "-t" de "read" simplifie cette tâche.
#  Voir "t-out.sh", ci-dessous.

#  Si vous avez besoin de quelque chose de réellement élégant...
#+ pensez à écrire l'application en C ou C++,
#+ en utilisant les fonctions de la bibliothèque appropriée, telles que
#+ 'alarm' et 'setitimer'.

exit 0

Une autre méthode est d'utiliser stty.

Exemple 9-3. Encore une fois, saisie avec délai

#!/bin/bash
# timeout.sh

# Ecrit par Stephane Chazelas,
# et modifié par l'auteur de ce document.

INTERVALLE=5                # timeout interval

lecture_timedout() {
  timeout=$1
  nomvariable=$2
  ancienne_configuration_tty=`stty -g`
  stty -icanon min 0 time ${timeout}0
  eval read $nomvariable      # ou simplement    read $nomvariable
  stty "$ancienne_configuration_tty"
  # Voir la page man de "stty".
}

echo; echo -n "Quel est votre nom? Vite!"
lecture_timedout $INTERVALLE votre_nom

# Ceci pourrait ne pas fonctionner sur tous les types de terminaux.
# Le temps imparti dépend du terminal (il est souvent de 25,5 secondes).

echo

if [ ! -z "$votre_nom" ]  #  Si le nom est entré avant que le temps ne se soit
                          #+ écoulé...
then
  echo "Votre nom est $votre_nom."
else
  echo "Temps écoulé."
fi

echo

# Le comportement de ce script diffère un peu de "timed-input.sh".
# A chaque appui sur une touche, le compteur est réinitialisé.

exit 0

Peut-être que la méthode la plus simple est d'utiliser l'option -t de read.

Exemple 9-4. read avec délai

#!/bin/bash
# t-out.sh (suggestion de "syngin seven")

LIMITETEMPS=4        # 4 secondes

read -t $LIMITETEMPS variable <&1

echo

if [ -z "$variable" ]
then
  echo "Temps écoulé, la variable n'est toujours pas initialisée."
else  
  echo "variable = $variable"
fi  

exit 0
$UID

numéro de l'identifiant utilisateur

numéro d'identification de l'utilisateur actuel, comme enregistré dans /etc/passwd

C'est l'identifiant réel de l'utilisateur actuel, même s'il a temporairement endossé une autre identité avec su. $UID est une variable en lecture seule, non sujet au changement à partir de la ligne de commande ou à l'intérieur d'un script, et est la contre partie de l'intégré id.

Exemple 9-5. Suis-je root?

#!/bin/bash
# am-i-root.sh:   Suis-je root ou non?

ROOT_UID=0   # Root a l'identifiant $UID 0.

if [ "$UID" -eq "$ROOT_UID" ]  # Le vrai "root" peut-il se lever, s'il-vous-plaît?
then
  echo "Vous êtes root."
else
  echo "Vous êtes simplement un utilisateur ordinaire (mais maman vous aime tout autant.)."
fi

exit 0


# ============================================================= #
# Le code ci-dessous ne s'exécutera pas, parce que le script s'est déjà arrêté.

# Une autre méthode d'arriver à la même fin:

NOM_UTILISATEURROOT=root

nomutilisateur=`id -nu`              # Ou...   nomutilisateur=`whoami`
if [ "$nomutilisateur" = "$NOM_UTILISATEURROOT" ]
then
  echo "Vous êtes root."
else
  echo "Vous êtes juste un gars régulier."
fi

Voir aussi Exemple 2-2.

Note

Les variables $ENV, $LOGNAME, $MAIL, $TERM, $USER et $USERNAME ne sont pas des variables intégrés à Bash. Elles sont néanmois souvent initialisées comme variables d'environnement dans un des fichiers de démarrage de Bash. $SHELL, le nom du shell de connexion de l'utilisateur, peut être configuré à partir de /etc/passwd ou dans un script d'<< initialisation >>, et ce n'est pas une variable intégrée à Bash.

tcsh% echo $LOGNAME
bozo
tcsh% echo $SHELL
/bin/tcsh
tcsh% echo $TERM
rxvt

bash$ echo $LOGNAME
bozo
bash$ echo $SHELL
/bin/tcsh
bash$ echo $TERM
rxvt
	      

Paramètres de position

$0, $1, $2, etc.

paramètres de positions, passés à partir de la ligne de commande à un script, passés à une fonction, ou initialisés (set) à une variable (voir Exemple 4-5 et Exemple 11-13)

$#

nombre d'arguments sur la ligne de commande [2] ou de paramètres de position (voir Exemple 34-2)

$*

Tous les paramètres de position, vus comme un seul mot

$@

Identique à $*, mais chaque paramètre est une chaîne entre guillemets, c'est-à-dire que les paramètres sont passés de manière intacte, sans interprétation ou expansion. Ceci signifie, entre autres choses, que chaque paramètre dans la liste d'arguments est vu comme un mot séparé.

Exemple 9-6. arglist: Affichage des arguments avec $* et $@

#!/bin/bash
# Appelez ce script avec plusieurs arguments, tels que "un deux trois".

E_BADARGS=65

if [ ! -n "$1" ]
then
  echo "Usage: `basename $0` argument1 argument2 etc."
  exit $E_BADARGS
fi  

echo

index=1

echo "Liste des arguments avec \"\$*\":"
for arg in "$*"  # Ne fonctionne pas correctement si "$*" n'est pas entre guillemets.
do
  echo "Arg #$index = $arg"
  let "index+=1"
done             # $* voit tous les arguments comme un mot entier. 
echo "Liste entière des arguments vue comme un seul mot."

echo

index=1

echo "Liste des arguments avec \"\$@\":"
for arg in "$@"
do
  echo "Arg #$index = $arg"
  let "index+=1"
done             # $@ voit les arguments comme des mots séparés. 
echo "Liste des arguments vue comme des mots séparés."

echo

exit 0

Suite à un shift, $@ contient le reste des paramètres de la ligne de commande, sans le précédent $1, qui a été perdu.
#!/bin/bash
# Appelé avec ./script 1 2 3 4 5

echo "$@"    # 1 2 3 4 5
shift
echo "$@"    # 2 3 4 5
shift
echo "$@"    # 3 4 5

# Chaque "shift" perd le paramètre $1.
# "$@" contient alors le reste des paramètres.

Le paramètre spécial $@ trouve son utilité comme outil pour filtrer l'entrée des scripts shell. La construction cat "$@" accepte l'entrée dans un script soit à partir de stdin soit à partir de fichiers donnés en paramètre du script. Voir Exemple 12-17 et Exemple 12-18.

Attention

Les paramètres $* et $@ affichent quelque fois un comportement inconsistent et bizarre, suivant la configuration de $IFS.

Exemple 9-7. Comportement de $* et $@ inconsistent

#!/bin/bash

#  Comportement non prédictible des variables internes Bash "$*" et "$@",
#+ suivant qu'elles soient ou non entre guillemets.
#  Gestion inconsistente de la séparation de mots et des retours chariot.


set -- "Premier un" "second" "troisième:un" "" "Cinquième: :un"
# Initialise les arguments du script, $1, $2, etc.

echo

echo 'IFS inchangée, utilisant "$*"'
c=0
for i in "$*"               # entre guillemets
do echo "$((c+=1)): [$i]"   # Cette ligne reste identique à chaque instance.
                            # Arguments de echo.
done
echo ---

echo 'IFS inchangée, utilisant $*'
c=0
for i in $*                 # entre guillemets
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS inchangée, utilisant "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS inchangée, utilisant $@'
c=0
for i in $@
do echo "$((c+=1)): [$i]"
done
echo ---

IFS=:
echo 'IFS=":", utilisant "$*"'
c=0
for i in "$*"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", utilisant $*'
c=0
for i in $*
do echo "$((c+=1)): [$i]"
done
echo ---

var=$*
echo 'IFS=":", utilisant "$var" (var=$*)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", utilisant $var (var=$*)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

var="$*"
echo 'IFS=":", utilisant $var (var="$*")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", utilisant "$var" (var="$*")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", utilisant "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", utilisant $@'
c=0
for i in $@
do echo "$((c+=1)): [$i]"
done
echo ---

var=$@
echo 'IFS=":", utilisant $var (var=$@)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", utilisant "$var" (var=$@)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

var="$@"
echo 'IFS=":", utilisant "$var" (var="$@")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", utilisant $var (var="$@")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done

echo

# Essayez ce script avec ksh ou zsh -y.

exit 0

# Ce script exemple par Stephane Chazelas,
# et légèrement modifié par l'auteur de ce document.

Note

Les paramètres $@ et $* diffèrent seulement lorsqu'ils sont entre des guillemets doubles.

Exemple 9-8. $* et $@ lorsque $IFS est vide

#!/bin/bash

# Si $IFS est initialisé, mais vide,
# alors "$*" et "$@" n'affiche pas les paramètres de position comme on pourrait
# s'y attendre.

mecho ()       # Affiche les paramètres de position.
{
echo "$1,$2,$3";
}


IFS=""         # Initialisé, mais vide.
set a b c      # Paramètres de position.

mecho "$*"     # abc,,
mecho $*       # a,b,c

mecho $@       # a,b,c
mecho "$@"     # a,b,c

# Le comportement de $* et $@ quand $IFS est vide dépend de la version de
# Bash ou sh.
# Personne ne peux donc conseiller d'utiliser cette "fonctionnalité" dans un
# script.


# Merci, S.C.

exit 0

Autres paramètres spéciaux

$-

Les options passées au script (en utilisant set). Voir Exemple 11-13.

Attention

Ceci était originellement une construction de ksh adoptée dans Bash, et malheureusement elle ne semble pas fonctionner de façon fiable dans les scripts Bash. Une utilité possible pour ceci est d'avoir un script testant lui-même s'il est interactif.

$!

PID (identifiant du processus) du dernier job ayant fonctionné en tâche de fond

$_

Variable spéciale initialisée au dernier argument de la dernièr commande exécutée.

Exemple 9-9. variable tiret bas

#!/bin/bash

echo $_              # /bin/bash
                     # Simple appel de /bin/bash pour lancer ce script.

du >/dev/null        # Donc pas de sortie des commandes
echo $_              # du

ls -al >/dev/null    # Donc pas de sortie des commandes
echo $_              # -al  (dernier argument)

:
echo $_              # :
$?

Code de sortie d'une commande, fonction, ou du script lui-même (voir Exemple 23-3)

$$

Identifiant du processus du script lui-même. La variable $$ trouve fréquemment son utilité dans les scripts pour construire des noms de fichiers temporaires << uniques >> (voir Exemple A-14, Exemple 30-6, Exemple 12-23 et Exemple 11-23). Ceci est généralement plus simple que d'appeler mktemp.

Notes

[1]

Le pid du script en cours est $$, bien sûr.

[2]

Les mots << argument >> et << paramètre >> sont souvent utilisés sans distinction. Dans le contexte de ce document, ils ont exactement la même signification, celle d'une variable passée à un script ou à une fonction.

>

#!/bin/bash

exec echo "Je sors \"$0\"."   # Sortie du script ici.

# ----------------------------------
# Les lignes suivantes ne s'exécutent jamais.

echo "Cet echo ne sera jamais exécuté."

exit 99                       #  Ce script ne sortira jamais par ici.
                              #  Vérifier le code de sortie après l'exécution du
                              #+ du script avec un 'echo $?'.
                              #  Cela ne sera *pas* 99.

Exemple 11-21. Un script lançant exec sur lui-même

#!/bin/bash
# self-exec.sh

echo

echo "Cette ligne apparaît UNE FOIS dans le script, cependant elle continue à s'afficher."
echo "Le PID de cette instance du script est toujours $$."
#     Démontre qu'un sous-shell n'est pas un processus fils.

echo "==================== Tapez Ctl-C pour sortir ===================="

sleep 1

exec $0   #  Lance une autre instance du même script remplaçant le précédent.

echo "Cette ligne ne s'affichera jamais!"  # Pourquoi pas?

exit 0

Un exec sert aussi à réaffecter les descripteurs de fichiers.exec <fichier-zzz remplace stdin par le fichier fichier-zzz (voir Exemple 16-1).

Note

L'option -exec pour find n'est pas du tout la même chose que la commande shell intégrée exec.

shopt

Cette commande permet de changer les options du shell au vol (voir Exemple 24-1 et Exemple 24-2). Elle apparait souvent dans les fichiers de démarrage de Bash, mais a aussi son utilité dans des scripts. Il est nécessaire de disposer de la version 2, ou ultérieurs, de Bash.
shopt -s cdspell
# Permet des petites erreurs dans le nom des répertoires avec 'cd'

cd /hpme  # Oups! J'ai mal tapé '/home'.
pwd       # /home
          # Le shell a corrigé la faute de frappe.

Commandes

true

Une commande qui renvoie un succès (zéro) comme état de sortie, mais ne fait rien d'autre.

# Boucle sans fin
while true   # alias pour ":"
do
   operation-1
   operation-2
   ...
   operation-n
   # A besoin d'un moyen pour sortir de la boucle.
done

false

Une commande qui renvoit un état de sortie correspondant à un échec, mais ne fait rien d'autre.

# Fausse boucle
while false
do
   # Le code suivant ne sera pas exécuté.
   operation-1
   operation-2
   ...
   operation-n
   # Rien ne se passe!
done   

type [cmd]

Identique à la commande externe which, type cmd donne le chemin complet vers << cmd >>. Contrairement à which, type est une commande intégrée à Bash. L'option -a est très utile pour que type identifie des mots clés et des commandes internes, et localise aussi les commandes système de nom identiques.

bash$ type '['
[ is a shell builtin
bash$ type -a '['
[ is a shell builtin
 [ is /usr/bin/[
	      

hash [cmds]

Enregistre le chemin des commandes spécifiées (dans une table de hachage du shell), donc le shell ou le script n'aura pas besoin de chercher le $PATH sur les appels futurs à ces commandes. Quand hash est appelé sans arguments, il liste simplement les commandes qui ont été hachées. L'option -r réinitialise la table de hachage.

help

help COMMANDE cherche un petit résumé sur l'utilisation de la commande COMMANDE intégrée au shell. C'est l'équivalent de whatis, pour les commandes intégrées.

bash$ help exit
exit: exit [n]
    Exit the shell with a status of N.  If N is omitted, the exit status
    is that of the last command executed.
	      

Notes

[1]

Une exception à ceci est la commande time, listée dans la documentation Bash officielle en tant que mot clé.

[2]

Une option est un argument agissant comme un indicateur, changeant les comportements du script de façon binaire. L'argument associé avec une option particulière indique le comportement que l'option active ou désactive.