9. Un nouveau regard sur les variables

Utilisées proprement, les variables peuvent ajouter puissance et flexibilité à vos scripts. Il faut pour cela apprendre leurs nuances et subtilités.

9.1. Les variables internes

Les variables intégrées:

Variables affectant le comportement des scripts Bash.

$BASH

Le chemin vers le binaire Bash.

bash$ echo $BASH
/bin/bash
$BASH_ENV

Variable d'environnement qui pointe vers un fichier Bash d'initialisation devant être lu au début de l'invocation du script.

$BASH_SUBSHELL

Variable indiquant le niveau de sous-shell. C'est un nouvel ajout de la version 3 de Bash.

Voir l'Exemple 21.1, « Étendue des variables dans un sous-shell » pour son utilisation.

$BASHPID

numéro de processus de l'instance courante de Bash. Cette variable n'est pas identique à $$ mais elle donne souvent le même résultat.

bash4$ echo $$
11015

bash4$ echo $BASHPID
11015

bash4$ ps ax | grep bash4
11015 pts/2    R      0:00 bash4
            

Mais ...

#!/bin/bash4

echo "\$\$ à l'extérieur du sous-shell = $$"                         # 9602
echo "\$BASH_SUBSHELL à l'extérieur du sous-shell = $BASH_SUBSHELL"  # 0
echo "\$BASHPID à l'extérieur du sous-shell = $BASHPID"              # 9602

echo

( echo "\$\$ à l'intérieur du sous-shell = $$"                        # 9602
  echo "\$BASH_SUBSHELL à l'intérieur du sous-shell = $BASH_SUBSHELL" # 1
  echo "\$BASHPID à l'intérieur du sous-shell = $BASHPID" )           # 9603
  # Remarque : $$ fournit le PID du processus parent.
$BASH_VERSINFO[n]

Tableau à six éléments contenant des informations sur la version installée de Bash. Cette variable ressemble à $BASH_VERSION, ci-dessous, en un peu plus détaillé.

# Infos sur la version de Bash :

for n in 0 1 2 3 4 5
do
  echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}"
done

# BASH_VERSINFO[0] = 3                      # N° majeur de version.
# BASH_VERSINFO[1] = 00                     # N° mineur de version.
# BASH_VERSINFO[2] = 14                     # Niveau de correctifs.
# BASH_VERSINFO[3] = 1                      # Version construite.
# BASH_VERSINFO[4] = release                # État de la version.
# BASH_VERSINFO[5] = i386-redhat-linux-gnu  # Architecture.
                                            # (identique à $MACHTYPE).
$BASH_VERSION

La version de Bash installée sur le système.

bash$ echo $BASH_VERSION
3.2.25(1)-release
            
tcsh% echo $BASH_VERSION
BASH_VERSION: Undefined variable.
            

Vérifier $BASH_VERSION est une bonne méthode pour déterminer quel est le shell en cours d'exécution, $SHELL ne donnant pas nécessairement la bonne réponse.

$CDPATH

Liste, séparée par :, des chemins de recherche disponibles pour la commande cd, fonctionnant comme la variable $PATH pour les binaires. On peut positionner la variable $CDPATH dans le fichier local ~/.bashrc.

bash$ cd bash-doc
bash: cd: bash-doc: Aucun fichier ou dossier de ce type


bash$ CDPATH=/usr/share/doc
bash$ cd bash-doc
/usr/share/doc/bash-doc


bash$ echo $PWD
/usr/share/doc/bash-doc
            
$DIRSTACK

La valeur du dessus de la pile de répertoires [40] (affectée par pushd et popd)

Cette variable intégrée correspond à la commande dirs. Néanmoins, dirs affiche le contenu entier de la pile de répertoires.

$EDITOR

L'éditeur appelé par défaut par les scripts, en général vi ou emacs.

$EUID

Numéro d'identifiant « effectif » de l'utilisateur.

Numéro d'identification, quelle que soit l'identité que l'utilisateur actuel assume, peut-être suite à un su.

[Attention]

Attention

$EUID n'est pas nécessairement le même que $UID.

$FUNCNAME

Le nom de la fonction en cours d'exécution.

xyz23 ()
            {
echo "$FUNCNAME en cours d'exécution."  # xyz23 en cours d'exécution.
}

xyz23

echo "FUNCNAME = $FUNCNAME"        # FUNCNAME =
                                   # vide en dehors d'une fonction

See also Exemple A.50, « Autre version du script getopt-simple.sh ».

$GLOBIGNORE

Une liste de modèles de noms de fichiers à exclure de la correspondance lors d'un remplacement.

