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[])  {
...
}

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 :

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 :

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.

suivant …