12.9. Commandes diverses

Commandes qui ne peuvent être classées

jot, seq

Ces outils génèrent des séquences de nombres entiers avec une incrémentation choisie par l'utilisateur.

Le retour à la ligne qui sépare habituellement les entiers peut être modifié avec l'option -s.

 bash$ seq 5
 1
 2
 3
 4
 5



 bash$ seq -s : 5
 1:2:3:4:5
 

jot et seq sont fort pratiques pour les boucles.

Exemple 12-39. Utiliser seq pour générer l'incrément d'une boucle

#!/bin/bash
# Utiliser "seq"

echo

for a in `seq 80`  # ou   for a in $( seq 80 )
# Identique à   for a in 1 2 3 4 5 ... 80   (évite beaucoup de frappe!).
# Pourrait aussi utiliser 'jot' (si présent sur le système).
do
  echo -n "$a "
done      # 1 2 3 4 5 ... 80
# Exemple d'utilisation de la sortie d'une commande pour générer la [liste]
# dans une boucle "for".

echo; echo


COMPTEUR=80  # Oui, 'seq' peut aussi prendre un compteur remplaçable.

for a in `seq $COMPTEUR`  # ou   for a in $( seq $COMPTEUR )
do
  echo -n "$a "
done      # 1 2 3 4 5 ... 80

echo; echo

DEBUT=75
FIN=80

for a in `seq $DEBUT $FIN`
#  Donner à "seq" deux arguments permet de commencer le comptage au premier et
#+ de le terminer au second.
do
  echo -n "$a "
done      # 75 76 77 78 79 80

echo; echo

DEBUT=45
INTERVALLE=5
FIN=80

for a in `seq $DEBUT $INTERVALLE $FIN`
#  Donner à "seq" trois arguments permet de commencer le comptage au premier,
#+ d'utiliser le deuxième comme intervalle et de le terminer au troisième.
do
  echo -n "$a "
done      # 45 50 55 60 65 70 75 80

echo; echo

exit 0
getopt

La commande getopt analyse les paramètres de la ligne de commande précédés par un tiret. Cette commande externe correspond à une version moins souple de la commande interne getopts .

Exemple 12-40. Utiliser getopt pour analyser les paramètres de la ligne de commande

#!/bin/bash

# Essayez ce qui suit lors de l'appel à ce script.
#   sh ex33a -a
#   sh ex33a -abc
#   sh ex33a -a -b -c
#   sh ex33a -d
#   sh ex33a -dXYZ
#   sh ex33a -d XYZ
#   sh ex33a -abcd
#   sh ex33a -abcdZ
#   sh ex33a -z
#   sh ex33a a
# Expliquez les résultats de chacun.

E_OPTERR=65

if [ "$#" -eq 0 ]
then   # Le script a besoin d'un argument en ligne de commande.
  echo "Usage $0 -[options a,b,c]"
  exit $E_OPTERR
fi  

set -- `getopt "abcd:" "$@"`
#  Positionne les paramètres de position par rapport aux arguments en ligne de
#+ commandes.
# Qu'arrive-t'il si vous utilisez "$*" au lieu de "$@"?

while [ ! -z "$1" ]
do
  case "$1" in
    -a) echo "Option \"a\"";;
    -b) echo "Option \"b\"";;
    -c) echo "Option \"c\"";;
    -d) echo "Option \"d\" $2";;
     *) break;;
  esac

  shift
done

#  Il est mieux d'utiliser la commande intégrée 'getopts' dans un script,
#+ plutôt que 'getopt'.
#  Voir "ex33.sh".

exit 0
run-parts

La commande run-parts [1] exécutent tous les scripts d'un répertoire cible triés par ordre ASCII. Evidement, ces scripts nécessitent les droits d'exécution.

Le démon crond lance run-parts pour exécuter les scripts du répertoire /etc/cron.*.

yes

Par défaut , la commande yes envoie une suite infinie de lettres y suivies de retours à la ligne sur stdout. Un ctrl-c arrête l'éxécution. Une chaîne différente peut être spécifiée en argument (yes chaine_differente affichera continuellement chaine_differente sur stdout). On pourrait se demander l'intérêt de la chose. En pratique, yes peut être utilisé comme un expect minimaliste en étant redirigé vers un programme en attente d'une saisie expect.