$GROUPS

Les groupes auxquels l'utilisateur appartient.

C'est une liste (de type tableau) des numéros d'identifiants des groupes pour l'utilisateur actuel, la même que celle enregistrée dans les fichiers /etc/passwd et /etc/group.

root# echo $GROUPS
0

root# echo ${GROUPS[1]}
1

root# echo ${GROUPS[5]}
6
              
$HOME

Répertoire personnel de l'utilisateur, en général /home/utilisateur (voir l'Exemple 10.7, « Utiliser le remplacement et les messages d'erreur »)

$HOSTNAME

La commande hostname définit le nom de l'hôte au démarrage en utilisant un script de démarrage. Néanmoins, la fonction gethostname() initialise la variable interne Bash $HOSTNAME. Voir aussi l'Exemple 10.7, « Utiliser le remplacement et les messages d'erreur ».

$HOSTTYPE

Type de l'hôte.

Comme $MACHTYPE, identifie le matériel du système.

bash$ echo $HOSTTYPE
i686
$IFS

Séparateur interne des champs de saisie.

Cette variable détermine la façon dont Bash reconnaît les champs ou les limites de mots lorsqu'il interprète des chaînes de caractères.

La valeur par défaut de $IFS est le blanc (espace, tabulation et retour chariot) mais elle peut être modifiée, par exemple pour analyser un fichier de données séparées par des virgules. Notez que $* utilise le premier caractère contenu dans $IFS. Voir l'Exemple 5.1, « Afficher des variables bizarres ».

bash$ echo "$IFS"


(Avec la valeur par défaut de $IFS, une ligne blanche apparaît.)

bash$ echo "$IFS" | cat -vte
 ^I$
 $
(Affiche les blancs : espace, ^I [tabulation horizontale],
  retour chariot, dollar ($) en fin de ligne.)

bash$ bash -c 'set w x y z; IFS=":-;"; echo "$*"'
w:x:y:z
(Lit les commandes à partir de la chaîne et affecte tout
argument suivant les paramètres de position)
              
[Attention]

Attention

$IFS ne gère pas les blancs de la même façon que les autres caractères.

Exemple 9.1. $IFS et blancs

#!/bin/bash
# ifs.sh

var1="a+b+c"
var2="d-e-f"
var3="g,h,i"

IFS=+
# The plus sign will be interpreted as a separator.
echo $var1     # a b c
echo $var2     # d-e-f
echo $var3     # g,h,i

echo

IFS="-"
# The plus sign reverts to default interpretation.
# The minus sign will be interpreted as a separator.
echo $var1     # a+b+c
echo $var2     # d e f
echo $var3     # g,h,i

echo

IFS=","
# The comma will be interpreted as a separator.
# The minus sign reverts to default interpretation.
echo $var1     # a+b+c
echo $var2     # d-e-f
echo $var3     # g h i

echo

IFS=" "
# The space character will be interpreted as a separator.
# The comma reverts to default interpretation.
echo $var1     # a+b+c
echo $var2     # d-e-f
echo $var3     # g,h,i

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

# However ...
# $IFS traite les blancs différemment des autres caractères.

affiche_un_argument_par_ligne()
{
  for arg
  do 
    echo "[$arg]"
  done #  ^    ^   Entouré de crochets, juste pour le plaisir des yeux.
}

echo; echo "IFS=\" \""
echo "-------"

IFS=" "
var=" a  b c   "
#    ^ ^^   ^^^
affiche_un_argument_par_ligne $var  # affiche_un_argument_par_ligne `echo " a  b c   "`
# [a]
# [b]
# [c]


echo; echo "IFS=:"
echo "-----"

IFS=:
var=":a::b:c:::"        # Comme ci-dessus, mais en remplaçant " " par ":".
#    ^ ^^   ^^^         #+ mais en remplaçant " " par ":"
affiche_un_argument_par_ligne $var
# []
# [a]
# []
# [b]
# [c]
# []
# []
# []

# Remarquez les crochets "vides".
# La même chose se produit avec le séparateur de champs "FS" dans awk.

echo

exit 0


(Merci beaucoup, Stéphane Chazelas, pour cette clarification et les exemples ci-dessus.)

Voir aussi l'Exemple 16.41, « Analyser le domaine d'un courrier indésirable » , Exemple 11.7, « Un remplaçant de grep pour les fichiers binaires » et Exemple 19.14, « Analyser une boîte mail » pour des exemples instructifs sur l'utilisation de $IFS.

$IGNOREEOF

Ignore EOF : le nombre de fins de fichier (control-D) que le shell va ignorer avant de se déconnecter.

