14. Commandes internes et intégrées

Une commande intégrée est une commande contenue dans la boîte à outils de Bash, elle est donc littéralement intégrée. C'est soit pour des raisons de performance -- les commandes intégrées s'exécutent plus rapidement que les commandes externes, qui nécessitent habituellement de dupliquer le processus -- soit parce qu'une commande intégrée spécifique a besoin d'un accès direct aux variables internes du shell.

Une commande intégrée peut être le synonyme d'une commande système du même nom mais Bash la réimplémente en interne. Par exemple, la commande Bash echo n'est pas la même que /bin/echo bien que leurs comportements soient pratiquement identiques.

#!/bin/bash

echo "Cette ligne utilise la commande intégrée \"echo\"."
/bin/echo "Cette ligne utilise la commande système /bin/echo."

Un mot clé est un mot, une expression ou un opérateur réservé. Les mots clés ont une signification particulière pour le shell et sont en fait les blocs permettant la construction de la syntaxe du shell. Comme exemples, « for », « while », « do » et « ! » sont des mots clés. Identiques à une commande intégrée, un mot clé est codé en dur dans Bash mais, contrairement à une commande intégrée, un mot clé n'est pas en lui-même une commande mais fait partie d'un ensemble plus large de commandes. [46]

I/O

echo

envoie (vers stdout) une expression ou une variable (voir l'Exemple 4.1, « Affectation de variable et substitution  »).

echo Bonjour
echo $a

Un echo nécessite l'option -e pour afficher des séquences d'échappement. Voir l'Exemple 5.2, « Caractères d'échappement ».

Habituellement, chaque commande echo envoie un retour à la ligne, mais l'option -n désactive ce comportement.

[Note]

Note

Un echo peut être utilisé pour envoyer des informations à un ensemble de commandes via un tube.

if echo "$VAR" | grep -q txt   # if [[ $VAR = *txt* ]]
then
  echo "$VAR contient la sous-chaîne \"txt\""
fi

Sachez que echo `commande` supprime tous les retours chariot que la sortie de commande génère.

La variable $IFS (séparateur interne de champ) contient habituellement \n (retour chariot) comme un des éléments de ses espaces blancs. Du coup, Bash divise la sortie de commande suivant les retours chariot et les prend comme argument pour echo. Ensuite, echo affiche ces arguments séparés par des espaces.

bash$ ls -l /usr/share/apps/kjezz/sounds
-rw-r--r--    1 root     root         1407 Nov  7  2000 reflect.au
 -rw-r--r--    1 root     root          362 Nov  7  2000 seconds.au




bash$ echo `ls -l /usr/share/apps/kjezz/sounds`
total 40 -rw-r--r-- 1 root root 716 Nov 7 2000 reflect.au -rw-r--r-- 1 root root ...
              

Donc, comment pouvons-nous intégrer un retour chariot dans la chaîne de caractère d'un echo ?

# Intégrer un retour chariot ?
echo "Pourquoi cette chaîne \n ne s'affiche pas sur deux lignes ?"
# Pas de deuxième ligne.

# Essayons autre chose.

echo

echo $"Une ligne de texte contenant
un retour chariot."
# S'affiche comme deux lignes distinctes (retour chariot intégré).
# Mais, le préfixe "$" des variables est-il réellement nécessaire ?

echo

echo "Cette chaîne se divise
en deux lignes."
# Non, le "$" n'est pas nécessaire.

echo
echo "---------------"
echo

echo -n $"Autre ligne de texte contenant
un retour chariot."
# S'affiche comme deux lignes distinctes (retour chariot intégré).
# Même l'option -n échoue à la suppression du retour chariot ici.

echo
echo
echo "---------------"
echo
echo

# Néanmoins, ce qui suit ne fonctionne pas comme attendu.
# Pourquoi pas ? Astuce : affectation d'une variable.
chaine1=$"Encore une autre ligne de texte contenant
un retour chariot (peut-être)."

echo $chaine1
# Encore une autre ligne de texte contenant un retour chariot (peut-être).
#                                          ^
# Le retour chariot est devenu une espace.

# Merci pour cette indication, Steve Parker.
[Note]

Note

Cette commande est une commande intégrée au shell, et n'est pas identique à /bin/echo, bien que son comportement soit similaire.

bash$ type -a echo
echo is a shell builtin
 echo is /bin/echo
              
printf

La commande printf, un print formaté, est un echo amélioré. C'est une variante limitée de la fonction printf() en langage C, et sa syntaxe est quelque peu différente.

printf format-string... parametre...

Il s'agit de la version intégrée à Bash de la commande /bin/printf ou /usr/bin/printf. Voir la page de manuel pour printf (la commande système) pour un éclairage détaillé.

[Attention]

Attention

Les anciennes versions de Bash peuvent ne pas supporter printf.

Exemple 14.2. printf en action

#!/bin/bash
# printf demo

PI=3.14159265358979
ConstanteDecimale=31373
Message1="Greetings,"
Message2="Earthling."

echo

printf "Pi avec deux décimales = %1.2f" $PI
echo
printf "Pi avec neuf décimales = %1.9f" $PI  # Il arrondit même correctement.

printf "\n"                                  # Affiche un retour chariot.
                                             # Équivalent à 'echo'.

printf "Constante = \t%d\n" $ConstanteDecimale  # Insère une tabulation (\t).

printf "%s %s \n" $Message1 $Message2

echo

# ==========================================#
# Simulation de la fonction C, sprintf().
# Changer une variable avec une chaîne de caractères formatée.

echo 

Pi12=$(printf "%1.12f" $PI)
echo "Pi avec 12 décimales = $Pi12"      # Erreur d'arrondi !

Msg=`printf "%s %s \n" $Message1 $Message2`
echo $Msg; echo $Msg

#  La fonction 'sprintf' est maintenant accessible en tant que module chargeable
#+ de Bash mais ce n'est pas portable.

exit 0

Formater les messages d'erreur est une application utile de printf

E_MAUVAISREP=65

var=repertoire_inexistant

error()
{
  printf "$@" >&2
  # Formate les paramètres de position passés et les envoie vers stderr.
  echo
  exit $E_MAUVAISREP
}

cd $var || error $"Ne peut aller dans %s." "$var"

# Merci, S.C.
read

« Lit » la valeur d'une variable à partir de stdin, c'est-à-dire récupère interactivement les entrées à partir du clavier. L'option -a permet à read de lire des variables tableau (voir l'Exemple 26.5, « Quelques propriétés spéciales des tableaux »).

Exemple 14.3. Affectation d'une variable, en utilisant read

#!/bin/bash
# "Lire" des variables.

echo -n "Entrez la valeur de la variable 'var1' : "
# L'option -n d'echo supprime le retour chariot.

read var1
#  Notez qu'il n'y a pas de '$' devant var1 car elle est en train d'être
#+ initialisée.

echo "var1 = $var1"


echo

# Une simple instruction 'read' peut initialiser plusieurs variables.
echo -n "Entrez les valeurs des variables 'var2' et 'var3' " \
   "(séparées par des espaces ou des tabulations): "
read var2 var3
echo "var2 = $var2      var3 = $var3"
#  Si vous entrez seulement une valeur,
#+ les autres variables resteront non initialisées (null).

exit 0

Un read sans variable associée assigne son entrée à la variable dédiée $REPLY.

Exemple 14.4. Qu'arrive-t'il quand read n'a pas de variable

#!/bin/bash
# read-novar.sh

echo

# ------------------------------ #
echo -n "Saisissez une valeur : "
read var
echo "\"var\" = "$var""
# Tout se passe comme convenu.
# ------------------------------ #

echo

# ------------------------------------------------------------------- #
echo -n "Saisissez une nouvelle valeur : "
read           #  Aucune variable n'est donnée à 'read', donc...
               #+ La saisie par 'read' est affectée à la variable par défaut, $REPLY.