yes | fsck /dev/hda1 confirme toutes les réparations à fsck (méfiance!).

yes | rm -r dirname aura le même effet que rm -rf dirname (toujours méfiance!).

Avertissement

La plus grande prudence est conseillée lorsque vous redirigez yes vers une commande potentiellement dangereuse pour votre système, comme fsck ou fdisk. Cela pourrait avoir des effets secondaires inattendus.

banner

affiche les paramètres sur stdout comme une grande bannière verticale en utilisant un symbole ASCII ( # par défaut ). On peut rediriger cette sortie vers l'imprimante pour obtenir une copie papier.

printenv

Montre toutes les variables d'environnement réglées pour un utilisateur donné.

bash$ printenv | grep HOME
 HOME=/home/bozo
 

lp

Les commandes lp et lpr envoient un (des) fichier(s) à la file d'impression. [2] Ces commandes tirent l'origine de leurs noms des imprimantes "ligne par ligne" d'un autre âge.

bash$ lp file1.txt ou bash lp <file1.txt

Il est souvent utile d'envoyer le résultat de la commande pr à lp.

bash$ pr -options file1.txt | lp

Les outils de mise en forme comme groff et Ghostscript peuvent directement envoyer leurs sorties à lp.

bash$ groff -Tascii file.tr | lp

bash$ gs -options | lp file.ps

Les commandes sont lpq pour visualiser la file d'impression et lprm pour retirer des documents de la file d'impression.

tee

[UNIX emprunte ici une idée aux commerces de tuyauterie]

C'est un opérateur de redirection avec une petite différence : comme le << T >> du plombier, il permet de << renvoyer >> vers un fichier la sortie d'une commande ou de plusieurs commandes à l'intérieur d'un tube mais sans affecter le résultat. Ceci est utile pour envoyer le résultat du processus en cours vers un fichier ou un papier, par exemple pour des raisons de déboguages.

                tee
                |------> vers le fichier
                |
 ===============|===============
commande--->----|-operateur-->---> résultats des commandes
 ===============================
 

cat listefichiers* | sort | tee fichier.verif | uniq > fichier.resultat
(le fichierfichier.verif contient les contenus concaténés puis triés des fichiers << listefichiers >> avant que les doublons ne soient supprimés par uniq.)

mkfifo

Cette commande obscure crée un tube nommé, un tampon temporaire pour transférer les données entre les programmes sur le principe du first-in-first-out (FIFO : premier arrivé, premier sorti) [3] Classiquement, un programme écrit dans le FIFO et un autre y lit. Voir Exemple A-16.

pathchk

Ce programme vérifie la validité d'un nom de fichier. Il renvoie un message d'erreur si le nom excède la taille maximale autorisée (255 caractères) ou si un des répertoires du chemin est inaccessible, alors un message d'erreur est affiché.

Malheureusement, pathchk ne renvoie pas un code d'erreur interprétable, ce qui le rend assez inutile dans un script. Cherchez du côté des opérateurs de tests sur les fichiers si besoin.

dd

C'est une commande légèrement obscure et l'une des plus craintes des commandes de duplication des données. A l'origine, c'était un outil d'échange de données entre les bandes magnétiques des mini-ordinateurs unix et les mainframes d'IBM. Cette commande est encore utilisée à cet effet. dd copie simplement un fichier (ou stdin/stdout) mais en effectuant une conversion. ASCII/EBCDIC est une conversion possible [4] minuscule/majuscule, permutation des paires d'octets entre l'entré et la sortie, saut et troncature des entêtes et queues du fichier d'entrées, un dd --help affichera la liste des autres conversions possibles de ce puissant programme.

# S'exercer à 'dd'.

 n=3
 p=5
 fichier_entree=projet.txt
 fichier_sortie=traces.txt

 dd if=$fichier_entree of=$fichier_sortie bs=1 skip=$((n-1)) count=$((p-n+1)) 2> /dev/null
# extrait les symboles de n à p du fichier $fichier_entree

 echo -n "bonjour le monde" | dd cbs=1 conv=unblock 2> /dev/null
# affiche "bonjour le monde" verticalement.
# Merci, S.C.

Pour montrer a quel point dd est souple, utilisons le pour capturer nos saisies.

Exemple 12-41. Capturer une saisie

#!/bin/bash
# Capture des touches clavier sans avoir besoin d'appuyer sur ENTER.


touches_appuyees=4                    # Nombre de touches à capturer.


ancien_parametrage_du_tty=$(stty -g)  # Sauve l'ancienne configuration du terminal.

echo "Appuyez sur $touches_appuyees touches."
stty -icanon -echo                    # Désactive le mode canonique.
                                      # Désactive l'echo local.
touches=$(dd bs=1 count=$touches_appuyees 2> /dev/null)
# 'dd' utilise stdin si "if" n'est pas spécifié.

stty "$ancien_parametrage_du_tty"     # Restaure l'ancien paramètrage du terminal.

echo "Vous avez appuyé sur les touches \"$keys\"."

# Merci, S.C. pour avoir montré la façon.
exit 0

dd peut effectuer un accès aléatoire sur un flux de données.
echo -n . | dd bs=1 seek=4 of=fichier conv=notrunc
# l'option "conv=notrunc" signifie que la sortie ne sera pas tronquée.
# Merci, S.C.

dd peut copier les données brutes d'un périphérique (comme un lecteur de disquette ou de bande magnétique) vers une image et inversement (Exemple A-6). On l'utilise couramment pour créer des disques de démarage.

dd if=kernel-image of=/dev/fd0H1440

De la même manière, dd peut copier le contenu entier d'un disque (même formaté avec un autre OS) vers un fichier image.

dd if=/dev/fd0 of=/home/bozo/projects/floppy.img

Comme autres exemples d'applications de dd, on peut citer l'initialisation d'un fichier swap temporaire (Exemple 29-2) ou d'un disque en mémoire (Exemple 29-3). dd peut même effectuer la copie bas-niveau d'une partition complète d'un disque dur même si la pratique n'est pas conseillée.

Les gens (qui n'ont probablement rien à faire de mieux de leur temps) pensent constamment à de nouvelles applications intéressantes de dd.

Exemple 12-42. Effacer les fichiers de façon sure

#!/bin/bash
# blotout.sh: Efface toutes les traces d'un fichier.

#  Ce script écrase un fichier cible avec des octets pris au hasard, puis avec
#+ des zéros, avant de le supprimer définitivement.
#  Après cela, même l'examen des secteurs du disque ne permet pas de retrouver
#+ les données du fichier d'origine.

PASSES=7         # Nombre d'écriture sur le fichier.
TAILLEBLOC=1     #  Les entrées/sorties avec /dev/urandom requièrent la taille
                 #+ d'un bloc, sinon vous obtiendrez des résultats bizarres.
E_MAUVAISARGS=70
E_NON_TROUVE=71
E_CHANGE_D_AVIS=72

if [ -z "$1" ]   # Aucun nom de fichier spécifié.
then
  echo "Usage: `basename $0` nomfichier"
  exit $E_MAUVAISARGS
fi

fichier=$1

if [ ! -e "$fichier" ]
then
  echo "Le fichier \"$fichier\" est introuvable."
  exit $E_NON_TROUVE
fi  

echo; echo -n "Etes-vous absolument sûr de vouloir écraser complètement \"$fichier\" (o/n)? "
read reponse
case "$reponse" in
[nN]) echo "Vous avez changé d'idée, hum?"
      exit $E_CHANGE_D_AVIS
      ;;
*)    echo "Ecrasement du fichier \"$fichier\".";;
esac