$LC_COLLATE

Souvent présente dans les fichiers .bashrc ou /etc/profile, cette variable contrôle l'ordre d'examen dans l'expansion des noms de fichiers et les correspondances de motifs. Si elle est mal gérée, LC_COLLATE peut apporter des résultats inattendus dans le remplacement de noms de fichiers.

[Note]

Note

À partir de la version 2.05 de Bash, le remplacement de noms de fichiers ne tient plus compte des lettres en minuscules et en majuscules dans une suite de caractères entre crochets. Par exemple, ls [A-M]* correspondrait à la fois à Fichier1.txt et à fichier1.txt. Pour annuler le comportement personnalisé de la correspondance par crochets, initialisez LC_COLLATE à C par un export LC_COLLATE=C dans /etc/profile et/ou ~/.bashrc.

$LC_CTYPE

Cette variable interne contrôle l'interprétation des caractères pour le remplacement et la correspondance de motifs.

$LINENO

Cette variable correspond au numéro de ligne du script shell dans lequel cette variable apparaît. Elle n'a une signification que dans le script où elle apparaît, ce qui fait qu'elle est essentiellement utilisée dans les phases de débogage.

# *** DEBUT BLOC DEBUG ***
le_dernier_argument_de_la_commande=$_  # Le sauver.

echo "À la ligne numéro $LINENO, la variable \"v1\" = $v1"
echo "Dernier argument de la ligne exécutée = $le_dernier_argument_de_la_commande"
# *** FIN BLOC DEBUG ***
$MACHTYPE

Type de la machine.

Identifie le matériel du système.

bash$ echo $MACHTYPE
i686
$OLDPWD

Ancien répertoire courant (« OLD-Print-Working-Directory », le dernier répertoire où vous étiez).

$OSTYPE

Type de système d'exploitation.

bash$ echo $OSTYPE
linux
$PATH

Chemin vers les binaires, habituellement /usr/bin/, /usr/X11R6/bin/, /usr/local/bin, etc.

Lorsqu'une commande est fournie, le shell recherche automatiquement l'exécutable dans les répertoires listés dans le chemin. Le chemin est stocké dans la variable d'environnement, $PATH, une liste de répertoires séparés par le symbole ":". Normalement, le système enregistre la définition de $PATH dans /etc/profile et/ou ~/.bashrc (voir l'Annexe G, Fichiers importants)

bash$ echo $PATH
/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin

PATH=${PATH}:/opt/bin ajoute le répertoire /opt/bin au chemin actuel. Dans un script, il peut être avantageux d'ajouter temporairement un répertoire au chemin de cette façon. Lorsque le script se termine, le $PATH original est restauré (un processus fils, tel qu'un script, ne peut pas changer l'environnement du processus père, le shell).

[Note]

Note

Le « répertoire » courant, ./, est habituellement omis de $PATH pour des raisons de sécurité.

$PIPESTATUS

Variable de type tableau contenant le(s) code(s) de sortie de la dernière commande de premier plan exécutée via un tube.

bash$ echo $PIPESTATUS
0

bash$ ls -al | fausse_commande
bash: fausse_commande: commande introuvable
bash$ echo ${PIPESTATUS[1]}
127

bash$ ls -al | fausse_commande
bash: fausse_commande: commande introuvable
bash$ echo $?
127
              

Les membres du tableau $PIPESTATUS contiennent le code de sortie de chaque commande respective exécutée via un tube. $PIPESTATUS[0] contient le code de sortie de la première commande du tube, $PIPESTATUS[1] le code de sortie de la deuxième commande et ainsi de suite.

[Attention]

Attention

La variable $PIPESTATUS peut contenir une valeur 0 erronée dans un shell de connexion (sur les versions de Bash antérieures à 3.0).

tcsh% bash

bash$ who | grep nobody | sort
bash$ echo ${PIPESTATUS[*]}
0
              

Les lignes ci-dessus contenues dans un script produiraient le résultat attendu, 0 1 0.

Merci à Wayne Pollock pour avoir partagé cette information en apportant l'exemple ci-dessus.

[Note]

Note

La variable $PIPESTATUS donne des résultats inattendus dans certains contextes.

bash$ echo $BASH_VERSION
3.00.14(1)-release

bash$ $ ls | fausse_commande | wc
bash: fausse_commande: commande introuvable
 0       0       0

bash$ echo ${PIPESTATUS[@]}
141 127 0
              

Chet Ramey attribue l'affichage ci-dessus au comportement de ls. Si ls écrit dans un tube dont la sortie n'est pas lue, alors SIGPIPE le tue et son code de sortie est 141. Sinon, son code de sortie est 0, comme attendu. C'est certainement le cas pour tr.

