Chapitre 11. 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 lité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 exemple, << 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. [1]

I/O

echo

envoie (vers stdout) une expression ou une variable (voir Exemple 4-1).
echo Hello
echo $a

Un echo nécessite l'option -e pour afficher des séquences d'échappement. Voir Exemple 5-2.

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

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 séquence \"txt\""
fi

Note

Un echo, en combinaison avec une substitution de commande peut configurer une variable.

a=`echo "HELLO" | tr A-Z a-z`

Voir aussi Exemple 12-15, Exemple 12-2, Exemple 12-32 et Exemple 12-33.

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 blanc. 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 362 Nov 7 2000 seconds.au
	      

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... parameter...

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

Les anciennes versions de Bash peuvent ne pas supporter printf.

Exemple 11-1. 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  # Insert une tabulation (\t)

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

echo

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

echo 

Pi12=$(printf "%1.12f" $PI)
echo "Pi avec 12 décimales = $Pi12"

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

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

E_BADDIR=65

var=repertoire_inexistant

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

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 d'obtenir des valeurs tableau (voir Exemple 26-3).

Exemple 11-2. Affectation d'une variable, en utilisant read

#!/bin/bash

echo -n "Entrez la valeur de la variable 'var1': "
# L'option -n pour 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, le(s) autre(s) variable(s) resteront
#+ non initialisées (null).

exit 0

Un read sans variable associée utilise en entrée la valeur de la variable par dédiée $REPLY.

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

#!/bin/bash

echo

# -------------------------- #
# Premier bloc de code.
echo -n "Entrez une valeur: "
read var
echo "\"var\" = "$var""
# Tout se passe comme convenu.
# -------------------------- #

echo

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

echo

exit 0

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

Exemple 11-4. Entrée à plusieurs lignes par read

#!/bin/bash

echo

echo "Entrez une chaîne de caractères terminée par un \\, puis appuyez sur <ENTER>."
echo "Ensuite, entrez une deuxième chaîne de caractères, et encore une fois appuyez 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 "Entrez 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 litéralement.
              #     première ligne \

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

# L'entrée 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 " keypress
echo; echo "La touche était "\"$keypress\""."

# 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 ils nécessitent d'être saisis 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 11-5. 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

#  Exercices:
#  ---------
#  1) Simplifier ce script en ré-écrivant de multiples tests "if" en une
#+    construction 'case'.
#  2) Ajouter la détection des touches "Home", "End", "PgUp" et "PgDn".

L'option -t pour read permet des réponses dépendantes du temps (voir Exemple 9-4).

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ètres, alors chacune des variables se voit assignée une suite de mots séparés par des espaces blancs. Attention!

Exemple 11-6. 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é avec la 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 un
#    un espace blanc 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.
# Cet 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

Envoyer la sortie d'un tube vers une commande read, en utilisant echo pour configurer les échouera.

Néanmoins, envoyer la sortie du tube d'un cat semble fonctionner.
cat file1 file2 |
while read ligne
do
echo $ligne
done

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écifié.

(cd /source/directory && tar cf - . ) | (cd /dest/directory && 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 - change le répertoire par $OLDPWD, l'ancien répertoire.

pwd

Print Working Directory (NdT: Affiche le répertoire courant). Cela donne le répertoire courant de l'utilisateur (ou du script) (voir Exemple 11-7). L'effet est identique à la lecture de la varibale intégrée $PWD.

pushd, popd, dirs

Cette 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 rep-dir

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 fonctionnant 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 11-7. 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 in directory `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

Variables

let

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

Exemple 11-8. Laisser let faire un peu d'arithmétique.

#!/bin/bash

echo

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

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

let "a /= 4"      # Equivalent à  let "a = a / 4"
echo "128 / 4 = $a"

let "a -= 5"      # Equivalent à  let "a = a - 5"
echo "32 - 5 = $a"

let "a = a * 10"  # Equivalent à  let "a = a * 10"
echo "27 * 10 = $a"

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

echo

exit 0
eval

eval arg1 [arg2] ... [argN]

Transforme en commande les arguments de la liste (utile pour générer du code dans un script).

Exemple 11-9. Montrer l'effet d'eval

#!/bin/bash

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`     # Similaaire à y=`df`
echo $y         # mais les retours chariot ont été supprimés.

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

exit 0

Exemple 11-10. Forcer une déconnexion

#!/bin/bash

y=`eval ps ax | sed -n '/ppp/p' | awk '{ print $1 }'`
# Trouve le numéro de processus de 'ppp'.

kill -9 $y   # Le tue.

# Les lignes ci-dessus peuvent être remplacées par
#  kill -9 `ps ax | awk '/ppp/ { print $1 }'


chmod 666 /dev/ttyS3
# Faire un SIGKILL sur ppp modifie les droits sur le port série.
# Les restaurer à leur état initial.

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

exit 0

Exemple 11-11. 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

echo $var | tr a-z n-za-m    # foobar
                             # Revenu à la variable originale.

# Exemple de Stephane Chazelas.

exit 0

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

Exemple 11-12. Utiliser eval pour forcer une substitution de variable dans un script Perl

In the Perl script "test.pl":
        ...		
        my $WEBROOT = <WEBROOT_PATH>;
        ...

To force variable substitution try:
        $export WEBROOT_PATH=/usr/local/webroot
        $sed 's/<WEBROOT_PATH>/$WEBROOT_PATH/' < test.pl > out

But this just gives:
        my $WEBROOT = $WEBROOT_PATH;

However:
        $export WEBROOT_PATH=/usr/local/webroot
        $eval sed 's/<WEBROOT_PATH>/$WEBROOT_PATH/' < test.pl > out
#        ====

That works fine, and gives the expected substitution:
        my $WEBROOT = /usr/local/webroot

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 internes au script. Une utilisation est de basculer des options qui aident à déterminer le comportement du script. Une autre application est de réinitialiser les paramètres de position qu'un script voit en résultat d'une commande (set `commande`). Le script peut ensuite analyser les champs de la sortie de la commande.

Exemple 11-13. 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

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. Quand aucune variable ne suit --, cela déconfigure les paramètres de positions.

Exemple 11-14. 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.
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 Exemple 10-2 et Exemple 12-40.

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 11-15. << 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.

exit 0
export

La commande export rend disponibles des variables aux processus fils du script ou shell en cours d'exécution. Malheureusement, il n'existe pas de moyen pour exporter des variables au processus père. Une utilisation importante de la commande export est dans les fichiers de démarrage, pour initialiser et rendre accessible les variables d'environnement aux processus utilisateur suivants.

Exemple 11-16. 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'.

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.


# Début du script awk.
# ------------------------------------------------
awk '{ total += $ENVIRON["numero_colonne"]
}
END { print total }' $nomfichier
# ------------------------------------------------
# Fin du script awk.