var="$REPLY"
echo "\"var\" = "$var""
# Ceci est équivalent au premier bloc de code.
# ------------------------------------------------------------------- #

echo
echo "========================="
echo


#  Cet exemple est similaire au script "reply.sh".
#  Néanmoins, celui-ci montre que $REPLY est disponible
#+ même après un 'read' dans une variable de la façon classique.

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

#  Dans certaines instances, vous pourriez souhaiter ignorer la première valeur lue.
#  Dans de tels cas, ignorez tout simplement la variable $REPLY.

{ # Bloc de code.
read            # Ligne 1, à ignorer
read ligne2     # Ligne 2, sauvegarder dans la variable.
  } <$0
echo "La ligne 2 de ce script est :"
echo "$ligne2"  #   # read-novar.sh
echo            #   #!/bin/bash  ligne ignorée.

# Voir aussi le script soundcard-on.sh.

exit 0

Habituellement, saisir un \ supprime le retour chariot lors de la saisie suite à un read. Avec l'option -r, un caractère \ saisi sera interprété littéralement.

Exemple 14.5. Lecture de plusieurs lignes par read

#!/bin/bash

echo

echo "Saisissez une chaîne de caractères terminée par un \\, puis appuyez sur ENTER."
echo "Ensuite, saisissez une deuxième chaîne de caractères (sans \\ cette fois), " \
  "puis appuyez de nouveau sur ENTER."
read var1     # Le "\" supprime le retour chariot lors de la lecture de $var1.
              #     première ligne \
              #     deuxième ligne

echo "var1 = $var1"
#     var1 = première ligne deuxième ligne

#  Pour chaque ligne terminée par un "\",
#+ vous obtenez une invite sur la ligne suivante pour continuer votre entrée
#+ dans var1.

echo; echo

echo "Saisissez une autre chaîne de caractères terminée par un \\ , puis appuyez sur ENTER."
read -r var2  # L'option -r fait que le "\" est lu littéralement.
              #     première ligne \

echo "var2 = $var2"
#     var2 = première ligne \

# La saisie de données se termine avec le premier ENTER.

echo 

exit 0

La commande read a quelques options intéressantes permettant d'afficher une invite et même de lire des frappes clavier sans appuyer sur ENTER.

# Lit une touche sans avoir besoin d'ENTER.

read -s -n1 -p "Appuyez sur une touche " touche
echo; echo "La touche était "\"$touche\""."

# L'option -s permet de supprimer l'écho.
# L'option -n N signifie que seuls N caractères sont acceptés en entrée.
# L'option -p permet l'affichage d'une invite avant de lire l'entrée.

#  Utiliser ces options est assez complexe car elles nécessitent d'être saisies dans le
#+ bon ordre.

L'option -n pour read permet aussi la détection des flèches de direction et certaines des autres touches inhabituelles.

Exemple 14.6. Détecter les flèches de direction

#!/bin/bash
# arrow-detect.sh : Détecte les flèches du clavier et quelques autres touches.
# Merci, Sandro Magi, pour m'avoir montré comment faire.

# --------------------------------------------
# Codes générés par l'appui sur les touches.
flechehaut='\[A'
flechebas='\[B'
flechedroite='\[C'
flechegauche='\[D'
insert='\[2'
delete='\[3'
# --------------------------------------------

SUCCES=0
AUTRE=65

echo -n "Appuyer sur une touche...  "
#  Il est possible qu'il faille appuyer aussi sur ENTER si une touche non gérée 
#+ ici est utilisée.
read -n3 touche                      # Lit 3 caractères.

echo -n "$touche" | grep "$flechehaut"  # Vérifie si un code est détecté.
if [ "$?" -eq $SUCCES ]
then
  echo "Appui sur la touche flèche haut."
  exit $SUCCES
fi

echo -n "$touche" | grep "$flechebas"
if [ "$?" -eq $SUCCES ]
then
  echo "Appui sur la touche flèche bas."
  exit $SUCCES
fi

echo -n "$touche" | grep "$flechedroite"
if [ "$?" -eq $SUCCES ]
then
  echo "Appui sur la touche flèche droite."
  exit $SUCCES
fi

echo -n "$touche" | grep "$flechegauche"
if [ "$?" -eq $SUCCES ]
then
  echo "Appui sur la touche flèche gauche."
  exit $SUCCES
fi

echo -n "$touche" | grep "$insert"
if [ "$?" -eq $SUCCES ]
then
  echo "Appui sur la touche \"Insert\"."
  exit $SUCCES
fi

echo -n "$touche" | grep "$delete"
if [ "$?" -eq $SUCCES ]
then
  echo "Appui sur la touche \"Delete\"."
  exit $SUCCES
fi


echo "Autre touche."

exit $AUTRE

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

#  Mark Alexander vient avec une version
#+ simplifiée du script ci-dessus (Merci !).
#  Cela supprime le besoin du grep.

#!/bin/bash

  flechehaut=$'\x1b[A'
  flechebas=$'\x1b[B'
  flechegauche=$'\x1b[D'
  flechedroite=$'\x1b[C'

  read -s -n3 -p "Appuyez sur une flèche : " x

  case "$x" in
  $flechehaut)
     echo "Vous avez appuyé sur la flèche haute"
     ;;
  $flechebas)
     echo "Vous avez appuyé sur la flèche basse"
     ;;
  $flechegauche)
     echo "Vous avez appuyé sur la flèche gauche"
     ;;
  $flechedroite)
     echo "Vous avez appuyé sur la flèche droite"
     ;;
  esac

exit $?

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

# Antonio Macchi propose une alternative plus simple.

#!/bin/bash

while true
do
  read -sn1 a
  test "$a" == `echo -en "\e"` || continue
  read -sn1 a
  test "$a" == "[" || continue
  read -sn1 a
  case "$a" in
    A)  echo "haut";;
    B)  echo "bas";;
    C)  echo "droite";;
    D)  echo "gauche";;
  esac
done

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

#  Exercice :
#  ---------
#  1) Ajouter la détection des touches "Home", "End", "PgUp" et "PgDn".

L'option -n de read ne détectera pas la touche Entrée (saut de ligne).

L'option -t de read permet de limiter le temps de réponse (voir l'Exemple 9.4, « read avec délai »).

La commande read peut aussi « lire » l'entrée à partir d'un fichier redirigé vers stdin. Si le fichier contient plus d'une ligne, seule la première ligne est affectée à la variable. Si read a plus d'un paramètre, alors chacune des variables se voit assignée une suite de mots séparés par des espaces blancs. Attention !

Exemple 14.7. Utiliser read avec la redirection de fichier

#!/bin/bash

read var1 < fichier-donnees
echo "var1 = $var1"
# var1 initialisée avec la première ligne du fichier d'entrées "fichier-donnees"

read var2 var3 < fichier-donnees
echo "var2 = $var2   var3 = $var3"
# Notez le comportement non intuitif de "read" ici.
# 1) Revient au début du fichier d'entrée.
# 2) Chaque variable est maintenant initialisée avec une chaîne correspondante,
#    séparée par des espaces blancs, plutôt qu'avec une ligne complète de texte.
# 3) La variable finale obtient le reste de la ligne.
# 4) S'il existe plus de variables à initialiser que de chaînes terminées par
#    une espace blanche sur la première ligne du fichier, alors les variables
#    supplémentaires restent vides.

echo "------------------------------------------------"

# Comment résoudre le problème ci-dessus avec une boucle :
while read ligne
do
  echo "$ligne"
done <fichier-donnees
# Merci à Heiner Steven de nous l'avoir proposé.

echo "------------------------------------------------"

#  Utilisez $IFS (variable comprenant le séparateur interne de fichier, soit
#+ Internal File Separator) pour diviser une ligne d'entrée pour "read", si vous
#+ ne voulez pas des espaces blancs par défaut.