[Note]

Note

$PIPESTATUS est une variable « volatile ». Elle doit être immédiatement capturée après le tube, c'est-à-dire avant que d'autres commandes n'interviennent.

bash$ $ ls | fausse_commande | wc
bash: fausse_commande: commande introuvable
 0       0       0

bash$ echo ${PIPESTATUS[@]}
0 127 0

bash$ echo ${PIPESTATUS[@]}
0
                    
[Note]

Note

L'option pipefail peut être utile dans les cas où $PIPESTATUS ne donne pas l'information souhaitée.

$PPID

Le $PPID d'un processus est l'identifiant du processus (PID) père. [41]

À comparer avec la commande pidof.

$PROMPT_COMMAND

Une variable contenant une commande à exécuter juste avant l'affichage de l'invite principale, $PS1.

$PS1

Ceci est l'invite principale, vue sur la ligne de commande.

$PS2

La deuxième invite, vue lorsqu'une saisie supplémentaire est attendue. Elle s'affiche comme « > ».

$PS3

La troisième invite, affichée lors d'une boucle select (voir l'Exemple 11.29, « Créer des menus en utilisant select »)

$PS4

La quatrième invite, affichée au début de chaque ligne d'affichage lorsqu'un script a été appelé avec l'option -x. Elle affiche un « + ».

$PWD

Répertoire courant (celui où vous vous trouvez sur le moment)

Analogue à la commande intégrée pwd.

#!/bin/bash

E_MAUVAIS_REPERTOIRE=73

clear # Efface l'écran.

RepertoireCible=/home/bozo/projects/GreatAmericanNovel

cd $RepertoireCible
echo "Suppression des anciens fichiers de $RepertoireCible."

if [ "$PWD" != "$RepertoireCible" ]
then    # Empêche la suppression d'un mauvais répertoire par accident.
  echo "Mauvais répertoire!"
  echo "Dans $PWD, plutôt que $RepertoireCible!"
  echo "Je quitte!"
  exit $E_MAUVAIS_REPERTOIRE
fi  

rm -rf *
rm .[A-Za-z0-9]*    # Supprime les fichiers commençant par un point.
# rm -f .[^.]* ..?*   pour supprimer les fichiers commençant par plusieurs points.
# (shopt -s dotglob; rm -f *)   fonctionnera aussi.
# Merci, S.C., pour nous l'avoir indiqué.

# Les noms de fichier peuvent contenir tous les caractères de 0 à 255, 
# à l'exception de "/".
# La suppression des fichiers commençant par des caractères bizarres est laissée
# en exercice.

# Autres opérations ici, si nécessaire.

echo
echo "Fait."
echo "Anciens fichiers supprimés de $RepertoireCible."
echo


exit 0
$REPLY

La variable par défaut lorsqu'aucune n'est adjointe au read. Aussi applicable au menu select, mais renvoie seulement le numéro de l'élément de la variable choisie et non pas la valeur de la variable elle-même.

#!/bin/bash
# reply.sh

# REPLY est la variable par défaut d'une commande 'read'

echo
echo -n "Quel est votre légume favori? "
read

echo "Votre légume favori est $REPLY."
#  REPLY contient la valeur du dernier "read" si et seulement si aucune variable
#+ n'est spécifiée.

echo
echo -n "Quel est votre fruit favori? "
read fruit
echo "Votre fruit favori est $fruit."
echo "mais..."
echo "La valeur de \$REPLY est toujours $REPLY."
#  $REPLY est toujours initialisé à sa précédente valeur car la variable $fruit
#+ a absorbé la nouvelle valeur obtenue par "read".

echo

exit 0
$SECONDS

Le nombre de secondes écoulées depuis le début de l'exécution du script.

#!/bin/bash

LIMITE_TEMPS=10
INTERVALLE=1

echo
echo "Appuyez sur Control-C pour sortir avant $LIMITE_TEMPS secondes."
echo

while [ "$SECONDS" -le "$LIMITE_TEMPS" ]
do
  if [ "$SECONDS" -eq 1 ]
  then
    unites=seconde
  else  
    unites=secondes
  fi

  echo "Ce script tourne depuis $SECONDS $unites."
  #  Sur une machine lente, le script peut laisser échapper quelquefois
  #+ un élément du comptage dans la boucle while.
  sleep $INTERVALLE
done

echo -e "\a"  # Beep!

exit 0
$SHELLOPTS

La liste des options activées du shell, variable en lecture seule.

