Chapitre 28. /dev et /proc

Table des matières
28.1. /dev
28.2. /proc

Une machine Linux ou UNIX a typiquement deux répertoires ayant un but particulier, /dev et /proc.

>declare -i nombre # Ce script va traiter les occurrences suivantes de "nombre" comme un entier. nombre=3 echo "nombre = $nombre" # nombre = 3 nombre=three echo "nombre = $nombre" # nombre = 0 # Essaie d'évaluer "three" comme un entier. Notez que certaines opérations arithmétiques sont permises pour des variables déclarées entières sans avoir besoin de expr ou de let.

-a tableau (array)

déclare -a index

La variable index sera traité comme un tableau.

-f fonction

declare -f

Une ligne declare -f sans argument dans un script donnera une liste de toutes les fonctions définies auparavant dans ce script.

declare -f nom_fonction

Un declare -f nom_fonction dans un script liste simplement la fonction nommée.

-x export

declare -x var3

Ceci déclare la disponibilité d'une variable pour une exportation en dehors de l'environnement du script lui-même.

var=$value

declare -x var3=373

La commande declare permet d'assigner une valeur à une variable dans la même déclaration que celle de ses ses propriétés.

Exemple 9-20. Utiliser declare pour typer des variables

#!/bin/bash

fonc1 ()
{
echo Ceci est une fonction.
}

declare -f        # Liste la fonction ci-dessus.

echo

declare -i var1   # var1 est un entier.
var1=2367
echo "var1 déclaré comme $var1"
var1=var1+1       # La déclaration d'un entier élimine le besoin d'utiliser let.
echo "var1 incrémenté par 1 vaut $var1."
# Essai de modification de la variable déclarée comme entier
echo "Essai de modification de var1 en une valeur flottante, 2367.1."
var1=2367.1       # Résultat: un message d'erreur, et une variable non modifiée.
echo "var1 vaut toujours $var1"

echo

declare -r var2=13.36           #  'declare' permet de configurer une variable
                                #+ proprement et de lui affecter une valeur.
echo "var2 déclaré comme $var2" #  Essai de modification d'une valeur en lecture
                                #+ seule.
var2=13.37                      # Génère un message d'erreur, et sort du script.

echo "var2 vaut toujours $var2" # Cette ligne ne s'exécutera pas.

exit 0                          # Le script ne terminera pas ici.
BGCOLOR="#E0E0E0" WIDTH="90%" >
#!/bin/bash
# assert.sh

assert ()                 #  Si la condition est fausse,
{                         #+ sort du script avec un message d'erreur.
  E_PARAM_ERR=98
  E_ASSERT_FAILED=99


  if [ -z "$2" ]          # Pas assez de paramètres passés.
  then
    return $E_PARAM_ERR   # Pas de dommages.
  fi

  noligne=$2

  if [ ! $1 ] 
  then
    echo "Mauvaise assertion:  \"$1\""
    echo "Fichier \"$0\", ligne $noligne"
    exit $E_ASSERT_FAILED
  # else (sinon)
  #   return (retour)
  #   et continue l'exécution du script.
  fi  
}    


a=5
b=4
condition="$a -lt $b"     # Message d'erreur et sortie du script.
                          #  Essayer de configurer la "condition" en autre chose
                          #+ et voir ce qui se passe.

assert "$condition" $LINENO
# Le reste du script s'exécute si assert n'échoue pas.


# Quelques commandes.
# ...
echo "Cette instruction s'exécute seulement si \"assert\" n'échoue pas."
# ...
# Quelques commandes de plus.