echo "Liste de tous les utilisateurs:"
OIFS=$IFS; IFS=:       # /etc/passwd utilise ":" comme séparateur de champ.
while read nom motpasse uid gid nomcomplet ignore
do
  echo "$nom ($nomcomplet)"
done </etc/passwd   # Redirection d'entrées/sorties.
IFS=$OIFS              # Restaure l'$IFS original.
# Cette astuce vient aussi de Heiner Steven.



#  Initialiser la variable $IFS à l'intérieur même de la boucle élimine le
#+ besoin d'enregistrer l'$IFS originale dans une variable temporaire.
#  Merci à Dim Segebart de nous l'avoir indiqué.
echo "------------------------------------------------"
echo "Liste de tous les utilisateurs:"

while IFS=: read nom motpasse uid gid nomcomplet ignore
do
  echo "$nom ($nomcomplet)"
done </etc/passwd   # Redirection d'entrées/sorties.

echo
echo "\$IFS vaut toujours $IFS"

exit 0

[Note]

Note

Envoyer la sortie d'un tube vers une commande read en utilisant echo pour définir des variables échouera.

Cependant, envoyer la sortie d'un cat à travers un tube semble fonctionner.

cat fichier1 fichier2 |
while read ligne
do
echo $ligne
done

Néanmoins, comme Bjön Eriksson le montre :

Exemple 14.8. Problèmes lors de la lecture d'un tube

#!/bin/sh
# readpipe.sh
# Cet exemple est une contribution de Bjon Eriksson.

dernier="(null)"
cat $0 |
while read ligne
do
    echo "{$ligne}"
    dernier=$ligne
done
printf "\nTout est fait, dernier :$dernier\n"

exit 0  # Fin du code.
        # La sortie (partielle) du script suit.
        # Le 'echo' apporte les crochets supplémentaires.

#############################################

./readpipe.sh 

{#!/bin/sh}
{dernier="(null)"}
{cat $0 |}
{while read ligne}
{do}
{echo "{$ligne}"}
{dernier=$ligne}
{done}
{printf "\nTout est fait, dernier :$dernier\n"}


Tout est fait, dernier :(null)

La variable (dernier) est initialisée à l'intérieur du sous-shell
mais est non initialisée à l'extérieur.

Le script gendiff, habituellement trouvé dans /usr/bin sur un grand nombre de distributions Linux, envoie la sortie de find via un tube vers la construction while read.

find $1 \( -name "*$2" -o -name ".*$2" \) -print |
while read f; do
. . .
[Astuce]

Astuce

Il est possible de coller le texte dans le champ en entrée d'un read. Voir Exemple A.39, « Un générateur de fichiers pad pour les auteurs de shareware ».

Système de fichiers

cd

La commande familière de changement de répertoire, cd, trouve son intérêt dans les scripts où l'exécution d'une commande requiert d'être dans un répertoire spécifique.

(cd /source/repertoire && tar cf - . ) | (cd /dest/repertoire && tar xpvf -)

[à partir de l'exemple précédemment cité d'Alan Cox]

L'option -P (physique) pour cd fait qu'il ignore les liens symboliques.

cd - affecte la variable $OLDPWD.

[Attention]

Attention

La commande cd ne fonctionne pas de la façon attendue si deux slashs se suivent.

bash$ cd //
bash$ pwd
//
              

La sortie devrait être /. Ceci est un problème à la fois à partir de la ligne de commande et dans un script.

pwd

Print Working Directory (NdT : Affiche le répertoire courant). Cela donne le répertoire courant de l'utilisateur (ou du script) (voir l'Exemple 14.9, « Modifier le répertoire courant »). L'effet est identique à la lecture de la variable intégrée $PWD.

pushd, popd, dirs

Cet ensemble de commandes est un mécanisme pour enregistrer les répertoires de travail, un moyen pour revenir en arrière ou aller en avant suivant les répertoires d'une manière ordonnée. Une pile LIFO est utilisée pour conserver la trace des noms de répertoires. Des options permettent diverses manipulations sur la pile de répertoires.

pushd nom-rep enregistre le chemin de nom-rep dans la pile de répertoires et change le répertoire courant par nom-rep

popd supprime (enlève du haut) le chemin du dernier répertoire et, en même temps, change de répertoire courant par celui qui vient d'être récupéré dans la pile.

dirs liste le contenu de la pile de répertoires (comparez ceci avec la variable $DIRSTACK). Une commande pushd ou popd satisfaite va automatiquement appeler dirs.

Les scripts requérant différents changements du répertoire courant sans coder en dur les changements de nom de répertoire peuvent faire un bon usage de ces commandes. Notez que la variable tableau implicite $DIRSTACK, accessible depuis un script, tient le contenu de la pile des répertoires.

Exemple 14.9. Modifier le répertoire courant

#!/bin/bash

rep1=/usr/local
rep2=/var/spool

pushd $rep1
# Fera un 'dirs' automatiquement (liste la pile des répertoires sur stdout).
echo "Maintenant dans le répertoire `pwd`." # Utilise les guillemets inverses
                                            # pour 'pwd'.

# Maintenant, faisons certaines choses dans le répertoire 'rep1'.
pushd $rep2
echo "Maintenant dans le répertoire `pwd`."

# Maintenant, faisons certaines choses dans le répertoire 'rep2'.
echo "L'entrée supérieure du tableau DIRSTACK est $DIRSTACK."
popd
echo "Maintenant revenu dans le répertoire `pwd`."

# Maintenant, faisons certaines choses de plus dans le répertoire 'rep1'.
popd
echo "Maintenant revenu dans le répertoire original `pwd`."

exit 0

# Que se passe-t'il si vous n'exécutez pas 'popd' puis quittez le script ?
# Dans quel répertoire vous trouverez-vous ? Pourquoi ?

Variables

let

La commande let réalise des opérations arithmétiques sur des variables. Dans la majorité des cas, il fonctionne comme une version simplifiée de expr.

Exemple 14.10. Laisser let faire un peu d'arithmétique.

#!/bin/bash

echo

let a=11            # Identique à 'a=11'
let a=a+5           # Équivalent à  let "a = a + 5"
                    # (double guillemets et espaces pour le rendre plus lisible)
echo "11 + 5 = $a"  # 16

let "a <<= 3"       # Équivalent à  let "a = a << 3"
echo "\"\$a\" (=16) décalé de 3 places = $a"
                    # 128

let "a /= 4"        # Équivalent à  let "a = a / 4"
echo "128 / 4 = $a" # 32

let "a -= 5"        # Équivalent à  let "a = a - 5"
echo "32 - 5 = $a"  # 27

let "a = a * 10"    # Équivalent à  let "a = a * 10"
echo "27 * 10 = $a" # 270

let "a %= 8"        # Équivalent à  let "a = a % 8"
echo "270 modulo 8 = $a  (270 / 8 = 33, reste $a)"
                    # 6

echo

exit 0

eval

eval arg1 [arg2] ... [argN]

Combine les arguments dans une expression ou liste d'expressions et les évalue. Toute variable contenue dans l'expression sera étendue. Le résultat se traduit en une commande. C'est utile pour de la génération de code à partir de la ligne de commande ou à l'intérieur d'un script.

bash$ processus=xterm
bash$ affiche_processus="eval ps ax | grep $processus"
bash$ $affiche_processus
1867 tty1     S      0:02 xterm
 2779 tty1     S      0:00 xterm
 2886 pts/1    S      0:00 grep xterm
              

Chaque appel à eval force à la ré-évaluation de ses arguments.

a='$b'
b='$c'
c=d

echo $a             # $b
                    # Premier niveau.
eval echo $a        # $c
                    # Second niveau.
eval eval echo $a   # d
                    # Troisième niveau.

# Merci, E. Choroba.

Exemple 14.11. Montrer l'effet d'eval

#!/bin/bash
# Exercising "eval" ...

