Le shell

Présentation

Le machin qui interprète les commandes.

Comment le shell trouve-t-il les commandes ?

La variable PATH contient le chemin d'accès aux commandes. Le shell l'utilise pour trouver les commandes. Il s'agit d'une liste de répertoires séparés par des :. La plupart des commandes sont en fait des programmes, c'est-à-dire des fichiers qu'on trouve dans le système de fichiers.

Par exemple, quand vous tapez ls, le shell exécute le fichier /bin/ls. Pour trouver ce fichier, il cherche dans le premier répertoire du PATH un fichier qui s'appelle ls. S'il ne trouve pas, il cherche ensuite dans le deuxième répertoire et ainsi de suite.

S'il ne trouve la commande dans aucun répertoire du PATH, le shell affiche un message d'erreur. Exemple:

chaland ~ $ sl
sl: Command not found

Commandes internes

Certaines commandes du shell ne sont pas des programmes mais des commandes internes (builtins functions). Elles sont directement reconnues et exécutées par le shell. Un exemple de commande interne est cd. C'est le répertoire courant du shell qui est modifié par cd, ce qui signifie que le script suivant :

#! /bin/sh
cd $*

ne marche pas, car le shell lance un autre shell pour exécuter le script. C'est ce sous-shell qui change son répertoire courant, et ce changement est perdu quand le sous-shell meurt.

Quels programmes utilisent le langage du shell ?

Scripts pour automatiser ou systématiser des tâches, opérations un peu compliquées.

sh : .profile (shell de login) ; sh lance un autre shell qui prend la relève pour la session. .xinitrc (pour lancer X)

Il existe un script spécial, qui est exécuté au moment où on se connecte. Ce script est contenu dans le fichier $HOME/.profile. C'est ce fichier qui vous dit s'il y a de nouveaux messages dans forum, si vous avez du courrier, etc.

Ce fichier est normalement mis à jour automatiquement par les scripts de la config conscrits. Il est néanmoins possible de le modifier pour changer des options.

La ligne de commande

Raccourcis pour les noms de fichiers

Il est parfois ennuyeux d'avoir à taper un nom complet de fichier comme nabuchodonosor. Il est encore plus ennuyeux d'avoir à taper une liste de fichier pour les donner en arguments à une commande, comme :

cc -o foo bar.c gee.c buz.c gog.c

Pour éviter ces problèmes, on peut utiliser des jokers (wildcards en anglais).

L'étoile

Une étoile * dans un nom de fichier est interprétée par le shell comme « n'importe quelle séquence de caractères » (mais ça ignore les fichiers dont le nom commence par un point). Exemple:

cc -o foo *.c

Pour interpréter l'étoile, le shell va faire la liste de tous les noms de fichiers du répertoire courant qui ne commencent pas par . et qui finissent par .c. Ensuite, il remplace *.c par cette liste (triée par ordre alphabétique) dans la ligne de commande, et exécute le résultat, c'est-à-dire par exemple :

cc -o foo bar.c buz.c foo.c gee.c gog.c

Le point d'interrogation

On a aussi le point d'interrogation ?, qui remplace un (et exactement un) caractère quelconque (sauf un point en début de nom). Par exemple, ls *.? liste tous les dont l'extension ne comporte qu'un caractère (.c, .h...).

Les crochets

La forme [abcd] remplace un caractère quelconque parmi a, b, c, d. Enfin, [^abcd] remplace un caractère quelconque qui ne se trouve pas parmi a, b, c, d.

Exemple

