9.7. La construction en double parenthèse

De façon similaire à la commande let, la construction ((...)) permet une évaluation arithmétique. Dans sa forme la plus simple, a=$(( 5 + 3 )) exécutera le calcul << 5 + 3 >>, soit 8, et attribuera sa valeur à la variable << a >>. Néanmoins, cette construction en double parenthèse est aussi un mécanisme permettant la manipulation de variables à la manière du C dans Bash.

Exemple 9-28. Manipulation, ç la façon du C, de variables

#!/bin/bash
# Manipuler une variable, style C, en utilisant la construction ((...)).


echo

(( a = 23 ))  # Initialiser une valeur, style C, avec des espaces des deux
              # côtés du signe "=".
echo "a (initial value) = $a"

(( a++ ))     # Post-incrémente 'a', style C.
echo "a (after a++) = $a"

(( a-- ))     # Post-décrémente 'a', style C.
echo "a (after a--) = $a"


(( ++a ))     # Pre-incrémente 'a', style C.
echo "a (after ++a) = $a"

(( --a ))     # Pre-décrémente 'a', style C.
echo "a (after --a) = $a"

echo

(( t = a<45?7:11 ))   # opérateur à trois opérandes, style C.
echo "If a < 45, then t = 7, else t = 11."
echo "t = $t "        # Oui!

echo


# ------------------
# Alerte Easter Egg!
# ------------------
#  Chet Ramey a apparemment laissé un ensemble de constructions C non
#+ documentées dans Bash (déjà adapté de ksh).
#  Dans les documents Bash, Ramey appele ((...)) un shell arithmétique,
#+ mais cela va bien au-delà.
#  Désolé, Chet, le secret est maintenant découvert.

# Voir aussi les boucles "for" et "while" utilisant la construction ((...)).

# Elles fonctionnent seulement avec Bash, version 2.04 ou ultérieure.

exit 0

Voir aussi Exemple 10-12.

>D'autres ont fait des suggestions intéressantes et ont indiqué des erreurs: Gabor Kiss, Leopold Toetsch, Peter Tillier, Marcus Berglof, Tony Richardson, Nick Drage (idées sur les scripts!), Rich Bartell, Jess Thrysoee, Adam Lazur, Bram Moolenaar, Baris Cicek, Greg Keraunen, Keith Matthews, Sandro Magi, Albert Reiner, Dim Segebart, Rory Winston, Lee Bigelow, Wayne Pollock, << jipe >>, and David Lawyer (lui-même auteur de quatre guides pratiques).

Ma gratitude pour Chet Ramey et Brian Fox pour l'écriture d'un élégant et puissant outil de scripts, Bash.

Et un très grand merci pour les volontaires qui ont durement travaillé au Linux Documentation Project. Le LDP contient un dépôt de connaissances Linux et a, sur une grande étendue, permis la publication de ce livre.

Merci en particulier à ma femme, Anita, pour ses encouragements et pour son support émotionnel.

echo "$nombre fichiers renommés." else echo "$nombre fichiers renommés." fi exit 0

Exemple A-5. encryptedpw: Charger un fichier sur un site ftp, en utilisant un mot de passe crypté en local

#!/bin/bash

# Exemple "ex72.sh" modifié pour utiliser les mots de passe cryptés.

#  Notez que c'est toujours peu sécurisé, car le mot de passe décrypté est
#+ envoyé en clair.
# Utilisez quelque chose comme "ssh" si cela vous préoccupe.

E_MAUVAISARGS=65

if [ -z "$1" ]
then
  echo "Usage: `basename $0` nomfichier"
  exit $E_MAUVAISARGS
fi  

NomUtilisateur=bozo      # Changez suivant vos besoins.
motpasse=/home/bozo/secret/fichier_avec_mot_de_passe_crypte.
# Le fichier contient un mot de passe crypté.

Nomfichier=`basename $1` # Supprime le chemin du fichier

Serveur="XXX"            #  Changez le nom du serveur et du répertoire suivant
Repertoire="YYY"         #+ vos besoins.


MotDePasse=`cruft <$motpasse`          # Décrypte le mot de passe.
#  Utilise la paquetage de cryptage de fichier de l'auteur,
#+ basé sur l'algorythme classique "onetime pad",
#+ et disponible à partir de:
#+ Site primaire:  ftp://metalab.unc.edu /pub/Linux/utils/file
#+                 cruft-0.2.tar.gz [16k]


ftp -n $Serveur <<Fin-de-Session
user $NomUtilisateur $MotDePasse
binary
bell
cd $Repertoire
put $Nomfichier
bye
Fin-de-Session
# L'option -n de "ftp" désactive la connexion automatique.
# "bell" fait sonner une cloche après chaque transfert.

exit 0

Exemple A-6. copy-cd: Copier un CD de données

#!/bin/bash
# copy-cd.sh: copier un CD de données

CDROM=/dev/cdrom                           # périphérique CD ROM
OF=/home/bozo/projects/cdimage.iso         # fichier de sortie
#       /xxxx/xxxxxxx/                     A modifier suivant votre système.
TAILLEBLOC=2048
VITESSE=2                                  # May use higher speed if supported.

echo; echo "Insérez le CD source, mais ne le montez *pas*."
echo "Appuyez sur ENTER lorsque vous êtes prêt. "
read pret                                  # Attendre une entrée, $pret n'est
                                           # pas utilisé.

echo; echo "Copie du CD source vers $OF."
echo "Ceci peut prendre du temps. Soyez patient."

dd if=$CDROM of=$OF bs=$TAILLEBLOC         # Copie brute du périphérique.


echo; echo "Supprimer le CD de données."
echo "Insérez un CDR vierge."
echo "Appuyez sur ENTER lorsque vous êtes prêt. "
read pret                                  # Attendre une entrée, $pret n'est
                                           # pas utilisé.

echo "Copie de $OF vers CDR."

cdrecord -v -isosize speed=$VITESSE dev=0,0 $OF
# Utilise le paquetage "cdrecord" de Joerg Schilling's (voir sa docs).
# http://www.fokus.gmd.de/nthp/employees/schilling/cdrecord.html


echo; echo "Copie terminée de $OF vers un CDR du périphérique $CDROM."

echo "Voulez-vous écraser le fichier image (o/n)? "  # Probablement un fichier
                                                     # immense.
read reponse

case "$reponse" in
[oO]) rm -f $OF
      echo "$OF supprimé."
      ;;
*)    echo "$OF non supprimé.";;
esac

echo

#  Exercice:
#  Modifiez l'instruction "case" pour aussi accepter "oui" et "Oui" comme
#+ entrée.

exit 0

Exemple A-7. collatz: Séries de Collatz

#!/bin/bash
# collatz.sh