longueur_fichier=$(ls -l "$fichier" | awk '{print $5}')
      # Le 5e champ correspond à la taille du fichier.

nb_passe=1

echo

while [ "$nb_passe" -le "$PASSES" ]
do
  echo "Passe #$nb_passe"
  sync         # Vider les tampons.
  dd if=/dev/urandom of=$fichier bs=$TAILLEBLOC count=$longueur_fichier
               # Remplir avec des octets pris au hasard.
  sync         # Vider de nouveau les tampons.
  dd if=/dev/zero of=$fichier bs=$TAILLEBLOC count=$longueur_fichier
               # Remplir avec des zéros.
  sync         # Vider encore une fois les tampons.
  let "nb_passe += 1"
  echo
done  


rm -f $fichier # Finalement, la suppression a complètement écrasé le fichier.
sync           # Vide les tampons une dernière fois.

echo "Le fichier \"$fichier\" a été complètement écrasé et supprimé."; echo


#  C'est une méthode assez sécurisée, mais inefficace et lente pour massacrer
#+ un fichier. La commande "shred", faisant partie du paquetage GNU "fileutils",
#+ fait la même chose mais de façon plus efficace.

#  Le fichier ne peut pas être récupéré par les méthodes habituelles.
#  Néanmoins...
#+ cette simple méthode ne pourra certainement *pas* résister à des
#+ plus poussées.