bash$ echo $SHELLOPTS
braceexpand:hashall:histexpand:monitor:history:interactive-comments:emacs
              
$SHLVL

Niveau du shell, à quel point Bash est imbriqué. [42] Si, à la ligne de commande, $SHLVL vaut 1, alors dans un script, il sera incrémenté et prendra la valeur 2.

[Note]

Note

Cette variable n'est pas affectée par les sous-shells. Utilisez $BASH_SUBSHELL quand vous avez besoin d'une indication d'une imbrication de sous-shell.

$TMOUT

Si la variable d'environnement $TMOUT est initialisée à une valeur différente de zéro appelée time, alors l'invite shell dépassera son délai au bout de time secondes. Ceci causera une déconnexion.

À partir de la version 2.05b de Bash, il est possible d'utiliser $TMOUT dans un script avec un read.

#  Fonctionne avec des scripts pour Bash, versions
#+ 2.05b et ultérieures.

TMOUT=3    # L'invite s'arrête dans trois secondes.

echo "Quelle est votre chanson favorite?"
echo "Faites vite car vous n'avez que $TMOUT secondes pour répondre !"
read chanson

if [ -z "$chanson" ]
then
  chanson="(sans réponse)"
  # Réponse par défaut.
fi

echo "Votre chanson favorite est $chanson."