#  Le célèbre "hailstone" ou la série de Collatz.
#  ----------------------------------------------
#  1) Obtenir un entier "de recherche" à partir de la ligne de commande.
#  2) NOMBRE <--- seed
#  3) Afficher NOMBRE.
#  4)  Si NOMBRE est pair, divisez par 2, ou
#  5)+ si impair, multiplier par 3 et ajouter 1.
#  6) NOMBRE <--- résultat
#  7) Boucler à l'étape 3 (pour un nombre spécifié d'itérations).
#
#  La théorie est que chaque séquence, quelle soit la valeur initiale,
#+ se stabilisera éventuellement en répétant des cycles "4,2,1...",
#+ même après avoir fluctuée à travers un grand nombre de valeurs.
#
#  C'est une instance d'une "itération", une opération qui remplit son
#+ entrée par sa sortie.
#  Quelque fois, le résultat est une série "chaotique".


MAX_ITERATIONS=200
# Pour une grande échelle de nombre (>32000), augmenter MAX_ITERATIONS.

h=${1:-$$}                      #  Nombre de recherche
                                #  Utiliser $PID comme nombre de recherche,
                                #+ si il n'est pas spécifié en argument de la
				#+ ligne de commande.

echo
echo "C($h) --- $MAX_ITERATIONS Iterations"
echo

for ((i=1; i<=MAX_ITERATIONS; i++))
do

echo -n "$h	"
#          ^^^^^
#           tab

  let "reste = h % 2"
  if [ "$reste" -eq 0 ]       # Pair?
  then
    let "h /= 2"              # Divise par 2.
  else
    let "h = h*3 + 1"         # Multiplie par 3 et ajoute 1.
  fi


COLONNES=10                   # Sortie avec 10 valeurs par ligne.
let "retour_ligne = i % $COLONNES"
if [ "$retour_ligne" -eq 0 ]
then
  echo
fi  

done

echo

#  Pour plus d'informations sur cette fonction mathématique,
#+ voir "Computers, Pattern, Chaos, and Beauty", par Pickover, p. 185 ff.,
#+ comme listé dans la bibliographie.

exit 0

Exemple A-8. days-between: Calculer le nombre de jours entre deux dates

#!/bin/bash
# days-between.sh:    Nombre de jours entre deux dates.
# Usage: ./days-between.sh [M]M/[D]D/AAAA [M]M/[D]D/AAAA

ARGS=2                # Deux arguments attendus en ligne de commande.
E_PARAM_ERR=65        # Erreur de paramètres.

ANNEEREF=1600         # Année de référence.
SIECLE=100
JEA=365
AJUST_DIY=367         # Ajusté pour l'année bisextile + fraction.
MEA=12
JEM=31
CYCLE=4

MAXRETVAL=256         # Valeur de retour positive la plus grande possible renvoyée
                      # par une fonction.

diff=		      # Déclaration d'une variable globale pour la différence de date.
value=                # Déclaration d'une variable globale pour la valeur absolue.
jour=                 # Déclaration de globales pour jour, mois, année.
mois=
annee=


Erreur_Param ()        # Mauvais paramètres en ligne de commande.
{
  echo "Usage: `basename $0` [M]M/[D]D/YYYY [M]M/[D]D/YYYY"
  echo "       (la date doit être supérieure au 1/3/1600)"
  exit $E_PARAM_ERR
}  


Analyse_Date ()                # Analyse la date à partir des paramètres en ligne de
{                              # commande.
  mois=${1%%/**}
  jm=${1%/**}                  # Jour et mois.
  jour=${dm#*/}
  let "annee = `basename $1`"  # Pas un nom de fichier mais fonctionne de la même façon.
}  


verifie_date ()                 # Vérifie la validité d'une date.
{
  [ "$jour" -gt "$JEM" ] || [ "$mois" -gt "$MEA" ] || [ "$annee" -lt "$ANNEEREF" ] && Erreur_Param
  # Sort du script si mauvaise(s) valeur(s).
  # Utilise une "liste-ou / liste-et".
  #
  # Exercice: Implémenter une vérification de date plus rigoureuse.
}


