Décompose un entier en nombre premiers.
bash$ factor 27417 27417: 3 13 19 37
Exemple 16.46. Générer des nombres premiers
#!/bin/bash # primes2.sh # Generating prime numbers the quick-and-easy way, #+ without resorting to fancy algorithms. CEILING=10000 # 1 to 10000 PRIME=0 E_NOTPRIME= is_prime () { local factors factors=( $(factor $1) ) # Load output of `factor` into array. if [ -z "${factors[2]}" ] # Third element of "factors" array: #+ ${factors[2]} is 2nd factor of argument. # If it is blank, then there is no 2nd factor, #+ and the argument is therefore prime. then return $PRIME # 0 else return $E_NOTPRIME # null fi } echo for n in $(seq $CEILING) do if is_prime $n then printf %5d $n fi # ^ Five positions per number suffices. done # For a higher $CEILING, adjust upward, as necessary. echo exit
Bash ne peut traiter les calculs en virgule flottante et n'intègre pas certaines fonctions mathématiques importantes. Heureusement, bc arrive à la rescousse.
bc n'est pas simplement une calculatrice souple à précision arbitraire, elle apporte aussi beaucoup des facilités disponibles normalement dans un langage de programmation, avec une syntaxe ressemblant en gros à celle de C.
bc est devenu un outil UNIX assez puissant pour être utilisé via un tube et est manipulable dans des scripts.
Voici un exemple simple d'utilisation de bc pour calculer la valeur d'une variable. Il emploie la substitution de commande.
variable=$(echo "OPTIONS; OPERATIONS" | bc)
Exemple 16.47. 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 16.48. 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.10 2008-05-10 08:36:11 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.10 $' | 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 16.49. 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 16.50. 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. 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. M_PI=3.141592654 # Valeur réelle de PI, dans un but de comparaison. 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. } # main() { # Initialisation des variables. tirs=0 dans_l_eau=0 sur_terre=0 Pi=0 error=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. error=$(echo "scale=9; $Pi - $M_PI" | bc) echo "Deviation de la valeur mathématique de PI = $error" 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.
See also Exemple A.37, « Écart-type ».
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.
Un peu comme pour la procédure avec bc, echo une ligne de commande vers dc.
echo "[Affiche une chaîne ... ]P" | dc # La commande P affiche la chaîne comprise entre les crochets qui la précèdent. # Et maintenant pour de l'arithmétique simple. echo "7 8 * p" | dc # 56 # Pousse 7, puis 8, sur le dessus de la pile, #+ multiplie (opérateur "*" operator), ensuite imprime le résultat #+ (opérateur "p").
La plupart des gens évitent dc à cause de son mode de saisie peu intuitif et du caractère assez obscur de ses opérateurs. Toutefois, cette commande garde son utilité.
Exemple 16.51. 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 exhiber leur maîtrise de cet outil puissant mais mystérieux.
bash$ echo "16i[q]sa[ln0=aln100%Pln100/snlbx]sbA0D68736142snlbxq" | dc Bash
dc <<< 10k5v1+2/p # 1.6180339887 # ^^^ Alimente dc en opérations en utilisant une chaîne Here. # ^^^ Fournit 10 et déclare la précision du calcul (10k). # ^^ Fournit 5 et prend sa racine carré # (5v, v = racine carrée). # ^^ Fournit 1 et l'additionne au résultat courant (1+). # ^^ Fournit 2 et divise le résultat courant par 2 (2/). # ^ Sort et affiche le résultat (p pour "pop"). # Le resultat est 1.6180339887 ... # ... qui est en fait le nombre d'or pythagoricien avec 10 décimales.
Exemple 16.52. 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 16.53. 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 ?