Le shell – mécanismes de base
a – L’unité d’exécution : la commande
D’une manière générale, une commande Unix est un fichier exécutable présent dans le système de fichiers que l’on invoque son par son chemin absolu (ex. /usr/bin/ls) ou bien par un chemin relatif (par rapport au répertoire courant, ex. ./mon_beau_programme).
En parallèle de ces commandes, les shells disposent de commandes dites internes, c’est à dire ne correspondant pas à un fichier exécutable, mais directement implémentées dans le code de l’interpréteur de commande lui-même. Ces commandes modifient généralement l’environnement de l’utilisateur (ex. pwd, export, etc…), mais pour des raisons de performances, certains shells implémentent directement quelques commandes parmi les plus utilisées (echo, print, test, etc…).
En effet, un processus ne peut pas lancer directement une commande externe, mais doit d’abord se dupliquer (via un appel système fork) puis invoquer l’appel système exec qui va charger et exécuter le processus appelé. Cette méthode est lourde en terme de temps d’exécution (fork duplique l’environnement courant et retransmet les descripteurs ouverts au processus fils) ; expliquant en majeure partie la réputation de lenteur (toute relative) des scripts shell (bien qu’avec les Unix-like modernes des techniques d’optimisation ont été déployées, avec notamment les appels système vfork (BSD) et clone (Linux)).
Une ligne de commande correspond donc à l’invocation d’une commande Unix, et est constituée du nom de la commande suivi de son ou ses arguments optionnels, séparés par des caractères particuliers. Dès que le shell rencontre un caractère « séparateur de commande », celle-ci est évaluée.
Par défaut, les caractères séparateurs : l’espace et la tabulation (La variable spéciale IFS contient la liste des séparateurs);
Les séparateurs de commande sont le caractère ASCII CR (0×13 – retour chariot) etle caractère ; (point virgule).
Ainsi la ligne :
echo "Bonjour Monde" ; echo fini
évaluera deux commandes successives ‘echo’ qui afficheront les chaines de caractères ‘Bonjour Monde’ puis ‘fini’.
b – arguments et code retour
Ceux qui ont déjà programmé en C se rappelleront la syntaxe du point d’entrée d’un programme C, la fonction main :
int main(int argc, char ∗argv[], char ∗envp[]) {
...
}
- Un programme C récupère donc une liste d’arguments (optionnels), effectue des traitements, et retourne un code d’erreur représenté par un entier.
- Le shell – grâce auquel l’opérateur invoque le programme – permet donc de passer les arguments au programme appelé et récupère son code numérique d’erreur de sortie du programme.
- Un script shell, tout comme le C permet de récupérer les arguments qui lui seront fournis (toujours optionnellement) et retournera un code numérique d’erreur de sortie.
Conventionnellement (et donc par défaut), le code d’erreur (qui du coup n’en est pas un …) retourné lorsque tout s’est déroulé correctement vaut 0.
Une variable spéciale du shell est automatiquement renseignée après chaque appel de commande : $?
Pour illustration, dans les exemples suivants, la commande grep (se conformant bien évidemment aux standards Unix) retourne 0 si la chaine recherchée est trouvée, et « autre chose » en cas d’échec de la recherche :
echo "bonjour monde' | grep monde ; echo $?
Dans ce cas, grep retrouve la concordance, retourne le code de sortie 0 qui est alors mémorisé dans la variable automatique $? du shell, affichée par la commande echo.
echo "bonjour monde' | grep mondiaux ; echo $?
Ce coup-ci, pas de concordance, la variable $? prend la valeur 1.
c – Descripteurs de fichiers
Par principe, sous Unix (pour lequel tout est fichier !!!), lors de la création d’un processus (via l’appel système fork), ce dernier hérite de l’environnement de son processus père, et notamment des descripteurs de fichiers ouverts par celui-ci.
De ce fait, lors de l’ouverture d’un nouveau shell, trois descripteurs de fichiers lui sont automatiquement affectés :
- descripteur 0 : l’entrée standard
- descripteur 1 : la sortie standard
- descripteur 2 : la sortie d’erreur
Ainsi, dès l’ouverture d’un terminal X, exécuter la commande :
ls -l /proc/$$/fd/
retourne :
total 0 lrwx------ 1 francois francois 64 nov. 11 08:34 0 -> /dev/pts/0 lrwx------ 1 francois francois 64 nov. 11 08:34 1 -> /dev/pts/0 lrwx------ 1 francois francois 64 nov. 11 08:34 2 -> /dev/pts/0 lrwx------ 1 francois francois 64 nov. 11 08:34 255 -> /dev/pts/0
La différentiation des sorties standard et sorties d’erreur est prévue afin de permettre l’élimination de messages non désirés lors de l’exécution de scripts non-interactifs.
d – Redirections
Par défaut, le résultat d’une commande (c’est à dire le flux de données issu du traitement) est donc envoyé vers sa sortie standard (qui est héritée du processus appelant …), et puisqu’en vertu du principe Unix de base selon lequel tout est fichier, les redirections vont donc permettre d’aiguiller différemment les différents flux :
- le symbole < : indique la redirection d’un fichier vers l’entrée standard du programme appelé
- les symboles > et >> : indiquent la redirection d’un fichier (et par conséquent d’un résultat d’exécution) vers un autre fichier
- Le symbole | : indique le chainage de commandes c’est à dire la redirection de la sortie standard d’une commande vers l’entrée standard de la commande suivante.
Exemples :
echo "ligne 1" > 1.txt echo"ligne 2" >> 1.txt echo"ligne 3" >> 1.txt echo"ligne 4" >> 1.txt echo"ligne 5" >> 1.txt
Crée un fichier 1.txt contenant 5 lignes.
cat 1.txt > 2.txt
liste le fichier 1.txt et renvoie son contenu vers le fichier 2.txt
tac 1.txt > 2.txt
liste le fichier 1.txt et renvoie son contenu inversé vers le fichier 2.txt après avoir écrasé son contenu
cat 1.txt | tail -3 | head -1
affiche la 3° ligne du fichier 1.txt
sed -e 's/bonjour/au revoir/' <<FIN
Remplace tous les bonjour par au revoir dans les lignes saisies jusqu’à la ligne ne comportant que l’étiquette FIN.