y=`eval ls -l`  # Similaire à y=`ls -l`
echo $y         # mais les retours chariot sont supprimés parce que la variable
                # n'est pas entre guillemets.
echo
echo "$y"       # Les retours chariot sont préservés lorsque la variable se
                # trouve entre guillemets.

echo; echo

y=`eval df`     # Similaire à y=`df`
echo $y         # mais les retours chariot ont été supprimés.

#  Quand LF n'est pas préservé, il peut être plus simple d'analyser la sortie,
#+ en utilisant des outils comme "awk".

echo
echo "==========================================================="
echo

# Maintenant, nous vous montrons quoi faire d'utile avec "eval"...
# (Merci E. Choroba!)

version=3.4     #  Pouvons-nous répartir la version en deux parties, la version
                #+ majeure et la version mineure en une seule commande ?
echo "version = $version"
eval majeur=${version/./;mineur=}     #  Remplace '.' dans la version par ';mineur='
                                      #  La substitution ramène '3; minor=4'
                                      #+ donc eval donne mineur=4, majeur=3
echo majeur: $major, mineur: $mineur  #  majeur: 3, mineur: 4

Exemple 14.12. Afficher les paramètres en ligne de commande

#!/bin/bash
# echo-params.sh

# Appeler ce script avec quelques paramètres en ligne de commande.
# Par exemple :
#     sh echo-params.sh premier deuxieme troisieme quatrieme cinquieme

params=$#              # Nombre de paramètres en ligne de commande.
param=1                # Commencer par le premier paramètre.

while [ "$param" -le "$params" ]
do
  echo -n "Paramètre "
  echo -n \$$param     #  Donne seulement le *nom* de la variable.
#         ^^^          #  $1, $2, $3, etc.
                       #  Pourquoi ?
                       #  \$ échappe le premier "$"
                       #+ donc il l'affiche littéralement,
                       #+ et $param déréférence "$param" . . .
                       #+ ... comme on s'y attendait.
  echo -n " = "
  eval echo \$$param   #  Donne la *valeur* de la variable.
# ^^^^      ^^^        #  Cet "eval" force l'*évaluation*
                       #+ de \$$
                       #+ comme une référence indirecte de variable.

(( param ++ ))         # Au suivant.
done

exit $?

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

$ sh echo-params.sh premier deuxieme troisieme quatrieme cinquieme
Paramètre $1 = premier
Paramètre $2 = deuxieme
Paramètre $3 = troisieme
Paramètre $4 = quatrieme
Paramètre $5 = cinquieme

Exemple 14.13. Forcer une déconnexion

#!/bin/bash
# Tuer ppp pour forcer une déconnexion

# Le script doit être exécuté en tant qu'utilisateur root.

killppp="eval kill -9 `ps ax | awk '/ppp/ { print $1 }'`"
#                     -------- ID du processus ppp -----  

$killppp                  # Cette variable est maintenant une commande.


# Les opérations suivantes doivent être faites en tant qu'utilisateur root.

chmod 666 /dev/ttyS3      #  Restaure les droits de lecture/écriture, sinon que
                          #+ se passe-t'il ?
#  Comme nous lançons un signal SIGKILL à ppp après avoir changé les droits sur
#+ le port série, nous restaurons les droits à l'état initial.

rm /var/lock/LCK..ttyS3   #  Supprime le fichier de verrouillage du port série.
                          #+ Pourquoi ?

#  Note :
#  Suivant le matériel et même la version du noyau,
#+ le port du modem de votre machine pourrait être différent --
#+ /dev/ttyS1 ou /dev/ttyS2.

exit 0

# Exercices:
# ---------
# 1) Que le script vérifie si l'utilisateur root l'appelle.
# 2) Faire une vérification concernant le processus à tuer (qu'il existe bien).
# 3) Écrivez une autre version de ce script basé sur 'fuser' :
#+   if [ fuser -s /dev/modem ]; then ...

Exemple 14.14. Une version de rot13

#!/bin/bash
# Une version de "rot13" utilisant 'eval'.
# Comparez à l'exemple "rot13.sh".

setvar_rot_13()              # "rot13" scrambling
{
  local nomvar=$1 valeurvar=$2
  eval $nomvar='$(echo "$valeurvar" | tr a-z n-za-m)'
}


setvar_rot_13 var "foobar"   # Lancez "foobar" avec rot13.
echo $var                    # sbbone

setvar_rot_13 var "$var"     # Lance "sbbone" à travers rot13.
                             # Revenu à la variable originale.
echo $var                    # foobar

# Exemple de Stephane Chazelas.
# Modifié par l'auteur du document.

exit 0

Rory Winston a apporté sa contribution en donnant un autre exemple de l'utilité de la commande eval.

Exemple 14.15. Utiliser eval pour forcer une substitution de variable dans un script Perl

Dans le script Perl "test.pl" :
        ...             
        my $WEBROOT = &lt;WEBROOT_PATH&gt;;
        ...

Pour forcer une substitution de variables, essayez :
        $export WEBROOT_PATH=/usr/local/webroot
        $sed 's/&lt;WEBROOT_PATH&gt;/$WEBROOT_PATH/' &lt; test.pl &gt; out

Mais ceci donne simplement :
        my $WEBROOT = $WEBROOT_PATH;

Néanmoins :
        $export WEBROOT_PATH=/usr/local/webroot
        $eval sed 's%\&lt;WEBROOT_PATH\&gt;%$WEBROOT_PATH%' &lt; test.pl &gt; out
#        ====

Ceci fonctionne bien et donne la substitution attendue :
        my $WEBROOT = /usr/local/webroot;


### Correction appliquée à l'exemple original de Paulo Marcel Coelho Aragao.

[Attention]

Attention

La commande eval est risquée et devrait normalement être évitée quand il existe une alternative raisonnable. Un eval $COMMANDES exécute le contenu de COMMANDES, qui pourrait contenir des surprises désagréables comme rm -rf *. Lancer eval sur un code inconnu écrit par des personnes inconnues vous fait prendre des risques importants.

set

La commande set modifie la valeur de variables/options internes au script. Une utilisation est de modifier les options qui déterminent le comportement du script. Une autre application est d'affecter aux paramètres de position du script le résultat d'une commande (set `commande`). Le script peut alors séparer les différents champs de la sortie de la commande.

Exemple 14.16. Utiliser set avec les paramètres de position

#!/bin/bash

# script "set-test"

# Appeler ce script avec trois paramètres en ligne de commande,
# par exemple, "./set-test one two three".

echo
echo "Paramètres de position avant set \`uname -a\` :"
echo "Argument #1 = $1"
echo "Argument #2 = $2"
echo "Argument #3 = $3"


set `uname -a` # Configure les paramètres de position par rapport à la sortie
               # de la commande `uname -a`

echo $_        # inconnu
# Drapeaux initialisés dans le script.

echo "Paramètres de position après set \`uname -a\` :"
# $1, $2, $3, etc. reinitialisés suivant le résultat de `uname -a`
echo "Champ #1 de 'uname -a' = $1"
echo "Champ #2 de 'uname -a' = $2"
echo "Champ #3 de 'uname -a' = $3"
echo ---
echo $_        # ---
echo

exit 0

Plus de jeu avec les paramètres de position.

Exemple 14.17. Inverser les paramètres de position

#!/bin/bash
# revposparams.sh : Inverse les paramètres de position.
# Script de Dan Jacobson, avec quelques corrections de style par l'auteur du document.


set a\ b c d\ e;
#     ^      ^     Espaces échappés
#       ^ ^        Espaces non échappés
OIFS=$IFS; IFS=:;
#              ^   Sauvegarde de l'ancien IFS et initialisation du nouveau.

echo