supprime_zero_devant () # Il est mieux de supprimer les zéros possibles
{                     # du jour et/ou du mois car sinon Bash va les
  val=${1#0}          # interpréter comme des valeurs octales
  return $val         # (POSIX.2, sect 2.9.2.1).
}


index_jour ()         # Formule de Gauss:
{                     # Nombre de jours du 3 Jan. 1600 jusqu'à la date passée
                      # en arguments.

  jour=$1
  mois=$2
  annee=$3

  let "mois = $mois - 2"
  if [ "$mois" -le 0 ]
  then
    let "mois += 12"
    let "annee -= 1"
  fi  

  let "annee -= $ANNEEREF"
  let "indexyr = $annee / $SIECLE"


  let "Jours = $JEA*$annee + $annee/$CYCLE - $indexyr + $indexyr/$CYCLE + $AJUST_DIY*$mois/$MEA + $jour - $JEM"
  # Pour une explication en détails de cet algorithme, voir
  # http://home.t-online.de/home/berndt.schwerdtfeger/cal.htm


  if [ "$Jours" -gt "$MAXRETVAL" ] # Si supérieur à 256,
  then                             # alors devient négatif
    let "dindex = 0 - $Jours"      # qui pourra être renvoyé par la fonction.
  else let "dindex = $Jours"
  fi

  return $dindex

}  


calcule_difference ()              # Différence entre les indices des jours.
{
  let "diff = $1 - $2"             # Variable globale.
}  


abs ()                             # Valeur absolut.
{                                  # Utilise une variable globale "valeur".
  if [ "$1" -lt 0 ]                # Si négatif
  then                             # alors
    let "value = 0 - $1"           # change de signe,
  else                             # sinon
    let "value = $1"               # on le laisse.
  fi
}



if [ $# -ne "$ARGS" ]            # Requiert deux arguments en ligne de commande.
then
  Erreur_Param
fi  

Analyse_Date $1
verifie_date $jour $mois $annee      # Vérifie si la date est valide.

supprime_zero_devant $jour           # Supprime tous zéros devant.
day=$?                               # sur le jour et/ou le mois.
supprime_zero_devant $mois
month=$?

index_jour $jour $mois $annee
date1=$?

abs $date1                         # S'assure que c'est positif en récupérant
date1=$value                       # la valeur absolue.

Analyse_Date $2
verifie_date $jour $mois $annee

supprime_zero_devant $jour
day=$?
supprime_zero_devant $mois
month=$?

index_jour $jour $mois $annee
date2=$?

abs $date2                         # S'assure que c'est positif.
date2=$value

calcule_difference $date1 $date2

abs $diff                          # S'assure que c'est positif.
diff=$value

echo $diff

exit 0
# Comparez ce script avec l'implémentation de la formule de Gauss en C sur
# http://buschencrew.hypermart.net/software/datedif

Exemple A-9. makedict: Créer un << dictionnaire >>

#!/bin/bash
# makedict.sh  [make dictionary]

# Modification du script /usr/sbin/mkdict.
# Script original copyright 1993, par Alec Muffett.
#
#  Ce script modifié inclus dans ce document d'une manière consistente avec le
#+ document "LICENSE" du paquetage "Crack" dont fait partie le script original.

#  Ce script manipule des fichiers texte pour produire une liste triée de mots
#+ trouvés dans les fichiers.
#  Ceci pourrait être utile pour compiler les dictionnaires et pour des
#+ recherches lexicographique.


E_MAUVAISARGS=65

if [ ! -r "$1" ]                     #  Au moins un argument, qui doit être
then                                 #+ un fichier valide.
	echo "Usage: $0 fichiers-à-manipuler"
  exit $E_MAUVAISARGS
fi  


# SORT="sort"                        #  Plus nécessaire de définir des options
                                     #+ pour sort. Modification du script
				     #+ original.

cat $* |                             # Contenu des fichiers spécifiés vers stdout.
        tr A-Z a-z |                 # Convertion en majuscule.
        tr ' ' '\012' |              #  Nouveau: modification des espaces en
                                     #+ retours chariot.
#       tr -cd '\012[a-z][0-9]' |    #  Suppression de tout ce qui n'est pas
                                     #  alphanumérique
                                     #+ (script original).
        tr -c '\012a-z'  '\012' |    #  Plutôt que de supprimer
                                     #+ modification des non alpha en retours
				     #+ chariot.
        sort |                       #  Les options $SORT ne sont plus
	                             #+ nécessaires maintenant.
        uniq |                       # Suppression des mots dupliqués.
        grep -v '^#' |               #  Suppression des lignes commençant avec
	                             #+ le symbole '#'.
        grep -v '^$'                 # Suppression des lignes blanches.

exit 0	

Exemple A-10. soundex: Conversion phonétique

#!/bin/bash
# soundex.sh: Calcule le code "soundex" pour des noms

# =======================================================
#        Script soundex
#              par
#         Mendel Cooper
#     thegrendel@theriver.com
#       23 Janvier 2002
#
#   Placé dans le domaine public.
#
#  Une version légèrement différente de ce script est apparu dans
#+ la colonne "Shell Corner" d'Ed Schaefer en juillet 2002
#+ du magazine en ligne "Unix Review",
#+ http://www.unixreview.com/documents/uni1026336632258/
# =======================================================


NBARGS=1                     # A besoin du nom comme argument.
E_MAUVAISARGS=70

if [ $# -ne "$NBARGS" ]
then
  echo "Usage: `basenom $0` nom"
  exit $E_MAUVAISARGS
fi  


affecte_valeur ()              #  Affecte une valeur numérique
{                              #+ aux lettres du nom.

  val1=bfpv                    # 'b,f,p,v' = 1
  val2=cgjkqsxz                # 'c,g,j,k,q,s,x,z' = 2
  val3=dt                      #  etc.
  val4=l
  val5=mn
  val6=r

# Une utilisation particulièrement intelligente de 'tr' suit.
# Essayez de comprendre ce qui se passe ici.

valeur=$( echo "$1" \
| tr -d wh \
| tr $val1 1 | tr $val2 2 | tr $val3 3 \
| tr $val4 4 | tr $val5 5 | tr $val6 6 \
| tr -s 123456 \
| tr -d aeiouy )

# Affecte des valeurs aux lettres.
# Supprime les numéros dupliqués, sauf s'ils sont séparés par des voyelles.
# Ignore les voyelles, sauf en tant que séparateurs, donc les supprime à la fin.
# Ignore 'w' et 'h', même en tant que séparateurs, donc les supprime au début.
#
# La substitution de commande ci-dessus utilise plus de tube qu'un plombier
# <g>.

}  


nom_en_entree="$1"
echo
echo "Nom = $nom_en_entree"


# Change tous les caractères en entrée par des minuscules.
# ------------------------------------------------
nom=$( echo $nom_en_entree | tr A-Z a-z )
# ------------------------------------------------
# Au cas où cet argument est un mixe de majuscules et de minuscules.


# Préfixe des codes soundex: première lettre du nom.
# --------------------------------------------


pos_caract=0                     # Initialise la position du caractère.
prefixe0=${nom:$pos_caract:1}
prefixe=`echo $prefixe0 | tr a-z A-Z`
                                 # Met en majuscule la première lettre de soundex.

let "pos_caract += 1"            # Aller directement au deuxième caractères.
nom1=${nom:$pos_caract}


# ++++++++++++++++++++++++++ Correctif Exception +++++++++++++++++++++++++++++++++
#  Maintenant, nous lançons à la fois le nom en entrée et le nom décalé d'un
#+ caractère vers la droite au travers de la fonction d'affectation de valeur.
#  Si nous obtenons la même valeur, cela signifie que les deux premiers
#+ caractères du nom ont la même valeur et que l'une d'elles doit être annulée.
#  Néanmoins, nous avons aussi besoin de tester si la première lettre du nom est
#+ une voyelle ou 'w' ou 'h', parce que sinon cela va poser problème.

caract1=`echo $prefixe | tr A-Z a-z`    # Première lettre du nom en minuscule.

affecte_valeur $nom
s1=$valeur
affecte_valeur $nom1
s2=$valeur
affecte_valeur $caract1
s3=$valeur
s3=9$s3                              #  Si la première lettre du nom est une
                                     #+ voyelle ou 'w' ou 'h',
                                     #+ alors sa "valeur" sera nulle (non
				     #+ initialisée).
				     #+ Donc, positionnons-la à 9, une autre
				     #+ valeur non utilisée, qui peut être
				     #+ vérifiée.


if [[ "$s1" -ne "$s2" || "$s3" -eq 9 ]]
then
  suffixe=$s2
else  
  suffixe=${s2:$pos_caract}
fi  
# ++++++++++++++++++++++ fin Correctif Exception +++++++++++++++++++++++++++++++++


fin=000                    # Utilisez au moins 3 zéro pour terminer.


soun=$prefixe$suffixe$fin  # Terminez avec des zéro.

LONGUEURMAX=4              # Tronquer un maximum de 4 caractères
soundex=${soun:0:$LONGUEURMAX}

echo "Soundex = $soundex"

echo

#  Le code soundex est une méthode d'indexage et de classification de noms
#+ en les groupant avec ceux qui sonnent de le même façon.
#  Le code soundex pour un nom donné est la première lettre de ce nom, suivi par
#+ un code calculé sur trois chiffres.
#  Des noms similaires devraient avoir les mêmes codes soundex

#   Exemples:
#   Smith et Smythe ont tous les deux le soundex "S-530"
#   Harrison = H-625
#   Hargison = H-622
#   Harriman = H-655

#  Ceci fonctionne assez bien en pratique mais il existe quelques anomalies.
#
#
#  Certaines agences du gouvernement U.S. utilisent soundex, comme le font les
#  généalogistes.
#
#  Pour plus d'informations, voir
#+ "National Archives and Records Administration home page",
#+ http://www.nara.gov/genealogy/soundex/soundex.html



# Exercice:
# --------
# Simplifier la section "Correctif Exception" de ce script.

exit 0

Exemple A-11. << life: Jeu de la Vie >>

#!/bin/bash
# life.sh: "Life in the Slow Lane"

# ##################################################################### #
# This is the Bash script version of John Conway's "Game of Life".      #
# "Life" is a simple implementation of cellular automata.               #
# --------------------------------------------------------------------- #
# On a rectangular grid, let each "cell" be either "living" or "dead".  #
# Designate a living cell with a dot, and a dead one with a blank space.#
#  Begin with an arbitrarily drawn dot-and-blank grid,                  #
#+ and let this be the starting generation, "generation 0".             #
# Determine each successive generation by the following rules:          #
# 1) Each cell has 8 neighbors, the adjoining cells                     #
#+   left, right, top, bottom, and the 4 diagonals.                     #
#                       123                                             #
#                       4*5                                             #
#                       678                                             #
#                                                                       #
# 2) A living cell with either 2 or 3 living neighbors remains alive.   #
# 3) A dead cell with 3 living neighbors becomes alive (a "birth").     #
SURVIVE=2                                                               #
BIRTH=3                                                                 #
# 4) All other cases result in dead cells.                              #
# ##################################################################### #


startfile=gen0   # Read the starting generation from the file "gen0".
                 # Default, if no other file specified when invoking script.
                 #
if [ -n "$1" ]   # Specify another "generation 0" file.
then
  if [ -e "$1" ] # Check for existence.
  then
    startfile="$1"
  fi  
fi  


ALIVE1=.
DEAD1=_
                 # Represent living and "dead" cells in the start-up file.

#  This script uses a 10 x 10 grid (may be increased,
#+ but a large grid will will cause very slow execution).
ROWS=10
COLS=10

GENERATIONS=10          #  How many generations to cycle through.
                        #  Adjust this upwards,
                        #+ if you have time on your hands.

NONE_ALIVE=80           #  Exit status on premature bailout,
                        #+ if no cells left alive.
TRUE=0
FALSE=1
ALIVE=0
DEAD=1

avar=                   #  Global; holds current generation.
generation=0            # Initialize generation count.

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


let "cells = $ROWS * $COLS"
                        # How many cells.

declare -a initial      # Arrays containing "cells".
declare -a current

display ()
{

alive=0                 # How many cells "alive".
                        # Initially zero.

declare -a arr
arr=( `echo "$1"` )     # Convert passed arg to array.

element_count=${#arr[*]}

local i
local rowcheck

for ((i=0; i<$element_count; i++))
do

  # Insert newline at end of each row.
  let "rowcheck = $i % ROWS"
  if [ "$rowcheck" -eq 0 ]
  then
    echo                # Newline.
    echo -n "      "    # Indent.
  fi  

  cell=${arr[i]}

  if [ "$cell" = . ]
  then
    let "alive += 1"
  fi  

  echo -n "$cell" | sed -e 's/_/ /g'
  # Print out array and change underscores to spaces.
done  

return

}

IsValid ()                            # Test whether cell coordinate valid.
{

  if [ -z "$1"  -o -z "$2" ]          # Mandatory arguments missing?
  then
    return $FALSE
  fi

local row
local lower_limit=0                   # Disallow negative coordinate.
local upper_limit
local left
local right

let "upper_limit = $ROWS * $COLS - 1" # Total number of cells.


if [ "$1" -lt "$lower_limit" -o "$1" -gt "$upper_limit" ]
then
  return $FALSE                       # Out of array bounds.
fi  

row=$2
let "left = $row * $ROWS"             # Left limit.
let "right = $left + $COLS - 1"       # Right limit.

if [ "$1" -lt "$left" -o "$1" -gt "$right" ]
then
  return $FALSE                       # Beyond row boundary.
fi  

return $TRUE                          # Valid coordinate.

}  


IsAlive ()              # Test whether cell is alive.
                        # Takes array, cell number, state of cell as arguments.
{
  GetCount "$1" $2      # Get alive cell count in neighborhood.
  local nhbd=$?


  if [ "$nhbd" -eq "$BIRTH" ]  # Alive in any case.
  then
    return $ALIVE
  fi

  if [ "$3" = "." -a "$nhbd" -eq "$SURVIVE" ]
  then                  # Alive only if previously alive.
    return $ALIVE
  fi  

  return $DEAD          # Default.

}  


GetCount ()             # Count live cells in passed cell's neighborhood.
                        # Two arguments needed:
			# $1) variable holding array
			# $2) cell number
{
  local cell_number=$2
  local array
  local top
  local center
  local bottom
  local r
  local row
  local i
  local t_top
  local t_cen
  local t_bot
  local count=0
  local ROW_NHBD=3

  array=( `echo "$1"` )

  let "top = $cell_number - $COLS - 1"    # Set up cell neighborhood.
  let "center = $cell_number - 1"
  let "bottom = $cell_number + $COLS - 1"
  let "r = $cell_number / $ROWS"

  for ((i=0; i<$ROW_NHBD; i++))           # Traverse from left to right. 
  do
    let "t_top = $top + $i"
    let "t_cen = $center + $i"
    let "t_bot = $bottom + $i"


    let "row = $r"                        # Count center row of neighborhood.
    IsValid $t_cen $row                   # Valid cell position?
    if [ $? -eq "$TRUE" ]
    then
      if [ ${array[$t_cen]} = "$ALIVE1" ] # Is it alive?
      then                                # Yes?
        let "count += 1"                  # Increment count.
      fi	
    fi  

    let "row = $r - 1"                    # Count top row.          
    IsValid $t_top $row
    if [ $? -eq "$TRUE" ]
    then
      if [ ${array[$t_top]} = "$ALIVE1" ] 
      then
        let "count += 1"
      fi	
    fi  

    let "row = $r + 1"                    # Count bottom row.
    IsValid $t_bot $row
    if [ $? -eq "$TRUE" ]
    then
      if [ ${array[$t_bot]} = "$ALIVE1" ] 
      then
        let "count += 1"
      fi	
    fi  

  done  


  if [ ${array[$cell_number]} = "$ALIVE1" ]
  then
    let "count -= 1"        #  Make sure value of tested cell itself
  fi                        #+ is not counted.


  return $count
  
}

next_gen ()               # Update generation array.
{

local array
local i=0

array=( `echo "$1"` )     # Convert passed arg to array.

while [ "$i" -lt "$cells" ]
do
  IsAlive "$1" $i ${array[$i]}   # Is cell alive?
  if [ $? -eq "$ALIVE" ]
  then                           #  If alive, then
    array[$i]=.                  #+ represent the cell as a period.
  else  
    array[$i]="_"                #  Otherwise underscore
   fi                            #+ (which will later be converted to space).  
  let "i += 1" 
done   


# let "generation += 1"   # Increment generation count.

# Set variable to pass as parameter to "display" function.
avar=`echo ${array[@]}`   # Convert array back to string variable.
display "$avar"           # Display it.
echo; echo
echo "Generation $generation -- $alive alive"

if [ "$alive" -eq 0 ]
then
  echo
  echo "Premature exit: no more cells alive!"
  exit $NONE_ALIVE        #  No point in continuing
fi                        #+ if no live cells.

}


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

# main ()

# Load initial array with contents of startup file.
initial=( `cat "$startfile" | sed -e '/#/d' | tr -d '\n' |\
sed -e 's/\./\. /g' -e 's/_/_ /g'` )
# Delete lines containing '#' comment character.
# Remove linefeeds and insert space between elements.

clear          # Clear screen.

echo #         Title
echo "======================="
echo "    $GENERATIONS generations"
echo "           of"
echo "\"Life in the Slow Lane\""
echo "======================="


# -------- Display first generation. --------
Gen0=`echo ${initial[@]}`
display "$Gen0"           # Display only.
echo; echo
echo "Generation $generation -- $alive alive"
# -------------------------------------------


let "generation += 1"     # Increment generation count.
echo

# ------- Display second generation. -------
Cur=`echo ${initial[@]}`
next_gen "$Cur"          # Update & display.
# ------------------------------------------

let "generation += 1"     # Increment generation count.

# ------ Main loop for displaying subsequent generations ------
while [ "$generation" -le "$GENERATIONS" ]
do
  Cur="$avar"
  next_gen "$Cur"
  let "generation += 1"
done
# ==============================================================

echo

exit 0

# --------------------------------------------------------------
# The grid in this script has a "boundary problem".
# The the top, bottom, and sides border on a void of dead cells.
# Exercise: Change the script to have the grid wrap around,
# +         so that the left and right sides will "touch",      
# +         as will the top and bottom.

Exemple A-12. Fichier de données pour le << Jeu de la Vie >>

# This is an example "generation 0" start-up file for "life.sh".
# --------------------------------------------------------------
#  The "gen0" file is a 10 x 10 grid using a period (.) for live cells,
#+ and an underscore (_) for dead ones. We cannot simply use spaces
#+ for dead cells in this file because of a peculiarity in Bash arrays.
#  [Exercise for the reader: explain this.]
#
# Lines beginning with a '#' are comments, and the script ignores them.
__.__..___
___._.____
____.___..
_._______.
____._____
..__...___
____._____
___...____
__.._..___
_..___..__

+++

Les deux scripts suivants sont de Mark Moraes de l'Université de Toronto. Voir le fichier joint << Moraes-COPYRIGHT >> pour les permissions et restrictions.

Exemple A-13. behead: Supprimer les en-têtes des courriers électroniques et des nouvelles

#! /bin/sh
# Supprime l'entête d'un message mail/news jusqu'à la première ligne vide.
# Mark Moraes, Université de Toronto

# ==> Ces commentaires sont ajoutés par l'auteur de ce document.

if [ $# -eq 0 ]; then
# ==> Si pas d'arguments en ligne de commande, alors fonctionne avec un
# ==> fichier redirigé vers stdin.
	sed -e '1,/^$/d' -e '/^[ 	]*$/d'
	# --> Supprime les lignes vides et les autres jusqu'à la première
	# --> commençant avec un espace blanc.
else
# ==> Si des arguments sont présents en ligne de commande, alors fonctionne avec
# ==> des fichiers nommés.
	for i do
		sed -e '1,/^$/d' -e '/^[ 	]*$/d' $i
		# --> De même.
	done
fi

# ==> Exercice: Ajouter la vérification d'erreurs et d'autres options.
# ==>
# ==> Notez que le petit script sed se répère à l'exception des arguments
# ==> passés.
# ==> Est-il intéressant de l'embarquer dans une fonction? Pourquoi?

Exemple A-14. ftpget: Télécharger des fichiers via ftp

#! /bin/sh 
# $Id: ftpget.sh,v 1.2 2003/11/02 17:21:35 guillaume Exp $ 
# Script pour réaliser une suite d'actions avec un ftp anonyme. Généralement,
# convertit une liste d'arguments de la ligne de commande en entrée vers ftp.
# Simple et rapide - écrit comme compagnon de ftplist 
# -h spécifie l'hôte distant (par défaut prep.ai.mit.edu) 
# -d spécifie le répertoire distant où se déplacer - vous pouvez spécifier une
# séquence d'options -d - elles seront exécutées chacune leur tour. Si les
# chemins sont relatifs, assurez-vous d'avoir la bonne séquence. Attention aux
# chemins relatifs, il existe bien trop de liens symboliques de nos jours.
# (par défaut, le répertoire au moment de la connexion)
# -v active l'option verbeux de ftp et affiche toutes les réponses du serveur
# ftp
# -f fichierdistant[:fichierlocal] récupère le fichier distant et le renomme en
# localfile 
# -m modele fait un mget suivant le modèle spécifié. Rappelez-vous de mettre
# entre guillemets les caractères shell.
# -c fait un cd local vers le répertoire spécifié
# Par exemple example, 
# 	ftpget -h expo.lcs.mit.edu -d contrib -f xplaces.shar:xplaces.sh \
#		-d ../pub/R3/fixes -c ~/fixes -m 'fix*' 
# récupèrera xplaces.shar à partir de ~ftp/contrib sur expo.lcs.mit.edu et
# l'enregistrera sous xplaces.sh dans le répertoire actuel, puis obtiendra
# tous les correctifs de ~ftp/pub/R3/fixes en les plaçant sous le répertoire
# ~/fixes.
# De façon évidente, la séquence des options est importante, car les commandes
# équivalentes sont exécutées par ftp dans le même ordre.
#
# Mark Moraes (moraes@csri.toronto.edu), Feb 1, 1989 
# ==> Les signes inférieur et supérieur ont été modifiés par des "parens" pour
# ==> éviter des soucis avec DocBook.
#


# ==> Ces commentaires ont été ajoutés par l'auteur de ce document.

# PATH=/local/bin:/usr/ucb:/usr/bin:/bin
# export PATH
# ==> Les deux lignes ci-dessus faisaient parti du script original et étaient
# ==> probablement inutiles

FICHIER_TEMPORAIRE=/tmp/ftp.$$
# ==> Crée un fichier temporaire, en utilisant l'identifiant du processus du
# ==> script ($$) pour construire le nom du fichier.

SITE=`domainname`.toronto.edu
# ==> 'domainname' est similaire à 'hostname'
# ==> Ceci pourrait être réécrit en ajoutant un paramètre ce qui rendrait son
# ==> utilisation plus générale.

usage="Usage: $0 [-h hotedisrant] [-d repertoiredistant]... [-f fichierdistant:fichierlocal]... \
		[-c repertoirelocal] [-m modele] [-v]"
optionsftp="-i -n"
verbflag=
set -f 		# So we can use globbing in -m
set x `getopt vh:d:c:m:f: $*`
if [ $? != 0 ]; then
	echo $usage
	exit 65
fi
shift
trap 'rm -f ${FICHIER_TEMPORAIRE} ; exit' 0 1 2 3 15
echo "user anonymous ${USER-gnu}@${SITE} > ${FICHIER_TEMPORAIRE}"
# ==> Ajout des guillemets (recommendé pour les echo complexes).
echo binary >> ${FICHIER_TEMPORAIRE}
for i in $*   # ==> Analyse les arguments de la ligne de commande.
do
	case $i in
	-v) verbflag=-v; echo hash >> ${FICHIER_TEMPORAIRE}; shift;;
	-h) hotedistant=$2; shift 2;;
	-d) echo cd $2 >> ${FICHIER_TEMPORAIRE}; 
	    if [ x${verbflag} != x ]; then
	        echo pwd >> ${FICHIER_TEMPORAIRE};
	    fi;
	    shift 2;;
	-c) echo lcd $2 >> ${FICHIER_TEMPORAIRE}; shift 2;;
	-m) echo mget "$2" >> ${FICHIER_TEMPORAIRE}; shift 2;;
	-f) f1=`expr "$2" : "\([^:]*\).*"`; f2=`expr "$2" : "[^:]*:\(.*\)"`;
	    echo get ${f1} ${f2} >> ${FICHIER_TEMPORAIRE}; shift 2;;
	--) shift; break;;
	esac