#  Le paquetage de suppression de fichier "wipe" de Tom Vier fait un travail
#+ bien plus en profondeur pour massacrer un fichier que ce simple script.
#     http://www.ibiblio.org/pub/Linux/utils/file/wipe-2.0.0.tar.bz2

#  Pour une analyse en détail du thème de la suppression de fichier et de la
#+ sécurité, voir le papier de Peter Gutmann,
#+     "Secure Deletion of Data From Magnetic and Solid-State Memory".
#           http://www.cs.auckland.ac.nz/~pgut001/secure_del.html


exit 0
od

Le filtre od (pour octal dump) convertit l'entré en octal (base 8) ou dans une autre base. C'est très utile pour voir ou traiter des fichiers binaires ou d'autres sources de données illisibles comme /dev/urandom. Voir Exemple 9-26 et Exemple 12-10.

hexdump

Effectue une image hexadécimale, octale, décimale ou ASCII d'un fichier binaire. hexdump est un équivalent moins complet d' od, traité ci-dessus.

objdump

Affiche un objet ou un exécutable binaire sous sa forme hexadécimale ou en tant que code désassemblé (avec l'option -d).

bash$ objdump -d /bin/ls
 /bin/ls:     file format elf32-i386

 Disassembly of section .init:

 080490bc <.init>:
 80490bc:       55                      push   %ebp
 80490bd:       89 e5                   mov    %esp,%ebp
 . . .
 

mcookie

Cette commande génère un fichier témoin (<< magic cookie >>), un nombre hexadécimal pseudo-aléatoire de 128 bits (32 caractères) qui est habituellement utilisé par les serveurs X comme << signature >> pour l'authentification. Elle peut être utilisée dans un script comme une solution sale mais rapide pour générer des nombres aléatoires.
random000=`mcookie | sed -e '2p'`
# Utilise sed pour supprimer les symbôles en trop

Evidemment, un script peut utiliser md5 pour obtenir le même résultat.
# Génère la somme de contrôle md5 du script lui-même.
 random001=`md5sum $0 | awk '{print $1}'`
# Utilise awk pour supprimer le nom du fichier

mcookie est aussi une autre facon de générer un nom de fichier << unique >>.

Exemple 12-43. Générateur de nom de fichier

#!/bin/bash
# tempfile-name.sh:  générateur de fichier temporaire.

BASE_STR=`mcookie`   # Chaîne magique de 32 caractères.
POS=11               # Position arbitraire dans la chaîne magique.
LONG=5               # Pour obtenir $LONG caractères consécutifs.

prefixe=temp         #  C'est après tout un fichier "temp"oraire.
                     #  Pour que le nom soit encore plus "unique", génère le
		     #+ préfixe du nom du fichier en utilisant la même méthode
		     #+ que le suffixe ci-dessous.

suffixe=${BASE_STR:POS:LONG}
                     # Extrait une chaîne de cinq caractères, commençant à la
		     # position 11.

nomfichiertemporaire=$prefixe.$suffixe
                     # Construction du nom du fichier.

echo "Nom du fichier temporaire = "$nomfichiertemporaire""

# sh tempfile-name.sh
# Nom du fichier temporaire = temp.e19ea

exit 0
units

Généralement appelé de façon interactive, cet utilitaire peut être utilisé dans un script. Il sert à convertir des mesures en différentes unités.

Exemple 12-44. Convertir des mètres en miles

#!/bin/bash
# unit-conversion.sh


convertir_unites ()  # Prend comme arguments les unités à convertir.
{
  cf=$(units "$1" "$2" | sed --silent -e '1p' | awk '{print $2}')
  # Supprime tous sauf le facteur conversion.
  echo "$cf"
}  

Unite1=miles
Unite2=meters
facteur_conversion=`convertir_unites $Unite1 $Unite2`
quantite=3.73

resultat=$(echo $quantite*$facteur_conversion | bc)

echo "Il existe $resultat $Unite2 dans $quantite $Unit1."

#  Que se passe-t'il si vous donnez des unités incompatibles, telles que
#+ "acres" et "miles"?

exit 0
m4

Trésor caché, m4 est un puissant filtre de traitement des macros. [5] Langage pratiquement complet, m4 fut écrit comme pré-processeur pour RatFor avant de s'avérer être un outil autonome très utile. En plus de ses possibilités étendues d'interpolation de macros, m4 intègre les fonctionnalités d'eval, tr et awk.

Un très bon article sur m4 et ses utilisations a été écrit pour le numéro d'avril 2002 du Linux Journal.

Exemple 12-45. Utiliser m4

#!/bin/bash
# m4.sh: Utiliser le processeur de macros m4

# Chaîne de caractères
chaine=abcdA01
echo "len($chaine)" | m4                           # 7
echo "substr($chaine,4)" | m4                      # A01
echo "regexp($chaine,[0-1][0-1],\&Z)" | m4         # 01Z

# Arithmétique
echo "incr(22)" | m4                               # 23
echo "eval(99 / 3)" | m4                           # 33

exit 0
doexec

doexec permet de transmettre une liste arbitraire d'arguments à un binaire exécutable. En particulier , le fait de transmettre argv[0] (qui correspond à $0 dans un script) permet à l'exécutable d'être invoqué avec des noms différents et d'agir en fonction de cette invocation. Ceci n'est qu'une autre façon de passer des options à un exécutable.

Par exemple , le répertoire /usr/local/bin peut contenir un binaire appelé << aaa >>. doexec /usr/local/bin/aaa list affichera la liste de tous les fichiers du répertoire courant qui commencent par un << a >>. Appeler le même binaire par doexec /usr/local/bin/aaa delete détruira ces fichiers.

Note

Les différentes actions d'un executable doivent être définies à l'interieur du code exécutable lui-même. De façon similaire au script suivant :
case `basename $0` in
 "name1" ) faire_qqchose;;
 "name2" ) faire_qqchose_d_autre;;
 "name3" ) encore_autre_chose;;
 *       ) quitter;;
 esac

Notes

[1]

Ces scripts sont inspirés de ceux trouvés dans la distribution debian

[2]

La file d'impression est l'ensemble des documents en attente d'impression.

[3]

Pour une excellente vue d'ensemble du sujet, lire l'article de Andy Vaught Introduction to Named Pipes ( Introduction aux tubes nommés ) dans le numéro de septembre 1997 du of Linux Journal.

[4]

EBCDIC (prononcer << ebb-sid-ic >>) est l'acronyme de Extended Binary Coded Decimal Interchange Code. C'est un vieux format de données d'IBM qui n'a plus cours aujourd'hui. Une utilisation étrange de l'option conv=ebcdic est l'encodage simple (mais pas très sécurisé) de fichiers textes.
cat $file | dd conv=swab,ebcdic > $file_encrypted
# Encode (baragouin).		    
# on peut ajouter l'option swab pour obscurcir un peu plus

 cat $file_encrypted | dd conv=swab,ascii > $file_plaintext
# Decode.

[5]

Une macro est une constante symbôlique qui peut être substituée par une simple chaine de caractères ou par une operation sur une série d'arguements.

>#!/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