until [ $# -eq 0 ]
do          #      Passage des différents paramètres de position.
  echo "### k0 = "$k""     # Avant
  k=$1:$k;  #      Ajoute chaque paramètre de position à la variable de la boucle.
#     ^
  echo "### k = "$k""      # Après
  echo
  shift;
done

set $k  #  Initialise les nouveaux paramètres de position.
echo -
echo $# #  Nombre de paramètres de position.
echo -
echo

for i   #  Oublier la "liste in" initialise la variable -- i --
        #+ avec les paramètres de position.
do
  echo $i  # Affiche les nouveaux paramètres de position.
done

IFS=$OIFS  # Restaure IFS.

#  Question :
#  Est-il nécessaire d'initialiser un nouvel IFS pour que ce script fonctionne
#+ correctement ?
#  Que se passe-t'il dans le cas contraire ? Essayez.
#  Et pourquoi utiliser le nouvel IFS -- une virgule -- en ligne 17,
#+ pour l'ajout à la variable de la boucle ?
#  Quel est le but de tout ceci ?

exit 0

$ ./revposparams.sh

### k0 = 
### k = a b

### k0 = a b
### k = c a b

### k0 = c a b
### k = d e c a b

-
3
-

d e
c
a b

Invoquer set sans aucune option ou argument liste simplement toutes les variables d'environnement ainsi que d'autres variables qui ont été initialisées.

bash$ set
AUTHORCOPY=/home/bozo/posts
 BASH=/bin/bash
 BASH_VERSION=$'2.05.8(1)-release'
 ...
 XAUTHORITY=/home/bozo/.Xauthority
 _=/etc/bashrc
 variable22=abc
 variable23=xzy
              

Utiliser set avec l'option -- affecte explicitement le contenu d'une variable aux paramètres de position. Si aucune variable ne suit --, cela déconfigure les paramètres de positions.

Exemple 14.18. Réaffecter les paramètres de position

#!/bin/bash

variable="un deux trois quatre cinq"

set -- $variable
# Initialise les paramètres de position suivant le contenu de "$variable".

premier_param=$1
deuxieme_param=$2
shift; shift       # Shift fait passer les deux premiers paramètres de position.
# shift 2 fonctionne aussi
params_restant="$*"

echo
echo "premier paramètre = $premier_param"             # un
echo "deuxième paramètre = $deuxieme_param"           # deux
echo "paramètres restants = $params_restant"          # trois quatre cinq

echo; echo

# De nouveau.
set -- $variable
premier_param=$1
deuxieme_param=$2
echo "premier paramètre = $premier_param"             # un
echo "deuxième paramètre = $deuxieme_param"           # deux

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

set --
# Désinitialise les paramètres de position si aucun variable n'est spécifiée.

premier_param=$1
deuxieme_param=$2
echo "premier paramètre = $premier_param"             # (valeur null)
echo "deuxième paramètre = $deuxieme_param"           # (valeur null)

exit 0

Voir aussi l'Exemple 10.2, « Boucle for avec deux paramètres dans chaque élément de la [liste] » et l'Exemple 15.55, « Utiliser getopt pour analyser les paramètres de la ligne de commande ».

unset

La commande unset supprime une variable shell en y affectant réellement la valeur null. Notez que cette commande n'affecte pas les paramètres de position.

bash$ unset PATH

bash$ echo $PATH




bash$ 

Exemple 14.19. « Déconfigurer » une variable

#!/bin/bash
# unset.sh: Dés-initialiser une variable.

variable=hello                       # Initialisée.
echo "variable = $variable"

unset variable                       # Dés-initialisée.
                                     # Même effet que : variable=
echo "(unset) variable = $variable"  # $variable est null.

if [ -z "$variable" ]                # Tente un test de longueur de chaîne.
then
  echo "\$variable a une taille nulle."
fi

exit 0

export

La commande export [47] rend disponibles des variables aux processus fils du script ou shell en cours d'exécution. Une utilisation importante de la commande export se trouve dans les fichiers de démarrage pour initialiser et rendre accessible les variables d'environnement aux processus utilisateur suivants.

[Attention]

Attention

Malheureusement, il n'existe pas de moyens pour exporter des variables dans le processus parent, vers le processus appelant ou qui a exécuté le script ou le shell.

Exemple 14.20. Utiliser export pour passer une variable à un script awk embarqué

#!/bin/bash

# Encore une autre version du script "column totaler" (col-totaler.sh)
# qui ajoute une colonne spécifiée (de nombres) dans le fichier cible.
#  Il utilise l'environnement pour passer une variable de script à 'awk'...
#+ et place le script awk dans une variable.

ARGS=2
E_MAUVAISARGS=65

if [ $# -ne "$ARGS" ] # Vérifie le bon nombre d'arguments de la ligne de
                      # commande.
then
   echo "Usage: `basename $0` nomfichier numéro_colonne"
   exit $E_MAUVAISARGS
fi

nomfichier=$1
numero_colonne=$2

#===== Identique au script original, jusqu'à ce point =====#

export numero_colonne
#  Exporte le numéro de colonne dans l'environnement de façon à ce qu'il soit
#+ disponible plus tard.


# ------------------------------------------------
awkscript='{ total += $ENVIRON["numero_colonne"] }
END { print total }' $nomfichier
# Oui, une variable peut contenir un script awk.
# ------------------------------------------------

# Maintenant, exécute le script awk
awk "$awkscript" "$nomfichier"


# Merci, Stephane Chazelas.

exit 0

[Astuce]

Astuce

Il est possible d'initialiser et d'exporter des variables lors de la même opération, en faisant export var1=xxx.

Néanmoins, comme l'a indiqué Greg Keraunen, dans certaines situations, ceci peut avoir un effet différent que d'initialiser une variable, puis de l'exporter.

bash$ export var=(a b); echo ${var[0]}
(a b)



bash$ var=(a b); export var; echo ${var[0]}
a
              
declare, typeset

Les commandes declare et typeset spécifient et/ou restreignent les propriétés des variables.

readonly

Identique à declare -r, configure une variable en lecture-seule ou, du coup, la transforme en constante. Essayer de modifier la variable échoue avec un message d'erreur. C'est l'équivalent shell du type const pour le langage C.

getopts

Ce puissant outil analyse les arguments en ligne de commande passés au script. C'est l'équivalent Bash de la commande externe getopt et de la fonction getopt familière aux programmeurs C. Il permet de passer et de concaténer de nombreuses options [48] et les arguments associés à un script (par exemple nomscript -abc -e /usr/local).

La construction getopts utilise deux variables implicites. $OPTIND est le pointeur de l'argument (OPTion INDex) et $OPTARG (OPTion ARGument) l'argument (optionnel) attaché à une option. Deux points suivant le nom de l'option lors de la déclaration marque cette option comme ayant un argument associé.

Une construction getopts vient habituellement dans une boucle while, qui analyse les options et les arguments un à un, puis incrémente la variable implicite $OPTIND pour passer à la suivante.

[Note]

Note

  1. Les arguments passés à la ligne de commande vers le script doivent être précédés par un tiret (-). Le préfixe - permet à getopts de reconnaitre les arguments en ligne de commande comme des options. En fait, getopts ne traitera pas les arguments sans les préfixes - et terminera l'analyse des options au premier argument rencontré qui ne les aura pas.

  2. Le modèle getopts diffère légèrement de la boucle while standard dans le fait qu'il manque les crochets de condition.

  3. La construction getopts remplace la commande getopt qui est obsolète.

while getopts ":abcde:fg" Option
# Déclaration initiale.
# a, b, c, d, e, f et g sont les options (indicateurs) attendues.
# Le : après l'option 'e' montre qu'il y aura un argument associé.
do
  case $Option in
    a ) # Fait quelque chose avec la variable 'a'.
    b ) # Fait quelque chose avec la variable 'b'.
    ...
    e)  # Fait quelque chose avec la variable 'e', et aussi avec $OPTARG,
        # qui est l'argument associé passé avec l'option 'e'.
    ...
    g ) # Fait quelque chose avec la variable 'g'.
  esac
