« Compter »
Décompose un entier en nombre premiers.
bash$ factor 27417 27417: 3 13 19 37
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 habituellement 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 dans des scripts.
Ceci est un simple exemple utilisant bc pour calculer la valeur d'une variable. Il utilise la substitution de commande.
variable=$(echo "OPTIONS; OPERATIONS" | bc)
Exemple 15.43. Paiement mensuel sur une hypothèque
#!/bin/bash # monthlypmt.sh : Calcule le paiement mensuel d'une hypothèque. # C'est une modification du code du paquetage "mcalc" (mortgage calculator, #+ c'est-à-dire calcul d'hypothèque), de Jeff Schmidt et Mendel Cooper #+ (l'auteur du guide ABS). # http://www.ibiblio.org/pub/Linux/apps/financial/mcalc-1.6.tar.gz [15k] echo echo "Étant donné le montant principal, le taux d'intérêt et la fin de l'hypothèque," echo "calcule le paiement mensuel." bas=1.0 echo echo -n "Entrez le montant principal (sans virgule) " read principal echo -n "Entrez le taux d'intérêt (pourcentage) " # Si 12%, entrez "12" et non pas ".12". read taux_interet echo -n "Entrez le nombre de mois " read nb_mois taux_interet=$(echo "scale=9; $taux_interet/100.0" | bc) # Convertit en décimal # ^^^^^^^^^^^^^^^^^^^ Diviser par 100. # "scale" détermine le nombre de décimales. taux_interet_tmp=$(echo "scale=9; $taux_interet/12 + 1.0" | bc) top=$(echo "scale=9; $principal*$taux_interet_tmp^$nb_mois" | bc) # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # Formule standard pour calculer un intérêt. echo; echo "Merci d'être patient. Ceci peut prendre longtemps." let "mois = $nb_mois - 1" # ==================================================================== for ((x=$mois; x > 0; x--)) do bot=$(echo "scale=9; $taux_interet_tmp^$x" | bc) bas=$(echo "scale=9; $bas+$bot" | bc) # bas = $(($bas + $bot")) done # ==================================================================== # -------------------------------------------------------------------- # Rick Boivie indique une implémentation plus efficace que la boucle #+ ci-dessus, ce qui réduit le temps de calcul de 2/3. # for ((x=1; x <= $mois; x++)) # do # bas=$(echo "scale=9; $bas * $taux_interet_tmp + 1" | bc) # done # Puis, il est revenu avec une alternative encore plus efficace, #+ car elle descend le temps d'exécution de 95%! # bas=`{ # echo "scale=9; bas=$bas; taux_interet_tmp=$taux_interet_tmp" # for ((x=1; x <= $mois; x++)) # do # echo 'bas = bas * taux_interet_tmp + 1' # done # echo 'bas' # } | bc` # Intègre une 'boucle for' dans la substitution de commande. # -------------------------------------------------------------------------- # D'un autre côté, Frank Wang suggère : # bottom=$(echo "scale=9; ($interest_rate^$term-1)/($interest_rate-1)" | bc) # Car # L'algorithme de la boucle est une somme de série géométrique de proportion. # La formule de la somme est e0(1-q^n)/(1-q), #+ où e0 est le premier élément et q=e(n+1)/e(n) #+ et n est le nombre d'éléments. # -------------------------------------------------------------------------- # let "paiement = $top/$bas" paiement=$(echo "scale=2; $top/$bas" | bc) # Utilise deux décimales pour les dollars et les cents. echo echo "paiement mensuel = \$$paiement" # Affiche un signe dollar devant le montant. echo exit 0 # Exercices : # 1) Filtrez l'entrée pour permettre la saisie de virgule dans le montant. # 2) Filtrez l'entrée pour permettre la saisie du taux d'intérêt en #+ pourcentage ou en décimale. # 3) Si vous êtes vraiment ambitieux, étendez ce script pour afficher #+ les tables d'amortissement complètes.
Exemple 15.44. Conversion de base
#!/bin/bash ################################################################################ # Script shell: base.sh - affiche un nombre en différentes bases (Bourne Shell) # Auteur : Heiner Steven (heiner.steven@odn.de) # Date : 07-03-95 # Catégorie : Desktop # $Id: base.sh,v 1.9 2007/01/08 23:58:45 gleu Exp $ # ==> La ligne ci-dessus est l'information ID de RCS. ################################################################################ # Description # # Modifications # 21-03-95 stv correction d'une erreur arrivant avec 0xb comme entrée (0.2) ################################################################################ # ==> Utilisé dans le guide ABS avec la permission de l'auteur du script. # ==> Commentaires ajoutés par l'auteur du guide ABS. NOARGS=65 PN=`basename "$0"` # Nom du programme VER=`echo '$Revision: 1.9 $' | cut -d' ' -f2` # ==> VER=1.6 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 ] # ==> est une "boucle while" réellement nécessaire # ==>+ car tous les cas soit sortent de la boucle # ==>+ soit terminent le script. # ==> (commentaire de Paulo Marcel Coelho Aragao.) 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 exit 0
Une autre façon d'utiliser bc est d'utiliser des documents en ligne embarqués dans un bloc de substitution de commandes. Ceci est très intéressant lorsque le script passe un grand nombre d'options et de commandes à bc
variable=`bc >> CHAINE_LIMITE options instructions operations CHAINE_LIMITE ` ...or... variable=$(bc >> CHAINE_LIMITE options instructions operations CHAINE_LIMITE )
Exemple 15.45. Appeler bc en utilisant un document en ligne
#!/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... hypotenuse () # Calculez l'hypoténuse d'un triangle à angle droit. { # c = sqrt( a^2 + b^2 ) hyp=$(bc -l << EOF scale = 9 sqrt ( $1 * $1 + $2 * $2 ) EOF ) # Can't directly return floating point values from a Bash function. # But, can echo-and-capture: echo "$hyp" } hyp=$(hypotenuse 3.68 7.31) echo "hypoténuse = $hyp" # 8.184039344 exit 0
Exemple 15.46. 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 unités. # Ce terrain ne comprend pratiquement que de l'eau mais aussi un peu de #+ terre dans ses quatre coins. # (pensez-y comme un carré comprenant un cercle.) # # Nous 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 carré, soit dans le #+ lac soit dans un des coins secs. # Comme le lac prend la majorité de l'espace disponible, la #+ plupart des tirs va tomber dans l'eau. # Seuls quelques tirs tomberont sur un sol rigide compris dans les quatre coins #+ du carré. # # 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. # # La raison de ceci est que le canon ne tire réellement que dans la partie #+ haute à droite du carré, premier quadrant des coordonnées cartésiennes. # (La précédente explication était une simplification.) # # 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 bien sûr. DIMENSION=10000 # Longueur de chaque côté. # 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 -n 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 rectangle = #+ distance. ((tirs++)) printf "#%4d " $tirs printf "Xc = %4d " $xCoord printf "Yc = %4d " $yCoord printf "Distance = %5d " $distance # Distance à partir du centre #+ du lac -- + # l'"origine" -- + #+ coordonnées (0,0). 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.
L'utilitaire dc (desk calculator) utilise l'empilement et la « notation polonaise inversée » (RPN). 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 15.47. Convertir une valeur décimale en hexadécimal
#!/bin/bash # hexconvert.sh : Convertit un nombre décimal en hexadécimal. E_SANSARGS=65 # Arguments manquants sur la ligne de commande. BASE=16 # Hexadécimal. 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 info de la commande dc est un moyen pénible de prendre conscience 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.
bash$ echo "16i[q]sa[ln0=aln100%Pln100/snlbx]sbA0D68736142snlbxq" | dc" Bash
Exemple 15.48. Factorisation
#!/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é écrite par Michel Charpentier <charpov@cs.unh.edu>. # Utilisé dans le guide ABS avec sa permission (merci). exit 0
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 15.49. 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 # Mauvais nombre d'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 # Maintenant, envoyez les paramètres à awk via un tube. echo -n "Hypoténuse de $1 et $2 = " echo $1 $2 | awk "$SCRIPTAWK" # ^^^^^^^^^^^^ # echo et pipe sont un moyen facile de passer des paramètres shell. exit 0 # Exercice : Ré-écrivez ce script en utilisant 'bc' à la place de awk. # Quelle méthode est la plus intuitive ?