exit 0
  • piéger la sortie.

    La commande exit d'un script déclenche un signal 0, terminant le processus, c'est-à-dire le script lui-même. [2] Il est souvent utilisé pour récupérer la main lors de exit, en forçant un << affichage >> des variables, par exemple. Le trap doit être la première commande du script.

  • Récupérer les signaux

    trap

    Spécifie une action à la réception d'un signal; aussi utile pour le déboguage.

    Note

    Un signal est un simple message envoyé au processus, soit par le noyau soit par un autre processus lui disant de réaliser quelque action spécifiée (habituellement pour finir son exécution). Par exemple, appuyer sur Control-C, envoit une interruption utilisateur, un signal INT, au programme en cours d'exécution.

    trap '' 2
    # Ignore l'interruption 2 (Control-C), sans action définie.
    
    trap 'echo "Control-C désactivé."' 2
    # Message lorsque Control-C est utilisé.

    Exemple 30-5. Récupérer la sortie

    #!/bin/bash
    
    trap 'echo Liste de Variables --- a = $a  b = $b' EXIT
    # EXIT est le nom du signal généré en sortie d'un script.
    
    a=39
    
    b=36
    
    exit 0
    # Notez que mettre en commentaire la commande 'exit' ne fait aucune différence,
    # car le script sort dans tous les cas après avoir exécuté les commandes.

    Exemple 30-6. Nettoyage après un Control-C

    #!/bin/bash
    # logon.sh: Un script rapide mais sale pour vérifier si vous êtes déjà connecté.
    
    
    VRAI=1
    JOURNAL=/var/log/messages
    # Notez que $JOURNAL doit être lisible (chmod 644 /var/log/messages).
    FICHIER_TEMPORAIRE=temp.$$
    # Crée un fichier temporaire "unique", en utilisant l'identifiant du processus.
    MOTCLE=adresse
    # A la connexion, la ligne "remote IP address xxx.xxx.xxx.xxx"
    #                     ajoutée à /var/log/messages.
    ENLIGNE=22
    INTERRUPTION_UTILISATEUR=13
    VERIFIE_LIGNES=100
    # Nombre de lignes à vérifier dans le journal.
    
    trap 'rm -f $FICHIER_TEMPORAIRE; exit $INTERRUPTION_UTILISATEUR' TERM INT
    # Nettoie le fichier temporaire si le script est interrompu avec control-c.
    
    echo
    
    while [ $VRAI ]  # Boucle sans fin.
    do
      tail -$VERIFIE_LIGNES $JOURNAL> $FICHIER_TEMPORAIRE
      # Sauve les 100 dernières lignes du journal dans un fichier temporaire.
      # Nécessaire, car les nouveaux noyaux génèrent beaucoup de messages lors de la
      # connexion.
      search=`grep $MOTCLE $FICHIER_TEMPORAIRE`
      # Vérifie la présence de la phrase "IP address",
      # indiquant une connexion réussie.
    
      if [ ! -z "$search" ] # Guillemets nécessaires à cause des espaces possibles.
      then
         echo "En ligne"
         rm -f $FICHIER_TEMPORAIRE    # Suppression du fichier temporaire.
         exit $ENLIGNE
      else
         echo -n "."        # l'option -n supprime les retours à la ligne de echo,
                            # de façon à obtenir des lignes de points continues.
      fi
    
      sleep 1  
    done  
    
    
    # Note: Si vous modifiez la variable MOTCLE par "Exit",
    # ce script peut être utilisé lors de la connexion pour vérifier une déconnexion
    # inattendue.
    
    # Exercice: Modifiez le script, suivant la note ci-dessus, et embellissez-le.
    
    exit 0
    
    
    # Nick Drage suggère une autre méthode.
    
    while true
      do ifconfig ppp0 | grep UP 1> /dev/null && echo "connecté" && exit 0
      echo -n "."   # Affiche des points (.....) jusqu'au moment de la connexion.
      sleep 2
    done
    
    # Problème: Appuyer sur Control-C pour terminer ce processus peut être
    # insuffisant (des points pourraient toujours être affichés).
    # Exercice: Corrigez ceci.
    
    
    
    # Stephane Chazelas a lui-aussi suggéré une autre méthode.
    
    CHECK_INTERVAL=1
    
    while ! tail -1 "$JOURNAL" | grep -q "$MOTCLE"
    do echo -n .
       sleep $CHECK_INTERVAL
    done
    echo "On-line"
    
    # Exercice: Discutez les avantages et inconvénients de chacune des méthodes.

    Note

    L'argument DEBUG pour trap exécute une action spécifique après chaque commande dans un script. Cela permet de tracer les variables, par exemple.

    Exemple 30-7. Tracer une variable

    #!/bin/bash
    
    trap 'echo "VARIABLE-TRACE> \$variable = \"$variable\""' DEBUG
    # Affiche la valeur de $variable après chaque commande.
    
    variable=29
    
    echo "Initialisation de \"\$variable\" à $variable."
    
    let "variable *= 3"
    echo "Multiplication de \"\$variable\" avec 3."
    
    #  La construction "trap 'commandes' DEBUG" serait plus utile dans le contexte
    #+ d'un script plus complexe, où placer de nombreuses instructions
    #+ "echo $variable" serait difficile et long.
    
    # Merci, Stephane Chazelas pour cette information.
    
    exit 0

    Note

    trap '' SIGNAL (deux apostrophes adjacentes) désactive SIGNAL pour le reste du script. trap SIGNAL restaure la fonctionnalité de SIGNAL. C'est utile pour protéger une portion critique d'un script d'une interruption indésirable.

    	trap '' 2  # Le signal 2 est Control-C, maintenant désactivé.
    	command
    	command
    	command
    	trap 2     # Réactive Control-C
    	

    Notes

    [1]

    Le débogueur Bash de Rocky Bernstein comble légèrement ce manque.

    [2]

    Par convention, signal 0 est affecté à exit.

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