done
if [ $# -ne 0 ]; then
	echo $usage
	exit 65   # ==> Modifier de l'"exit 2" pour se conformer avec le standard.
fi
if [ x${verbflag} != x ]; then
	optionsftp="${optionsftp} -v"
fi
if [ x${hotedistant} = x ]; then
	hotedistant=prep.ai.mit.edu
	# ==> A réécrire pour utiliser votre site ftp favori.
fi
echo quit >> ${FICHIER_TEMPORAIRE}
# ==> Toutes les commandes sont sauvegardées dans fichier_temporaire.

ftp ${optionsftp} ${hotedistant} < ${FICHIER_TEMPORAIRE}
# ==> Maintenant, exécution par ftp de toutes les commandes contenues dans le
# ==> fichier fichier_temporaire.

rm -f ${FICHIER_TEMPORAIRE}
# ==> Enfin, fichier_temporaire est supprimé (vous pouvez souhaiter le copier
# ==> dans un journal).


# ==> Exercices:
# ==> ---------
# ==> 1) Ajouter une vérification d'erreurs.
# ==> 2) Ajouter des tas de trucs.

+

Antek Sawicki a contribué avec le script suivant, qui fait une utilisation très intelligente des opérateurs de substitution de paramètres discutés dans la Section 9.3.

Exemple A-15. password: Générer des mots de passe aléatoires de 8 caractères

#!/bin/bash
#  Pourrait avoir besoin d'être appelé avec un #!/bin/bash2 sur les anciennes
#+ machines.
#
#  Générateur de mots de passe aléatoires pour bash 2.x
#+ par Antek Sawicki <tenox@tenox.tc>,
#  qui a généreusement permis à l'auteur de ce document de l'utiliser ici.
#
# ==> Commentaires ajoutés par l'auteur du document ==>


MATRICE="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
LONGUEUR="8"
# ==> Modification possible de 'LONGUEUR' pour des mots de passe plus longs.


while [ "${n:=1}" -le "$LONGUEUR" ]
# ==> Rappelez-vous que := est l'opérateur de "substitution par défaut".
# ==> Donc, si 'n' n'a pas été initialisé, l'initialisez à 1.
do
	PASS="$PASS${MATRICE:$(($RANDOM%${#MATRICE})):1}"
	# ==> Très intelligent, pratiquement trop astucieux.

	# ==> Commençons par le plus intégré...
	# ==> ${#MATRICE} renvoie la longueur du tableau MATRICE.

	# ==> $RANDOM%${#MATRICE} renvoie un nombre aléatoire entre 1 et la
	# ==> longueur de MATRICE - 1.

	# ==> ${MATRICE:$(($RANDOM%${#MATRICE})):1}
	# ==> renvoie l'expansion de MATRICE à une position aléatoire, par
	# ==> longueur 1. 
	# ==> Voir la substitution de paramètres {var:pos:len}, section 3.3.1
	# ==> et les exemples suivants.

	# ==> PASS=... copie simplement ce résultat dans PASS (concaténation).

	# ==> Pour mieux visualiser ceci, décommentez la ligne suivante
	# ==>             echo "$PASS"
	# ==> pour voir la construction de PASS, un caractère à la fois,
	# ==> à chaque itération de la boucle.

	let n+=1
	# ==> Incrémentez 'n' pour le prochain tour.
done

echo "$PASS"      # ==> Ou, redirigez le fichier, comme voulu.

exit 0

+

James R. Van Zandt a contribué avec ce script, qui utilise les tubes nommés et, ce sont ses mots, << really exercises quoting and escaping >>.

Exemple A-16. fifo: Faire des sauvegardes journalières, en utilisant des tubes nommés

#!/bin/bash
# ==> Script de James R. Van Zandt, et utilisé ici avec sa permission.

# ==> Commentaires ajoutés par l'auteur de ce document.

  
  ICI=`uname -n`    # ==> nom d'hôte
  LA_BAS=bilbo
  echo "début de la sauvegarde distabte vers $LA_BAS à `date +%r`"
  # ==> `date +%r` renvoie l'heure en un format sur 12 heures, par exempe
  # ==> "08:08:34 PM".
  
  #  Assurez-vous que /pipe est réellement un tube et non pas un fichier
  #+ standard.
  rm -rf /tube
  mkfifo /tube       # ==> Crée un fichier "tube nommé", nommé "/tube".
  
  # ==> 'su xyz' lance les commandes en tant qu'utilisateur "xyz".
  # ==> 'ssh' appele le shell sécurisé (client de connexion à distance).
  su xyz -c "ssh $LA_BAS \"cat >/home/xyz/sauve/${ICI}-jour.tar.gz\" < /tube"&
  cd /
  tar -czf - bin boot dev etc home info lib man root sbin share usr var >/tube
  # ==> Utilise un tube nommé, /tube, pour communiquer entre processus:
  # ==> 'tar/gzip' écrit dans le tube et 'ssh' lit /tube.

  #  ==> Le résultat final est que cela sauvegarde les répertoires principaux;
  #+ ==> à partir de /.

  # ==> Quels sont les avantages d'un "tube nommé" dans cette situation,
  # ==> en opposition avec le "tube anonyme", avec |?
  # ==> Est-ce qu'un tube anonyme pourrait fonctionner ici?


  exit 0

+

Stephane Chazelas a contribué avec le script suivant pour démontrer que générer des nombres aléatoires ne requiert pas de tableaux.

Exemple A-17. primes: Générer des nombres aléatoires en utilisant l'opérateur modulo

#!/bin/bash
# primes.sh: Génère des nombres premiers, sans utiliser des tableaux.
# Script contribué par Stephane Chazelas.

#  Il n'utilise *pas* l'algorithme classique "Sieve of Eratosthenes",
#+ mais utilise à la palce la méthode plus intuitive de test de chaque nombre
#+ candidat pour les facteurs (diviseurs), en utilisant l'opérateur modulo "%".


LIMITE=1000                    # Premiers de 2 à 1000

Premiers()
{
 (( n = $1 + 1 ))             # Va au prochain entier.
 shift                        # Prochain paramètre dans la liste.
#  echo "_n=$n i=$i_"
 
 if (( n == LIMITE ))
 then echo $*
 return
 fi

 for i; do                    #  "i" est initialisé à "@", les précédentes
                              #+ valeurs de $n.
#   echo "-n=$n i=$i-"
   (( i * i > n )) && break   # Optimisation.
   (( n % i )) && continue    #  Passe les non premiers en utilisant l'opérateur
                              #+ modulo.
   Premiers $n $@             # Récursion à l'intérieur de la boucle.
   return
   done

   Premiers $n $@ $n          # Récursion à l'extérieur de la boucle.
                              #  Accumule successivement les paramètres de
			      #+ position.
                              # "$@" est la liste des premiers accumulés.
}

Premiers 1

exit 0

# Décommenter les lignes 17 et 25 pour vous aider à comprendre ce qui se passe.

# Comparez la vitesse de cet algorithme de génération des nombres premiers avec
# celui de "Sieve of Eratosthenes" (ex68.sh).

# Exercice: Réécrivez ce script sans récursion, pour une exécution plus rapide.

+

Jordi Sanfeliu a donné sa permission pour utiliser son élégant script sur les arborescences.

Exemple A-18. tree: Afficher l'arborescence d'un répertoire

#!/bin/sh
#         @(#) tree      1.1  30/11/95       by Jordi Sanfeliu
#                                         email: mikaku@arrakis.es
#
#     Version initiale :  1.0  30/11/95
#     Prochaine version:  1.1  24/02/97   Maintenant avec des liens symboliques
#     Corrigé par      :  Ian Kjos, pour supporter les répertoires non dispo
#                         email: beth13@mail.utexas.edu
#
#         Tree est un outil pour visualiser la hiérarchie d'un répertoire
#

# ==> Le script 'Tree' utilisé ici avec la permission de son auteur, Jordi Sanfeliu.
# ==> Commentaires ajoutés par l'auteur de ce document.
# ==> Ajout des guillemets pour les arguments.


search () {
   for dir in `echo *`
   # ==> `echo *` affiche tous les fichiers du répertoire actuel sans retour à
   # ==> la ligne.
   # ==> Même effet que     for dir in *
   # ==> mais "dir in `echo *`" ne gère pas les noms de fichiers comprenant des
   # ==> espaces blancs.
   do
      if [ -d "$dir" ] ; then   # ==> S'il s'agit d'un répertoire (-d)...
         zz=0   # ==> Variable temporaire, pour garder trace du niveau de
                # ==> répertoire.
         while [ $zz != $deep ]    # Conserve la trace de la boucle interne.
         do
            echo -n "|   "    # ==> Affiche le symbôle du connecteur vertical
	                      # ==> avec 2 espaces mais pas de retour à la ligne
                              # ==> pour l'indentation.
            zz=`expr $zz + 1` # ==> Incrémente zz.
         done
         if [ -L "$dir" ] ; then   # ==> Si le répertoire est un lien symbolique...
            echo "+---$dir" `ls -l $dir | sed 's/^.*'$dir' //'`
	    # ==> Affiche le connecteur horizontal et affiche le nom du
            # ==> répertoire mais...
	    # ==> supprime la partie date/heure.
         else
            echo "+---$dir"      # ==> Affiche le symbole du connecteur
                                 # ==> horizontal et le nom du répertoire.
            if cd "$dir" ; then  # ==> S'il peut se déplacer dans le sous-répertoire...
               deep=`expr $deep + 1`   # ==> Incrémente la profondeur.
               search     # avec la récursivité ;-)
	                  # ==> La fonction s'appelle elle-même.
               numdirs=`expr $numdirs + 1`   # ==> Incrémente le compteur de
	                                     # ==> répertoires.
            fi
         fi
      fi
   done
   cd ..   # ==> Se placer un niveau au-dessus.
   if [ "$deep" ] ; then  # ==> Si la profondeur est nulle (renvoie TRUE)...
      swfi=1              # ==> initialiser l'indicateur indiquant que la
                          # ==> recherche est terminée.
   fi
   deep=`expr $deep - 1`  # ==> Décrémente la profondeur.
}

# - Principal -
if [ $# = 0 ] ; then
   cd `pwd` # ==> Pas d'arguments au script, alors utilise le répertoire actuel.
else
   cd $1    # ==> Sinon, va dans le répertoire indiqué.
fi
echo "Répertoire initial = `pwd`"
swfi=0      # ==> Indicateur de terminaison.
deep=0      # ==> Profondeur de la liste.
numdirs=0
zz=0

while [ "$swfi" != 1 ]   # Tant que l'indicateur n'est pas initialisé
do
   search   # ==> Appelle la fonctione après avoir initialisé les variables.
done
echo "Nombre total de répertoires = $numdirs"

exit 0
# ==> Challenge: essayez de comprendre comment fonctionne ce script

+

Noah Friedman a donné sa permission pour utiliser son script contenant des fonctions sur les chaînes de caractères, qui reproduit les fonctions de manipulations de la bibliothèque C string.

Exemple A-19. string: Manipuler les chaînes de caractères comme en C

#!/bin/bash

# string.bash --- bash emulation of string(3) library routines
# Author: Noah Friedman <friedman@prep.ai.mit.edu>
# ==>     Used with his kind permission in this document.
# Created: 1992-07-01
# Last modified: 1993-09-29
# Public domain

# Conversion to bash v2 syntax done by Chet Ramey

# Commentary:
# Code:

#:docstring strcat:
# Usage: strcat s1 s2
#
# Strcat appends the value of variable s2 to variable s1. 
#
# Example:
#    a="foo"
#    b="bar"
#    strcat a b
#    echo $a
#    => foobar
#
#:end docstring:

###;;;autoload   ==> Autoloading of function commented out.
function strcat ()
{
    local s1_val s2_val

    s1_val=${!1}                        # indirect variable expansion
    s2_val=${!2}
    eval "$1"=\'"${s1_val}${s2_val}"\'
    # ==> eval $1='${s1_val}${s2_val}' avoids problems,
    # ==> if one of the variables contains a single quote.
}

#:docstring strncat:
# Usage: strncat s1 s2 $n
# 
# Line strcat, but strncat appends a maximum of n characters from the value
# of variable s2.  It copies fewer if the value of variabl s2 is shorter
# than n characters.  Echoes result on stdout.
#
# Example:
#    a=foo
#    b=barbaz
#    strncat a b 3
#    echo $a
#    => foobar
#
#:end docstring:

###;;;autoload
function strncat ()
{
    local s1="$1"
    local s2="$2"
    local -i n="$3"
    local s1_val s2_val

    s1_val=${!s1}                       # ==> indirect variable expansion
    s2_val=${!s2}

    if [ ${#s2_val} -gt ${n} ]; then
       s2_val=${s2_val:0:$n}            # ==> substring extraction
    fi

    eval "$s1"=\'"${s1_val}${s2_val}"\'
    # ==> eval $1='${s1_val}${s2_val}' avoids problems,
    # ==> if one of the variables contains a single quote.
}

#:docstring strcmp:
# Usage: strcmp $s1 $s2
#
# Strcmp compares its arguments and returns an integer less than, equal to,
# or greater than zero, depending on whether string s1 is lexicographically
# less than, equal to, or greater than string s2.
#:end docstring:

###;;;autoload
function strcmp ()
{
    [ "$1" = "$2" ] && return 0

    [ "${1}" '<' "${2}" ] > /dev/null && return -1

    return 1
}

#:docstring strncmp:
# Usage: strncmp $s1 $s2 $n
# 
# Like strcmp, but makes the comparison by examining a maximum of n
# characters (n less than or equal to zero yields equality).
#:end docstring:

###;;;autoload
function strncmp ()
{
    if [ -z "${3}" -o "${3}" -le "0" ]; then
       return 0
    fi
   
    if [ ${3} -ge ${#1} -a ${3} -ge ${#2} ]; then
       strcmp "$1" "$2"
       return $?
    else
       s1=${1:0:$3}
       s2=${2:0:$3}
       strcmp $s1 $s2
       return $?
    fi
}

#:docstring strlen:
# Usage: strlen s
#
# Strlen returns the number of characters in string literal s.
#:end docstring:

###;;;autoload
function strlen ()
{
    eval echo "\${#${1}}"
    # ==> Returns the length of the value of the variable
    # ==> whose name is passed as an argument.
}

#:docstring strspn:
# Usage: strspn $s1 $s2
# 
# Strspn returns the length of the maximum initial segment of string s1,
# which consists entirely of characters from string s2.
#:end docstring:

###;;;autoload
function strspn ()
{
    # Unsetting IFS allows whitespace to be handled as normal chars. 
    local IFS=
    local result="${1%%[!${2}]*}"
 
    echo ${#result}
}

#:docstring strcspn:
# Usage: strcspn $s1 $s2
#
# Strcspn returns the length of the maximum initial segment of string s1,
# which consists entirely of characters not from string s2.
#:end docstring:

###;;;autoload
function strcspn ()
{
    # Unsetting IFS allows whitspace to be handled as normal chars. 
    local IFS=
    local result="${1%%[${2}]*}"
 
    echo ${#result}
}

#:docstring strstr:
# Usage: strstr s1 s2
# 
# Strstr echoes a substring starting at the first occurrence of string s2 in
# string s1, or nothing if s2 does not occur in the string.  If s2 points to
# a string of zero length, strstr echoes s1.
#:end docstring:

###;;;autoload
function strstr ()
{
    # if s2 points to a string of zero length, strstr echoes s1
    [ ${#2} -eq 0 ] && { echo "$1" ; return 0; }

    # strstr echoes nothing if s2 does not occur in s1
    case "$1" in
    *$2*) ;;
    *) return 1;;
    esac

    # use the pattern matching code to strip off the match and everything
    # following it
    first=${1/$2*/}

    # then strip off the first unmatched portion of the string
    echo "${1##$first}"
}

#:docstring strtok:
# Usage: strtok s1 s2
#
# Strtok considers the string s1 to consist of a sequence of zero or more
# text tokens separated by spans of one or more characters from the
# separator string s2.  The first call (with a non-empty string s1
# specified) echoes a string consisting of the first token on stdout. The
# function keeps track of its position in the string s1 between separate
# calls, so that subsequent calls made with the first argument an empty
# string will work through the string immediately following that token.  In
# this way subsequent calls will work through the string s1 until no tokens
# remain.  The separator string s2 may be different from call to call.
# When no token remains in s1, an empty value is echoed on stdout.
#:end docstring:

###;;;autoload
function strtok ()
{
 :
}

#:docstring strtrunc:
# Usage: strtrunc $n $s1 {$s2} {$...}
#
# Used by many functions like strncmp to truncate arguments for comparison.
# Echoes the first n characters of each string s1 s2 ... on stdout. 
#:end docstring:

###;;;autoload
function strtrunc ()
{
    n=$1 ; shift
    for z; do
        echo "${z:0:$n}"
    done
}

# provide string

# string.bash ends here


# ========================================================================== #
# ==> Everything below here added by the document author.

# ==> Suggested use of this script is to delete everything below here,
# ==> and "source" this file into your own scripts.

# strcat
string0=one
string1=two
echo
echo "Testing \"strcat\" function:"
echo "Original \"string0\" = $string0"
echo "\"string1\" = $string1"
strcat string0 string1
echo "New \"string0\" = $string0"
echo

# strlen
echo
echo "Testing \"strlen\" function:"
str=123456789
echo "\"str\" = $str"
echo -n "Length of \"str\" = "
strlen str
echo



# Exercise:
# --------
# Add code to test all the other string functions above.


exit 0

+

+

Stephane Chazelas montre la programmation objet dans un script Bash.

Exemple A-20. obj-oriented: Bases de données orientées objet

#!/bin/bash
# obj-oriented.sh: programmation orienté objet dans un script shell.
# Script par Stephane Chazelas.


person.new()        # Ressemble à la déclaration d'une classe en C++.
{
  local nom_objet=$1 nom=$2 prenom=$3 datenaissance=$4

  eval "$nom_objet.set_nom() {
          eval \"$nom_objet.get_nom() {
                   echo \$1
                 }\"
        }"

  eval "$nom_objet.set_prenom() {
          eval \"$nom_objet.get_prenom() {
                   echo \$1
                 }\"
        }"

  eval "$nom_objet.set_datenaissance() {
          eval \"$nom_objet.get_datenaissance() {
            echo \$1
          }\"
          eval \"$nom_objet.show_datenaissance() {
            echo \$(date -d \"1/1/1970 0:0:\$1 GMT\")
          }\"
          eval \"$nom_objet.get_age() {
            echo \$(( (\$(date +%s) - \$1) / 3600 / 24 / 365 ))
          }\"
        }"

  $nom_objet.set_nom $nom
  $nom_objet.set_prenom $prenom
  $nom_objet.set_datenaissance $datenaissance
}

echo

person.new self Bozeman Bozo 101272413
#  Crée une instance de "person.new" (en fait, passe les arguments à la
#+ fonction).

self.get_prenom              #   Bozo
self.get_nom                 #   Bozeman
self.get_age                 #   28
self.get_datenaissance       #   101272413
self.show_datenaissance      #   Sat Mar 17 20:13:33 MST 1973

echo

# typeset -f
# pour voir les fonctions créées (attention, cela fait défiler la page).

exit 0