echo /users/*

affiche à peu près la même chose que

ls /users

(La commande echo se contente d'afficher ses arguments.)

&icone.attention; Attention :

Interlude: comment effacer un fichier nommé ?* ? On ne peut pas taper rm ?* car le shell remplace ?* par la liste de tous les fichiers du répertoire courant. On peut taper rm -i * qui supprime tous les fichiers, mais en demandant confirmation à chaque fichier. On répond n à toutes les questions sauf rm: remove  ?*. Autre méthode: utiliser les mécanismes de quotation.

Quotation

Avec tous ces caractères spéciaux, comment faire pour passer des arguments bizarres à une commande ? Par exemple, comment faire afficher un point d'interrogation suivi d'une étoile et d'un dollar par echo ? Le shell fournit des mécanismes pour ce faire. Ce sont les quotations.

Le backslash (\)

Il suffit de précéder un caractère spécial d'un backslash, et le shell remplace ces deux caractères par le caractère spécial seul. Évidemment, le backslash est lui-même un caractère spécial.

Exemples:

chaland ~ $ echo \?\*\$
?*$
chaland ~ $ echo \\\?\\\*\\\$
\?\*\$

Les apostrophes ou simples quotes (')

Un autre moyen est d'inclure une chaîne de caractères entre apostrophes (simples quotes) '. Tout ce qui se trouve entre deux apostrophes sera passé tel quel par le shell à la commande. Exemple:

chaland ~ $ echo '$?*'
$?*

Les guillemets doubles ou doubles quotes (")

Les guillemets se comportent comme les apostrophes, à une exception près: les dollars et les backslashes sont interprétés entre les guillemets. Exemple:

chaland ~ $ echo "$HOME/*"
/users/87/maths/doligez/*

Une technique utile: Quand on juxtapose deux chaînes de caractères quotées, le shell les concatène, et elles ne forment qu'un argument. Exemple:

chaland ~ $ echo "'"'"'
'"

Quant aux interactions plus compliquées (backslashes à l'intérieur des guillemets, guillemets à l'intérieur des apostrophes, etc.), le meilleur moyen de savoir si ça donne bien le résultat attendu est d'essayer. La commande echo est bien utile dans ce cas.

Les backquotes (`)

Dernière forme de quotation: `commande`. Le shell exécute la commande indiquée entre backquotes, lit la sortie de la commande mot par mot, et remplace ` commande ` par la liste de ces mots. Exemple :

chaland ~ $ echo `ls`
Mail News bin foo lib misc mur notes.aux notes.dvi notes.log
notes.tex planar text
chaland ~ $ ls -l `which emacs`
-rwxr-xr-t  1 root   taff   978944 Jul 16  1991 /usr/local/bin/emacs

La commande which cmd employée ci-dessus affiche sur sa sortie le nom absolu du fichier exécuté par le shell quand on lance la commande it cmd :

chaland ~ $ which emacs
/usr/local/bin/emacs

Redirections

Chaque commande a une entrée standard, une sortie standard, et une sortie d'erreur. Par défaut, l'entrée standard est le clavier, la sortie standard est l'écran, et la sortie d'erreur est aussi l'écran.

Rediriger la sortie dans un fichier : >

On peut rediriger la sortie standard d'une commande vers un fichier (caractère >). Le résultat de la commande sera placé dans le fichier au lieu de s'afficher sur l'écran. Exemple:

chaland ~ $ ls -l > foo

Le résultat de ls -l ne s'affiche pas à l'écran, mais il est placé dans le fichier foo. On peut alors taper

chaland ~ $ less foo

(ou more foo) pour lire le fichier page par page.

Rediriger l'entrée : <

On peut aussi rediriger l'entrée standard d'une commande (caractère <). La commande lira alors le fichier au lieu du clavier. Exemple:

chaland ~ $ elm leroy < foo

envoie par mail à Xavier Leroy le résultat de la commande ls -l de tout à l'heure.

On peut aussi taper more < foo qui est équivalent à more foo car more sans argument lit son entrée standard et l'affiche page par page sur le terminal.

Connecter la sortie d'une commande sur l'entrée d'une autre : |

On peut se passer du fichier intermédiaire grâce à un pipe (caractère |). Un pipe connecte directement la sortie standard d'une commande sur l'entrée standard d'une autre commande. Exemple: pour afficher page par page la liste des fichiers du répertoire courant, faire

chaland ~ $ ls -l | less

Le pipe est d'un usage très courant et rend beaucoup de services.

Récapitulatif

La panoplie complète des redirections est la suivante (le tableau indique les redirections qui diffèrent selon les shells) :

Fonction Bourne shell Z-shell
Redirige la sortie d'erreur (2) et la sortie standard (1) sur l'entrée de la commande suivante 2>&1 | |&
Redirige la sortie d'erreur et la sortie standard dans un fichier 2>&1 > >&
Redirige la sortie d'erreur et la sortie standard à la fin d'un fichier existant 2>&1 >> >>&

Remarques

Normalement, une redirection avec > sur un fichier qui existe déjà efface le contenu du fichier avant d'y placer le résultat de la commande. Les shells ont des options pour demander confirmation, ou refuser d'effacer le fichier.

Une ligne de commandes contenant des | s'appelle un pipe-line. Quelques commandes souvent utilisées dans les pipe-lines sont:

Exemples

cat glop buz > toto

Concatène les fichiers glop et buz et place le résultat dans toto.

wc -w /usr/dict/words

Affiche le nombre de mots du dictionnaire Unix.

grep gag /usr/dict/words | tail

Affiche les 10 derniers mots du dictionnaire qui contiennent la chaîne gag.

Variables

Le shell a des variables. Pour désigner le contenu d'une variable, on écrit le nom de la variable précédé d'un dollar. Exemple: echo $HOME affiche le nom du répertoire personnel de l'utilisateur.

La façon de donner une valeur à une variable varie selon le type de shell utilisé :

C-Shell (csh, tcsh, lcsh) : on utilise la commande setenv :

 
chaland ~ $ setenv foo bar
chaland ~ $ echo $foo
bar

Famille des Bourne Shell (sh, bash, zsh, ksh) : on utilise export :

chaland ~ $ foo=bar
chaland ~ $ export foo
chaland ~ $ echo $foo
bar

Les valeurs des variables sont accessibles aux commandes lancées par le shell. L'ensemble de ces valeurs constitue l'environnement. On peut aussi supprimer une variable de l'environnement avec unsetenv (C-Shell) ou unset (Bourne Shell).

Quelques variables d'environnement:

Exercice : Assurez-vous que /usr/local/games/bin se trouve bien dans votre PATH.

Programmation du shell sh

Un shell, quel qu'il soit, peut exécuter des commandes prises dans un fichier. Un fichier contenant des commandes pour le shell est appelé un script. C'est en fait un programme écrit dans le langage du shell. Ce langage comprend non seulement les commandes que nous avons déjà vues, mais aussi des structures de contrôle (constructions conditionnelles et boucles).

Pour la programmation du shell, nous allons utiliser le shell sh, qui est le plus répandu et standard. Ce que nous avons vu jusqu'ici s'applique aussi bien à sh qu'à zsh et aussi à csh, à l'exception de setenv et de certaines redirections signalées. Pour être un script, un fichier doit commencer par la ligne:

#!/bin/sh

Il doit aussi avoir être exécutable (bit x). Le #!/bin/sh sur la première ligne indique que ce script doit être exécuté par le shell sh dont on indique le chemin d'accès.

Structures de contrôle

for

for var in liste de chaînes ;
do commandes ; done

Affecte successivement à la variable de nom var chaque chaîne de caractères trouvée dans la liste de chaînes, et exécute les commandes une fois pour chaque chaîne.

Rappel : $var accède à la valeur courante de var. La partie commandes est une suite de commandes, séparées par des ; ou des retours à la ligne. (Tous les ; dans cette syntaxe peuvent aussi être remplacés par des retours à la ligne.)

Exemple :

for i in *; do echo $i; done

affiche les noms de tous les fichiers du répertoire courant, un par ligne.

if

if commande ; then commandes ;
else commandes ; fi

Exécute l'une ou l'autre des listes de commandes, suivant que la première commande a réussi ou non (voir ci-dessous).

while

while commande ; do commande ; done

Exécute les commandes de manière répétée tant que la première commande réussit.

case

case chaîne in
  pattern) commande ;;
  pattern) commande ;;
esac

Exécute la première commande telle que la chaîne est de la forme pattern. Un pattern est un mot contenant éventuellement les constructions *, ?, [a-d], avec la même signification que pour les raccourcis dans les noms de fichiers. Exemple :

case $var in
  [0-9]*) echo 'Nombre';;
  [a-zA-Z]*) echo 'Mot';;
  *) echo 'Autre chose';;
esac

Code de retour

On remarque que la condition des commandes if et while est une commande. Chaque commande renvoie un code de retour (qui est ignoré en utilisation normale). Si le code est 0, la commande a réussi; sinon, la commande a échoué. Par exemple, le compilateur cc renvoie un code d'erreur non nul si le fichier compilé contient des erreurs, ou s'il n'existe pas.

Les commandes if et while considèrent donc le code de retour 0 comme « vrai », et tout autre code comme « faux ».

Il existe une commande test, qui évalue des expressions booléennes passées en argument, et renvoie un code de retour en fonction du résultat. Elle est bien utile pour les scripts. Exemple :

if test $var = foo
then echo 'La variable vaut foo'
else echo 'La variable ne vaut pas foo'
fi

Variables

Dans les scripts, on peut utiliser des variables définies à l'extérieur (avec setenv ou export), mais aussi définir ses variables locales propres au script. On donne une valeur à une variable avec une commande de la forme nom-de-variable=valeur. Les variables sont utilisées pour stocker des informations.

On a aussi des variables spéciales, initialisées automatiquement au début du script:

$0 Le nom de la commande (i.e. : du script)
$1, $2, etc. Le premier, deuxième, etc, argument passés au script.
$* La liste de tous les arguments passés au script.
$# Le nombre d'arguments passés au script.
$? Le code de retour de la dernière commande lancée.
$! Le numéro de process de la dernière commande lancée en tâche de fond.
$$ Le numéro de process du shell lui-même.

Vous êtes maintenant en mesure de faire ces exercices pour vous entraîner.

Basé sur un polycopié de Roberto Di Cosmo, Xavier Leroy et Damien Doligez. Ajustements : Nicolas George. Dernière modification le 2002-12-16.