done
shift $(($OPTIND - 1))
# Déplace le pointeur d'argument au suivant.

# Tout ceci n'est pas aussi compliqué qu'il n'y paraît <grin>.

Exemple 14.21. Utiliser getopts pour lire les options/arguments passés à un script

#!/bin/bash
#+ S'exercer avec getopts et OPTIND
#+ Script modifié le 10/09/03 suivant la suggestion de Bill Gradwohl.

#  Nous observons ici comment 'getopts' analyse les arguments en ligne de
#+ commande du script.
#  Les arguments sont analysés comme des "options" (flags) et leurs arguments
#+ associés.

# Essayez d'appeller ce script avec
# 'nomscript -mn'
# 'nomscript -oq qOption' (qOption peut être une chaîne de caractère arbitraire.)
# 'nomscript -qXXX -r'
#
# 'nomscript -qr'    - Résultat inattendu, prend "r" comme argument à l'option
#                      "q"
# 'nomscript -q -r'  - Résultat inattendu, identique à ci-dessus
# 'scriptname -mnop -mnop'  - Résultat inattendu
# (OPTIND est incapable d'indiquer d'où provient une option)

#  Si une option attend un argument ("flag:"), alors il récupèrera tout ce qui
#+ se trouve ensuite sur la ligne de commandes.

SANS_ARGS=0 
E_ERREUROPTION=65

if [ $# -eq "$SANS_ARGS" ]  # Script appelé sans argument?
then
  echo "Usage: `basename $0` options (-mnopqrs)"
  exit $E_ERREUROPTION        # Sort et explique l'usage, si aucun argument(s)
                              # n'est donné.
fi  
# Usage: nomscript -options
# Note: tiret (-) nécessaire


while getopts ":mnopq:rs" Option
do
  case $Option in
    m     ) echo "Scénario #1: option -m-   [OPTIND=${OPTIND}]";;
    n | o ) echo "Scénario #2: option -$Option-   [OPTIND=${OPTIND}]";;
    p     ) echo "Scénario #3: option -p-   [OPTIND=${OPTIND}]";;
    q     ) echo "Scénario #4: option -q- \
avec l'argument \"$OPTARG\"   [OPTIND=${OPTIND}]";;
    # Notez que l'option 'q' doit avoir un argument associé,
    # sinon il aura la valeur par défaut.
    r | s ) echo "Scénario #5: option -$Option-"'';;
    *     ) echo "Option non implémentée.";;   # DEFAULT
  esac
done

shift $(($OPTIND - 1))
# Décrémente le pointeur d'argument de façon à ce qu'il pointe vers le prochain.
#  $1 référence maintenant le premier élément n'étant pas une option sur la
#+ ligne de commande si un tel élément existe.
 
 exit 0

#   Comme Bill Gradwohl le dit,
#  "Le mécanisme getopts vous permet de spécifier :  nomscript -mnop -mnop
#+  mais il n'y a pas de moyen de différencier d'où cela vient en utilisant
#+  OPTIND."


Comportement des scripts

source, . (commande point)

Cette commande, lorsqu'elle est appelée à partir de la ligne de commande, exécute un script. À l'intérieur d'un script, un source nom-fichier charge le fichier nom-fichier. Exécuter le source d'un fichier (point de commandes) importe le code dans le script, s'ajoutant au script (même effet que la directive #include dans un programme C). Le résultat est le même que si les lignes « sourcées » de code étaient présentes physiquement dans le corps du script. Ceci est utile dans les situations où de multiples scripts utilisent un fichier de données communes ou une bibliothèque de fonctions.

Exemple 14.22. « Inclure » un fichier de données

#!/bin/bash

. fichier-donnees    # charge un fichier de données.
# Même effet que "source fichier-donnees", mais plus portable.

#  Le fichier "fichier-donnees" doit être présent dans le répertoire courant,
#+ car il est référencé par rappor à son 'basename'.

# Maintenant, référençons quelques données à partir de ce fichier.

echo "variable1 (de fichier-donnees) = $variable1"
echo "variable3 (de fichier-donnees) = $variable3"

let "sum = $variable2 + $variable4"
echo "Somme de variable2 + variable4 (de fichier-donnees) = $sum"
echo "message1 (de fichier-donnees) est \"$message1\""
# Note:                                 guillemets échappés

print_message Ceci est la fonction message-print de fichier-donnees.


exit 0

Le fichier fichier-données pour l'Exemple 14.22, « « Inclure » un fichier de données », ci-dessus, doit être présent dans le même répertoire.

# This is a data file loaded by a script.
# Files of this type may contain variables, functions, etc.
# It may be loaded with a 'source' or '.' command by a shell script.

# Let's initialize some variables.

variable1=22
variable2=474
variable3=5
variable4=97

message1="Hello, how are you?"
message2="Enough for now. Goodbye."

print_message ()
{
# Echoes any message passed to it.

  if [ -z "$1" ]
  then
    return 1
    # Error, if argument missing.
  fi

  echo

  until [ -z "$1" ]
  do
    # Step through arguments passed to function.
    echo -n "$1"
    # Echo args one at a time, suppressing line feeds.
    echo -n " "
    # Insert spaces between words.
    shift
    # Next one.
  done  

  echo

  return 0
}  

Si le fichier inclus est lui-même un script exécutable, alors il sera exécuté, puis renverra le contrôle au script qui l'a appelé. Un script exécutable inclus pourrait utiliser un return dans ce but.

Des arguments pourraient être passés (en option) au fichier inclus en tant que paramètres de position.

source $fichier $arg1 arg2

Il est même possible pour un script de s'intégrer (se sourcer) lui-même, bien qu'il ne semble pas que cela ait la moindre application pratique.

Exemple 14.23. Un script (inutile) qui se charge lui-même

#!/bin/bash
# self-source.sh : un script qui s'exécute lui-même "récursivement."
# De "Stupid Script Tricks", Volume II.

NBTOURSMAX=100    # Nombre maximal de tours d'exécution.

echo -n  "$nb_tour  "
#  Lors du premier tour, ceci va juste afficher deux espaces car $nb_tour n'est
#+ toujours pas initialisé.

let "nb_tour += 1"
#  Suppose que la variable non initialisée $nb_tour peut être incrémentée la
#+ première fois.
#  Ceci fonctionne avec Bash et pdksh mais cela repose sur un comportement
#+ non portable (et certainement dangereux).
#  Il serait mieux d'initialiser $nb_tour à 0 avant de l'incrémenter.

while [ "$nb_tour" -le $NBTOURSMAX ]
do
  . $0   # Le script "s'inclut" lui-même, plutôt que de s'appeler.
         # ./$0 (qui serait une vraie récursion) ne fonctionne pas ici.
         # Pourquoi ?
done  

#  Ce qui arrive ici n'est pas réellement une récursion, car le script
#+ s'étend lui-même effectivement, c'est-à-dire que cela génère une nouvelle
#+ section de code, à chaque tour de la boucle 'while' lors du 'source' en ligne
#+ 20.
#
#  Bien sûr, le script interprète chaque nouvelle ligne incluse "#!" comme un
#+ commentaire, et non pas comme le début d'un nouveau script.

echo

exit 0   # L'effet très net est le comptage de 1 à 100.
         # Très impressionnant.

# Exercice :
# ---------
#  Écrire un script qui utilise cette astuce pour faire quelque chose de
#+ réellement utile.

exit

Termine un script sans condition. [49] La commande exit peut prendre de façon optionnelle un argument de type entier, qui est renvoyé au script en tant qu'état de sortie du script. C'est une bonne pratique de terminer tous les scripts, même les plus simples, avec un exit 0, indiquant un succès.

[Note]

Note

Si un script se termine avec un exit sans argument, l'état de sortie est le statut de exit lors de son dernier lancement dans le script, sans compter le exit. C'est équivalent à un exit $?.