# Merci, Stephane Chazelas.

exit 0

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 sheel 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. [2] et les arguments associés à un script (par exemple scriptname -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 à la fois, puis décrémente la variable implicite $OPTIND pour passer à la suivante.

Note

  1. Les arguments passés à la ligne de commande vers le script doivent être précédés par un tiret (-) ou par un plus (+). Le préfixe - ou + permet à getopts de reconnaitre les arguments en ligne de commande comme des options. En fait, getopts ne passera pas les arguments sans les préfixes - ou +, 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 et moins puissante.

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 '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 11-17. Utiliser getopts pour lire les options/arguments passés à un script

#!/bin/bash

# 'getopts' analyse les arguments en ligne de commande du script.
#  Les arguments sont analysés comme des "options" (flags) et leur 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
#  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-";;
    n | o ) echo "Scénario #2: option -$Option-";;
    p     ) echo "Scénario #3: option -p-";;
    q     ) echo "Scénario #4: option -q-, with argument \"$OPTARG\"";;
    # 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.

exit 0

Comportement des scripts

source, . (commande point)

Cette commande, lorsqu'elle est appelée à partir de la ligne de commande, exécute un script. A l'intérieur d'un script, un source nom-fichier charge le fichier nom-fichier. C'est le script équivalent à la directive C/C++ #include. Elle est utile dans certaines situations où de nombreux scripts utilisent un fichier commun de données ou une bibliothèque de fonctions.

Exemple 11-18. << 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

Fichier fichier-données pour Exemple 11-18, 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
}  

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

Exemple 11-19. 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"
#  Assume 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 si il n'est pas initialisé.

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.
done  

#  Ce qui arrive ici n'est pas réellement une récursion, car le script
#+ s'étend lui-même effectivement (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:
# --------
# Ecrire un script qui utilise cette astuce pour faire quelque chose d'utile.
exit

Termine un script sans condition. 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, sauf les plus simples, avec un exit 0, indiquant un succès.

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.

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 une sortie (exit) à partir du script lorsque la commande bénéficiant du exec se termine. Pour cette raison, si un exec apparait dans un script, cela sera probablement la dernière commande.

Exemple 11-20. 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 11-21. 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 0

Un exec sert aussi à réaffecter les descripteurs de fichiers.exec <fichier-zzz remplace stdin par le fichier fichier-zzz (voir Exemple 16-1).

Note

L'option -exec pour 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 Exemple 24-1 et Exemple 24-2). Elle apparait souvent dans les fichiers de démarrage de Bash, mais a aussi son utilité dans des scripts. Il est nécessaire de disposer de la version 2, ou ultérieurs, 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.

Commandes

true

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

# Boucle sans fin
while true   # alias pour ":"
do
   operation-1
   operation-2
   ...
   operation-n
   # A besoin d'un moyen pour sortir de la boucle.
done

false

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

# Fausse boucle
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 donne le chemin complet vers << 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 identiques.

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

hash [cmds]

Enregistre le chemin des commandes spécifiées (dans une table de hachage du shell), 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é hachées. L'option -r réinitialise la table de hachage.

help

help COMMANDE cherche un petit résumé sur l'utilisation de la commande 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.
	      

Notes

[1]

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

[2]

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.