Ces scripts, bien que ne rentrant pas dans le texte de ce document, illustrent quelques techniques intéressantes de programmation shell. Ils sont aussi utiles. Amusez-vous à les analyser et à les lancer.
Exemple A-1. manview: Visualiser des pages man formatées
#!/bin/bash # manview.sh: Formatte la source d'une page man pour une visualisation. # Ceci est utile lors de l'écriture de la source d'une page man et que vous #+ voulez voir les résultats intermédiaires lors de votre travail. E_MAUVAISARGS=65 if [ -z "$1" ] then echo "Usage: `basename $0` nomfichier" exit $E_MAUVAISARGS fi groff -Tascii -man $1 | less # De la page man de groff. # Si la page man inclut des tables et/ou des équations, # alors le code ci-dessus échouera. # La ligne suivant peut gérer de tels cas. # # gtbl < "$1" | geqn -Tlatin1 | groff -Tlatin1 -mtty-char -man # # Merci, S.C. exit 0 |
Exemple A-2. mailformat: Formater un courrier électronique
#!/bin/bash
# mail-format.sh: Formatte les courriers électroniques.
# Supprime les caractères '>', les tabulations et coupe les lignes
#+ excessivement longues.
# =================================================================
# Vérification standard des argument(s) du script
ARGS=1
E_MAUVAISARGS=65
E_PASDEFICHIER=66
if [ $# -ne $ARGS ] # Le bon nombre d'arguments a-t'il été passé au script?
then
echo "Usage: `basename $0` nomfichier"
exit $E_MAUVAISARGS
fi
if [ -f "$1" ] # Vérifie si le fichier existe.
then
nomfichier=$1
else
echo "Le fichier \"$1\" n'existe pas."
exit $E_PASDEFICHIER
fi
# =================================================================
LONGUEUR_MAX=70 # Longueur à partir de laquelle on coupe les lignes.
# Suppression du caractère '>', des tabulations en début de ligne
#+ et coupage des lignes après $LONGUEUR_MAX caractères.
sed '
s/^>//
s/^ *>//
s/^ *//
s/ *//
' $1 | fold -s --width=$LONGUEUR_MAX
# L'option -s de "fold" coupe les lignes à un espace blanc, si
#+ possible.
# Ce script a été inspiré par un article d'un journal bien connu
#+ proposant un utilitaire Windows de 164Ko pour les mêmes fonctionnalités.
#
# Un joli ensemble d'utilitaire de manipulation de texte et un langage de
#+ scripts efficace apportent une alternative à des exécutables gonflés.
exit 0 |
Exemple A-3. rn: Un utilitaire simple pour renommer des fichiers
Ce script est une modification de l'Exemple 12-15.
#! /bin/bash
#
# Un très simplifié "renommeur" de fichiers (basé sur "lowercase.sh").
#
# L'utilitaire "ren", par Vladimir Lanin (lanin@csd2.nyu.edu),
#+ fait un bien meilleur travail que ceci.
ARGS=2
E_MAUVAISARGS=65
UN=1 # Pour avoir correctement singulier ou pluriel
# (voir plus bas.)
if [ $# -ne "$ARGS" ]
then
echo "Usage: `basename $0` ancien-modele nouveau-modele"
# Comme avec "rn gif jpg", qui renomme tous les fichiers gif du répertoire
#+ courant en jpg.
exit $E_MAUVAISARGS
fi
nombre=0 # Garde la trace du nombre de fichiers renommés.
for fichier in *$1* # Vérifie tous les fichiers correspondant du répertoire.
do
if [ -f "$fichier" ] # If finds match...
then
fname=`basename $fichier` # Supprime le chemin.
n=`echo $fname | sed -e "s/$1/$2/"` # Substitue nouveau par ancien dans
# le fichier.
mv $fname $n # Renomme.
let "nombre += 1"
fi
done
if [ "$nombre" -eq "$UN" ] # Pour une bonne grammaire.
then
echo "$nombre fichier renommé."
else
echo "$nombre fichiers renommés."
fi
exit 0
# Exercices:
# ---------
# Avec quel type de fichiers cela ne fonctionnera pas?
# Comment corriger cela?
#
# Réécrire ce script pour travailler sur tous les fichiers d'un répertoire,
#+ contenant des espaces dans leur noms, et en les renommant après avoir
#+ substitué chaque espace par un tiret bas. |
Exemple A-4. blank-rename: Renommer les fichiers dont le nom contient des espaces
C'est une version encore plus simple du script précédent.
#! /bin/bash
# blank-rename.sh
#
# Substitue les tirets soulignés par des blancs dans tous les fichiers d'un
#+ répertoire.
UN=1 # Pour obtenir le singulier/puriel correctement (voir
# plus bas).
nombre=0 # Garde trace du nombre de fichiers renommés.
TROUVE=0 # Valeur de retour en cas de succès.
for fichier in * #Traverse tous les fichiers du répertoire.
do
echo "$fichier" | grep -q " " # Vérifie si le nom du fichier
if [ $? -eq $TROUVE ] #+ contient des espace(s).
then
nomf=$fichier # Supprime le chemin.
n=`echo $nomf | sed -e "s/ /_/g"` # Remplace l'espace par un tiret.
mv "$nomf" "$n" # Réalise le renommage.
let "nombre += 1"
fi
done
if [ "$nombre" -eq "$UN" ] # Pour une bonne grammaire.
then
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 |