[Note]

Note

Une commande exit peut aussi être utilisé pour terminer un sous-shell.

exec

Cette commande shell intégrée remplace le processus courant avec une commande spécifiée. Normalement, lorsque le shell rencontre une commande, il lance un processus fils pour exécuter la commande. En utilisant la commande intégrée exec, le shell n'exécute aucun processus fils et la commande bénéficiant du exec remplace purement et simplement le shell. Lorsqu'elle est utilisée dans un script, cela force la sortie (exit) du script lorsque la commande bénéficiant du exec se termine. [50]

Exemple 14.24. Effets d'exec

#!/bin/bash

exec echo "Je sors \"$0\"."   # Sortie du script ici.

# ----------------------------------
# Les lignes suivantes ne s'exécutent jamais.

echo "Cet echo ne sera jamais exécuté."

exit 99                       #  Ce script ne sortira jamais par ici.
                              #  Vérifier le code de sortie après l'exécution du
                              #+ du script avec un 'echo $?'.
                              #  Cela ne sera *pas* 99.

Exemple 14.25. Un script lançant exec sur lui-même

#!/bin/bash
# self-exec.sh

echo

echo "Cette ligne apparaît UNE FOIS dans le script, cependant elle continue à s'afficher."
echo "Le PID de cette instance du script est toujours $$."
#     Démontre qu'un sous-shell n'est pas un processus fils.

echo "==================== Tapez Ctl-C pour sortir ===================="

sleep 1

exec $0   #  Lance une autre instance du même script remplaçant le précédent.

echo "Cette ligne ne s'affichera jamais!"  # Pourquoi pas ?

exit 99   # Ne quittera pas ici.
          # Le code de retour ne sera pas 99.

Un exec sert aussi à réaffecter les descripteurs de fichiers. Par exemple, exec <fichier-zzz remplace stdin par le fichier fichier-zzz.

[Note]

Note

L'option -exec de find n'est pas du tout la même chose que la commande shell intégrée exec.

shopt

Cette commande permet de changer les options du shell au vol (voir l'Exemple 24.1, « Alias à l'intérieur d'un script » et l'Exemple 24.2, « unalias : Configurer et supprimer un alias »). Elle apparaît souvent dans les fichiers de démarrage de Bash mais a aussi son utilité dans des scripts. Nécessite la version 2, ou ultérieure, de Bash.

shopt -s cdspell
# Permet des petites erreurs dans le nom des répertoires avec 'cd'

cd /hpme  # Oups! J'ai mal tapé '/home'.
pwd       # /home
          # Le shell a corrigé la faute de frappe.
caller

Placer une commande caller dans une fonction affiche des informations sur stdout à propos de celui qui a appelé cette fonction.

#!/bin/bash

fonction1 ()
{
  # À l'intérieur de fonction1 ().
  caller 0   # Parle-moi de lui.
}

fonction1    # Ligne 9 du script.

# 9 main test.sh
# ^                 Numéro de ligne où a eu lieu l'appel de la fonction.
#   ^^^^            Appelé depuis la partie "main" du script.
#        ^^^^^^^    Nom du script appelant.

caller 0     # N'a aucun effet parce qu'il n'est pas à l'intérieur d'une fonction.

Une commande caller peut aussi renvoyer des informations de l'appelant sur un script inclus à l'intérieur d'un autre script. De façon analogue à une fonction, ceci est un « appel de sous-routine ».

Cette commande est utile pour le débogage.

Commandes

true

Une commande qui renvoie un succès (zéro) comme état de sortie, mais ne fait rien d'autre.

bash$ true
bash$ echo $?
0
              
# Boucle sans fin
while true   # alias pour ":"
do
   operation-1
   operation-2
   ...
   operation-n
   # A besoin d'un moyen pour sortir de la boucle ou le script ne s'arrêtera pas.
done
false

Une commande qui renvoie un état de sortie correspondant à un échec, mais ne fait rien d'autre.

bash$ false
bash$ echo $?
1
              
# Tester "false"
if false
then
  echo "false évalué à \"true\""
else
  echo "false évalué à \"false\""
fi
# false s'évalue "false"


# Boucle while "false" (boucle nulle)
while false
do
   # Le code suivant ne sera pas exécuté.
   operation-1
   operation-2
   ...
   operation-n
   # Rien ne se passe!
done
type [cmd]

Identique à la commande externe which, type cmd identifie « cmd ». Contrairement à which, type est une commande intégrée à Bash. L'option -a est très utile pour que type identifie des mots clés et des commandes internes, et localise aussi les commandes système de nom identique.