Il existe d'autres façons, plus complexes, pour implémenter une entrée avec temporisation. Une alternative consiste à configurer une boucle rythmée pour signaler au script la fin de l'attente. Ceci requiert aussi une routine de gestion du signal pour récupérer (voir l'Exemple 32.5, « Récupérer la sortie ») l'interruption créée par la boucle.

Exemple 9.2. Saisie avec un délai

#!/bin/bash
# timed-input.sh

# TMOUT=3            Fonctionne aussi, depuis les dernières versions de Bash.

INTERRUPTION_TIMER=14
LIMITETEMPS=3  # Trois secondes dans cette instance
               # Peut être configuré avec une valeur différente.

AfficheReponse()
{
  if [ "$reponse" = TIMEOUT ]
  then
    echo $reponse
  else       # ne pas mixer les deux interfaces.
    echo "Votre légume favori est le $reponse"
    kill $!  #  Kill n'est plus nécessaire pour la fonction TimerOn lancée en
             #+ tâche de fond.
             # $! est le PID du dernier job lancé en tâche de fond.
  fi

}  


TimerOn()
{
  sleep $LIMITETEMPS && kill -s 14 $$ &
  # Attend trois secondes, puis envoie sigalarm au script.
}  


VecteurInt14()
{
  reponse="TIMEOUT"
  AfficheReponse
  exit $INTERRUPTION_TIMER
}  

trap VecteurInt14 $INTERRUPTION_TIMER   #  Interruption de temps (14)
                                        #+ détournée pour notre but.

echo "Quel est votre légume favori?"
TimerOn
read reponse
AfficheReponse


#  C'est une implémentation détournée de l'entrée de temps.
#  Néanmoins l'option "-t" de "read" simplifie cette tâche.
#  Voir le script "t-out.sh", ci-dessous.

#  Si vous avez besoin de quelque chose de réellement élégant...
#+ pensez à écrire l'application en C ou C++,
#+ en utilisant les fonctions de la bibliothèque appropriée, telles que
#+ 'alarm' et 'setitimer'.
#  Néanmoins, pourquoi ne pas chronométrer un script complet,
#+ à la place d'une simple saisie d'un utilisateur ?

exit 0

Autre méthode avec stty.

Exemple 9.3. Encore une fois, saisie avec délai

#!/bin/bash
# timeout.sh

#  Écrit par Stephane Chazelas,
#+ et modifié par l'auteur de ce document.

INTERVALLE=5                # délai

lecture_delai() {
  delai=$1
  nomvariable=$2
  ancienne_configuration_tty=`stty -g`
  stty -icanon min 0 time ${delai}0
  eval read $nomvariable      # ou simplement    read $nomvariable
  stty "$ancienne_configuration_tty"
  # Voir la page man de "stty".
}

echo; echo -n "Quel est votre nom ? Vite !"
lecture_delai $INTERVALLE votre_nom

#  Ceci pourrait ne pas fonctionner sur tous les types de terminaux.
#+ Le temps imparti dépend du terminal (il est souvent de 25,5 secondes).

echo

if [ ! -z "$votre_nom" ]  #  Si le nom est entré avant que le temps ne se soit
                          #+ écoulé...
then
  echo "Votre nom est $votre_nom."
else
  echo "Temps écoulé."
fi

echo

# Le comportement de ce script diffère un peu de "timed-input.sh".
# À chaque appui sur une touche, le compteur est réinitialisé.

exit 0

Peut-être que la méthode la plus simple est d'utiliser l'option -t de read.

Exemple 9.4. Lecture avec délai

#!/bin/bash
# t-out.sh
# Inspiré d'une suggestion de "syngin seven" (merci).

LIMITETEMPS=4        # Quatre secondes

read -t $LIMITETEMPS variable <&1
#                             ^^^
#  Dans ce cas, "<&1" est nécessaire pour Bash 1.x et 2.x,
#  mais inutile pour Bash 3.x.

echo

if [ -z "$variable" ]  # Est nul ?
then
  echo "Temps écoulé, la variable n'est toujours pas initialisée."
else  
  echo "variable = $variable"
fi  

exit 0

$UID

Numéro de l'identifiant utilisateur.

Numéro d'identification de l'utilisateur actuel, tel qu'enregistré dans /etc/passwd .

C'est l'identifiant réel de l'utilisateur actuel, même s'il a temporairement endossé une autre identité avec su. $UID est une variable en lecture seule, non sujette au changement à partir de la ligne de commande ou à l'intérieur d'un script, et est la contrepartie de l'intégré id.

Exemple 9.5. Suis-je root ?

#!/bin/bash
# am-i-root.sh:   Suis-je root ou non ?

ROOT_UID=0   # Root a l'identifiant $UID 0.

if [ "$UID" -eq "$ROOT_UID" ]  # Le vrai "root" peut-il se lever, s'il-vous-plaît ?
then
  echo "Vous êtes root."
else
  echo "Vous n'êtes qu'un utilisateur ordinaire (mais maman vous aime quand même)."
fi

exit 0


# ============================================================================= #
# Le code ci-dessous ne s'exécutera pas, parce que le script s'est déjà arrêté.

# Une autre méthode pour arriver au même résultat :

NOM_UTILISATEURROOT=root

nomutilisateur=`id -nu`              # Ou...   nomutilisateur=`whoami`
if [ "$nomutilisateur" = "$NOM_UTILISATEURROOT" ]
then
  echo "Vous êtes root."
else
  echo "Vous n'êtes qu'une personne ordinaire."
fi

Voir aussi l'Exemple 2.3, « cleanup : Une version améliorée et généralisée des scripts précédents ».

[Note]

Note

Les variables $ENV, $LOGNAME, $MAIL, $TERM, $USER et $USERNAME ne sont pas des variables intégrées à Bash. Néanmoins, elles sont souvent initialisées comme variables d'environnement dans l'un des fichiers de démarrage de Bash. $SHELL, le nom du shell de connexion de l'utilisateur, peut être configuré à partir de /etc/passwd ou dans un script d'« initialisation », et ce n'est pas une variable intégrée à Bash.

tcsh% echo $LOGNAME
bozo
tcsh% echo $SHELL
/bin/tcsh
tcsh% echo $TERM
rxvt

bash$ echo $LOGNAME
bozo
bash$ echo $SHELL
/bin/tcsh
bash$ echo $TERM
rxvt
                

Paramètres de position

$0, $1, $2, etc.

Paramètres de position passés par la ligne de commande à un script, ou passés à une fonction, ou attribués (set) à une variable (voir l'Exemple 4.5, « Paramètres de position  » et l'Exemple 15.16, « Utiliser set avec les paramètres de position »)

$#

Nombre d'arguments sur la ligne de commande [43] ou de paramètres de position (voir l'Exemple 36.2, « Un script d'appel légèrement plus complexe »)

$*

Tous les paramètres de position, vus comme un seul mot.

[Note]

Note

"$*" doit être entre guillemets.

$@

Identique à $*, mais chaque paramètre est une chaîne entre guillemets, c'est-à-dire que les paramètres sont passés de manière intacte, sans interprétation ni expansion. Ceci signifie, entre autres, que chacun des paramètres de la liste des arguments est vu comme un mot séparé.

[Note]

Note

Bien sûr, "$@" doit être placé entre guillemets.

Exemple 9.6. arglist : Affichage des arguments avec $* et $@

#!/bin/bash
# arglist.sh
# Appelez ce script avec plusieurs arguments, tels que "un deux trois".

E_MAUVAISARGS=65

if [ ! -n "$1" ]
then
  echo "Usage: `basename $0` argument1 argument2 etc."
  exit $E_MAUVAISARGS
fi  

echo

index=1    # Initialise le compteur.

echo "Liste des arguments avec \"\$*\" :"
for arg in "$*"  # Ne fonctionne pas correctement si "$*" n'est pas entre guillemets.
do
  echo "Arg #$index = $arg"
  let "index+=1"
done             # $* voit tous les arguments comme un mot entier. 
echo "Liste entière des arguments vue comme un seul mot."

echo

index=1    # Ré-initialisation du compteur.
           # Qu'arrive-t'il si vous oubliez de le faire ?

echo "Liste des arguments avec \"\$@\" :"
for arg in "$@"
do
  echo "Arg #$index = $arg"
  let "index+=1"
done             # $@ voit les arguments comme des mots séparés. 
echo "Liste des arguments vue comme des mots séparés."

echo

index=1    # Ré-initialisation du compteur.

echo "Liste des arguments avec \$* (sans guillemets) :"
for arg in $*
do
  echo "Argument #$index = $arg"
  let "index+=1"
done             # $* sans guillemets voit les arguments comme des mots séparés.
echo "Liste des arguments vue comme des mots séparés."


exit 0

Après un shift, $@ contient le reste des paramètres de la ligne de commande, sans le $1 précédent qui a été perdu.

#!/bin/bash
# Appelé avec ./script 1 2 3 4 5

echo "$@"    # 1 2 3 4 5
shift
echo "$@"    # 2 3 4 5
shift
echo "$@"    # 3 4 5

# Chaque "shift" perd le paramètre $1.
# "$@" contient alors le reste des paramètres.

Le paramètre spécial $@ trouve son utilité comme outil pour filtrer l'entrée des scripts shell. L'expression cat "$@" accepte l'entrée dans un script soit à partir de stdin, soit à partir de fichiers donnés en paramètres du script. Voir l'Exemple 16.24, « rot13 : rot13, cryptage ultra-faible. » et l'Exemple 16.25, « Générer des énigmes « Crypto-Citations » ».

[Attention]

Attention

Les paramètres $* et $@ affichent quelquefois un comportement incohérent et bizarre, suivant la configuration de $IFS.

Exemple 9.7. Comportement de $* et $@ incohérent

#!/bin/bash

#  Comportement non prévisible des variables internes Bash "$*" et "$@",
#+ suivant qu'elles sont, ou non, placées entre guillemets.
#  Gestion incohérente de la séparation de mots et des retours chariot.


set -- "Premier un" "second" "troisième:un" "" "Cinquième: :un"
# Initialise les arguments du script, $1, $2, etc.

echo

echo 'IFS inchangée, avec "$*"'
c=0
for i in "$*"               # entre guillemets
do echo "$((c+=1)): [$i]"   # Cette ligne reste identique à chaque instance.
                            # Arguments de echo.
done
echo ---

echo 'IFS inchangée, avec $*'
c=0
for i in $*                 # sans guillemets
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS inchangée, avec "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS inchangée, avec $@'
c=0
for i in $@
do echo "$((c+=1)): [$i]"
done
echo ---

IFS=:
echo 'IFS=":", avec "$*"'
c=0
for i in "$*"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", avec $*'
c=0
for i in $*
do echo "$((c+=1)): [$i]"
done
echo ---

var=$*
echo 'IFS=":", avec "$var" (var=$*)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", avec $var (var=$*)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

var="$*"
echo 'IFS=":", avec $var (var="$*")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", avec "$var" (var="$*")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", avec "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", avec $@'
c=0
for i in $@
do echo "$((c+=1)): [$i]"
done
echo ---

var=$@
echo 'IFS=":", avec $var (var=$@)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", avec "$var" (var=$@)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

var="$@"
echo 'IFS=":", avec "$var" (var="$@")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---

echo 'IFS=":", avec $var (var="$@")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done

echo

# Essayez ce script avec ksh ou zsh -y.

exit 0

# Ce script d'exemple est de Stéphane Chazelas,
# légèrement modifié par l'auteur de ce document.

[Note]

Note

Les paramètres $@ et $* diffèrent seulement lorsqu'ils sont entre guillemets.

Exemple 9.8. $* et $@ lorsque $IFS est vide

#!/bin/bash

#+ Si $IFS est initialisé mais vide,
#+ alors "$*" et "$@" n'affichent pas les paramètres de position
#+ tout à fait comme prévu.

mecho ()       # Affiche les paramètres de position.
{
echo "$1,$2,$3";
}


IFS=""         # Initialisé, mais vide.
set a b c      # Paramètres de position.

mecho "$*"     # abc,,
mecho $*       # a,b,c

mecho $@       # a,b,c
mecho "$@"     # a,b,c

#  Le comportement de $* et $@ quand $IFS est vide dépend de la version de
#+ Bash ou sh.
#  Personne ne peut donc conseiller d'utiliser cette «fonctionnalité» 
#+ dans un script.

# Merci à Stéphane Chazelas.

exit 0

Autres paramètres spéciaux

$-

Les options passées au script (en utilisant set). Voir l'Exemple 15.16, « Utiliser set avec les paramètres de position ».

[Attention]

Attention

À l'origine, ette construction vient de ksh. Elle a été adoptée dans Bash mais, malheureusement, ne semble pas fonctionner de façon fiable dans les scripts Bash. On peut s'en servir par exemple pour obtenir un script qui teste lui-même s'il est interactif.

$!

Identifiant du processus (PID) du dernier job ayant fonctionné en tâche de fond.

TRACE=$0.log

COMMANDE1="sleep 100"

echo "Trace des PID des commandes en tâche de fond pour le script : $0" >> "$TRACE"
# Pour qu'ils soient enregistrés et tués si nécessaire.
echo >> "$TRACE"

# Commandes de trace.

echo -n "PID de \"$COMMANDE1\" :  " >> "$TRACE"
${COMMANDE1} &
echo $! >> "$TRACE"
# PID de "sleep 100" :  1506

# Merci, Jacques Lederer, pour cette suggestion.

Utiliser $! pour contrôler un job :

job_qui_peut_se_bloquer & { sleep ${TIMEOUT}; eval 'kill -9 $!' &> /dev/null; }
# Force la fin d'un programme qui se comporte mal.
# Utile, par exemple, dans les scripts d'initialisation.

# Merci, Sylvain Fourmanoit, pour cette utilisation ingénieuse de la variable "!".

Ou autrement :

# Exemple de Matthew Sage.
# Utilisé avec sa permission.

DELAI=30   # Délai d'attente en secondes
nombre=0

job_qui_peut_se_bloquer & {
        while ((nombre < DELAI )); do
                eval '[ ! -d "/proc/$!" ] && ((count = TIMEOUT))'
                #  /proc est l'endroit où sont disponibles des informations
                #+ sur les processus en cours d'exécution.
                # "-d" teste si le répertoire existe.
                # Donc, nous attendons que le job en question se manifeste.
                ((nombre++))
                sleep 1
        done
        eval '[ -d "/proc/$!" ] && kill -15 $!'
        # Si le job est en cours d'exécution, tuons-le.
}
$_

Variable spéciale contenant le dernier argument final de la dernière commande exécutée.

Exemple 9.9. Variable tiret bas

#!/bin/bash

echo $_              # /bin/bash
                     # Simple appel de /bin/bash pour lancer ce script.
                     # Remarque : des variations sont ici possibles,
                     #+ selon la manière dont le script est appelé.

du >/dev/null        # Donc pas de sortie des commandes
echo $_              # du

ls -al >/dev/null    # Donc pas de sortie des commandes
echo $_              # -al  (dernier argument)

:
echo $_              # :

$?

Code de sortie d'une commande, d'une fonction ou du script lui-même (voir l'Exemple 24.7, « Maximum de deux nombres »)

$$

Identifiant (PID) du processus du script lui-même. [44] La variable $$ est souvent employée dans les scripts pour construire des noms de fichiers temporaires « uniques » (voir l'Exemple 32.6, « Nettoyage après un Control-C », l'Exemple 16.31, « Déballer une archive rpm », et l'Exemple 15.27, « Un script qui se tue lui-même »). Cette méthode est en général plus simple que l'appel à mktemp.



[40] Une pile de répertoires est un ensemble d'emplacements mémoire consécutifs, construit de sorte que les valeurs qu'on lui ajoute (push) sont ensuite récupérées (pop) dans l'ordre inverse. La dernière valeur ajoutée est la première récupérée. On parle parfois de pile LIFO (last-in-first-out) ou pushdown, ou pile, tout simplement.

[41] Le PID du script en cours est $$, bien sûr.

[42] Un peu analogue à la récursion, dans ce contexte, l'imbrication fait référence à un sous-motif inclus dans un motif plus large. Une des définitions de nest, d'après l'édition 1913 du dictionnaire Webster, illustre très bien ce cas : « une collection de boîtes, cases ou d'objets de ce type, de taille progressive, placées les unes dans les autres. »

[43] Les mots « argument » et « paramètre » sont souvent utilisés sans distinction. Dans le contexte de ce document, ils ont exactement la même signification, celle d'une variable passée à un script ou à une fonction.

[44] Dans un script, dans un sous-shell, $$ renvoie le PID du script, pas celui du sous-shell.