bash$ type '['
[ is a shell builtin
bash$ type -a '['
[ is a shell builtin
 [ is /usr/bin/[


bash$ type type
type is a shell builtin
              
hash [cmds]

Enregistre le chemin des commandes spécifiées -- dans une table de hachage du shell [51] -- donc le shell ou le script n'aura pas besoin de chercher le $PATH sur les appels futurs à ces commandes. Quand hash est appelé sans arguments, il liste simplement les commandes qui ont été stockées. L'option -r réinitialise la table de hachage.

bind

La commande intégrée bind affiche ou modifie les correspondances de touche de readline [52] .

help

Récupère un petit résumé sur l'utilisation d'une commande intégrée au shell. C'est l'équivalent de whatis pour les commandes intégrées.

bash$ help exit
exit: exit [N]
    Exit the shell with a status of N.  If N is omitted, the exit status
    is that of the last command executed.
              

14.1. Commandes de contrôle des jobs

Certaines des commandes de contrôle de jobs prennent en argument un identifiant de job (job identifier). Voir la table à la fin de ce chapitre.

jobs

Liste les jobs exécutés en tâche de fond en indiquant le numéro de job. Pas aussi utile que ps.

[Note]

Note

Il est trop facile de confondre les jobs et les processus. Certaines commandes intégrées, telles que kill, disown et wait acceptent soit un numéro de job soit un numéro de processus comme argument. Les commandes fg, bg et jobs acceptent seulement un numéro de job.

bash$ sleep 100 &
[1] 1384

bash $ jobs
[1]+  Running                 sleep 100 &

« 1 » est le numéro de job (les jobs sont maintenus par le shell courant). « 1384 » est le PID ou numéro de processus (les processus sont maintenus par le système). Pour tuer ce job/processus, faites soit un kill %1 soit un kill 1384.

Merci, S.C.

disown

Supprime le(s) job(s) de la table du shell des jobs actifs.

fg, bg

La commande fg fait basculer un job, qui tournait en tâche de fond, en avant-plan. La commande bg relance un job suspendu en tâche de fond. Si aucun numéro de job n'est spécifié, alors la commande fg ou bg agit sur le job en cours d'exécution.

wait

Suspend l'exécution du script jusqu'à ce que tous les jobs en tâche de fond aient terminé, ou jusqu'à ce que le numéro de job ou l'identifiant de processus spécifié en option se termine. Retourne l'état de sortie de la commande attendue.

Vous pouvez utiliser la commande wait pour empêcher un script de se terminer avant qu'un job en arrière-plan ne finisse son exécution (ceci créerait un processus orphelin).

Exemple 14.26. Attendre la fin d'un processus avant de continuer

#!/bin/bash

ROOT_UID=0   # Seulement les utilisateurs ayant $UID 0 ont les privilèges de
             # root.
E_NONROOT=65
E_SANSPARAM=66

if [ "$UID" -ne "$ROOT_UID" ]
then
  echo "Vous devez être root pour exécuter ce script."
  # "Passe ton chemin gamin, il est temps d'aller au lit."
  exit $E_NONROOT
fi  

if [ -z "$1" ]
then
  echo "Usage: `basename $0` chaine-find"
  exit $E_SANSPARAM
fi


echo "Mise à jour de la base 'locate'..."
echo "Ceci peut prendre du temps."
updatedb /usr &     # Doit être lancé en tant que root.

wait
# Ne pas lancez le reste du script jusqu'à ce que 'updatedb' finisse.
# La base de données doit être mise à jour avant de chercher quelque chose.

locate $1

#  Sans la commande 'wait', avec le pire scénario, le script sortirait
#+ alors que 'updatedb' serait toujours en cours d'exécution,
#+ le laissant orphelin.

exit 0

Optionnellement, wait peut prendre un identifiant de job en tant qu'argument, par exemple, wait%1 ou wait $PPID. Voir la table des identifiants de job.

[Astuce]

Astuce

À l'intérieur d'un script, lancer une commande en arrière-plan avec un "et commercial" (&) peut faire que le script se bloque jusqu'à un appui sur la touche ENTER. Ceci semble arriver avec les commandes qui écrivent sur stdout. Cela peut être un gros problème.

#!/bin/bash
# test.sh

ls -l &
echo "Terminé."
bash$ ./test.sh
Terminé.
 [bozo@localhost test-scripts]$ total 1
 -rwxr-xr-x    1 bozo     bozo           34 Oct 11 15:09 test.sh
 _

               


    Comme l'explique Walter Brameld IV :

    Ces scripts ne se bloquent pas. Il semble qu'ils le fassent car la commande
    en tâche de fond écrit le texte sur la console après l'invite. L'utilisateur
    a l'impression que l'invite n'a jamais été affichée. Voici la séquence des
    événements :

    1. Le script lance la commande en tâche de fond.
    2. Le script quitte.
    3. Le shell affiche l'invite.
    4. La commande en tâche de fond continue son exécution et l'écriture du
       texte sur la console.
    5. La commande en tâche de fond se termine.
    6. L'utilisateur ne voit pas une invite en bas de l'affichage, pensant
       du coup que le script est bloqué.

Placer un wait après la commande de tâche de fond semble remédier à ceci.

#!/bin/bash
# test.sh

ls -l &
echo "Terminé."
wait
bash$ ./test.sh
Terminé.
 [bozo@localhost test-scripts]$ total 1
 -rwxr-xr-x    1 bozo     bozo           34 Oct 11 15:09 test.sh
               

Rediriger la sortie de la commande dans un fichier ou même sur /dev/null permet aussi d'éviter ce problème.

suspend

Ceci a un effet similaire à Controle+Z, mais cela suspend le shell (le processus père du shell devrait le relancer à un moment approprié).

logout

Sort d'un login shell, quelque fois en spécifiant un état de sortie.

times

Donne des statistiques sur le temps système passé lors de l'exécution des commandes de la façon suivante :

0m0.020s 0m0.020s

Cette fonctionnalité est d'une valeur relativement limitée car il est peu commun d'évaluer la rapidité des scripts shells.

kill

Force la fin d'un processus en lui envoyant le signal de terminaison approprié (voir l'Exemple 16.6, « pidof aide à la suppression d'un processus »).

Exemple 14.27. Un script qui se tue lui-même

#!/bin/bash
# self-destruct.sh

kill $$  # Le script tue son propre processus ici.
         # Rappelez-vous que "$$" est le PID du script.

echo "Cette ligne ne s'affichera pas."
# À la place, le shell envoie un message "Terminated" sur stdout.

exit 0   # Fin normale ? Non !

#  Après que le script se soit terminé prématurément,
#+ quel code de sortie retourne-t'il?
#
# sh self-destruct.sh
# echo $?
# 143
#
# 143 = 128 + 15
#             signal TERM

[Note]

Note

kill -l liste tous les signaux (comme le fait le fichier /usr/include/asm/signal.h). Un kill -9 est une mort certaine, qui terminera un processus qui refuse obstinément de mourir avec un simple kill. Quelque fois, un kill -15 fonctionne. Un processus zombie, c'est-à-dire un processus qui a terminé mais dont le processus père n'a pas encore été tué, ne peut pas être tué par un utilisateur connecté -- vous ne pouvez pas tuer quelque chose qui est déjà mort -- mais init nettoiera habituellement cela plus ou moins tôt.

killall

La commande killall tue un processus en cours d'exécution suivant son nom, plutôt que son identifiant de processus. S'il existe plusieurs instances d'une même commande, killall les tuera toutes.

[Note]

Note

Ceci fait référence à la commande killall de /usr/bin, pas au script killall dans /etc/rc.d/init.d.

command

La directive command désactive les alias et les fonctions pour la commande « COMMANDE » qui la suit immédiatement.

bash$ command ls
              
[Note]

Note

C'est une des trois directives qui modifient le traitement de commandes de script. Les autres sont des commandes intégrées et activées.

builtin

Appeler builtin COMMANDE_INTEGREE lance la commande COMMANDE_INTEGREE en tant que commande intégrée du shell, désactivant temporairement à la fois les fonctions et les commandes externes du système disposant du même nom.

enable

Ceci active ou désactive une commande intégrée du shell. Comme exemple, enable -n kill désactive la commande intégrée kill, de façon à ce que, quand Bash rencontre kill, il appelle la commande externe /bin/kill.

L'option -a d'enable liste toutes les commandes intégrées du shell, indiquant si elles sont ou non activées. L'option -f nomfichier permet à enable de charger une commande intégrée en tant que module de bibliothèque partagée (DLL) à partir d'un fichier objet correctment compilé. [53].

autoload

Ceci est une transposition à Bash du chargeur automatique de ksh. Avec autoload activé, une fonction avec une déclaration « autoload » se chargera depuis un fichier externe à sa première invocation. [54] Ceci sauve des ressources système.

Notez qu'autoload ne fait pas partie de l'installation de base de Bash. Il a besoin d'être chargé avec enable -f (voir ci-dessus).

Tableau 14.1. Identifiants de jobs

Notation Signification
%N Numéro de job [N]
%S Appel (ligne de commande) de jobs commençant par la chaîne de caractères S
%?S Appel (ligne de commande) de jobs contenant la chaîne de caractères S
%% Job « courant » (dernier job arrêté en avant-plan ou lancé en tâche de fond)
%+ Job « courant » (dernier job arrêté en avant-plan ou lancé en tâche de fond)
%- Dernier job
$! Dernier processus en tâche de fond



[46] Une exception à ceci est la commande time, listée dans la documentation Bash officielle en tant que mot clé.

[47] Exporter des informations revient à les rendre disponibles dans un contexte plus général. Voir aussi la portée.

[48] Une option est un argument agissant comme un indicateur, changeant les comportements du script de façon binaire. L'argument associé avec une option particulière indique le comportement que l'option active ou désactive.

[49] Techniquement, une commande exit termine seulement le processus ou le shell dans lequel il s'exécute, pas le processus parent.

[50] Sauf si exec est utilisé pour affecter de nouveau les descripteurs de fichiers.

[51] Le hachage (ou découpage) est une méthode pour créer des clés de recherche pour des données stockées dans une table. Les éléments de données eux-mêmes sont « découpés » pour créer des clés en utilisant un des nombreux algorithmes (méthodes ou recettes) simples de mathématiques.

Un avantage du hachage est qu'il est rapide. Un inconvénient est que les « collisions » -- où une seule clé correspond à plus d'un élément de données -- sont possibles.

Pour des exemples de hachage, voir Exemple A.22, « Bibliothèque de fonctions de hachage » et Exemple A.23, « Coloriser du texte en utilisant les fonctions de hachage ».

[52] La bibliothèque readline est utilisée par Bash pour lire les entrées utilisateur dans un shell interactif.

[53] Le source C pour un certain nombre de commandes intégrées chargeables est disponible typiquement dans le répertoire /usr/share/doc/bash-?.??/functions.

Notez que l'option -f d'enable n'est pas reconnue sur tous les systèmes.

[54] Le même effet qu'autoload peut être réalisé avec typeset -fu.