Le shell est un interpréteur de commandes. Il permet de lancer les commandes UNIX disponibles sur votre station, en leur affectant un certain nombre de paramètres d’exécution, de contrôler les données d’entrée et de sortie, les messages d’erreur éventuels, et surtout de lesenchaîner de manière efficace et pratique.
Un shell est démarré pour chaque console ouverte (ou fenêtre X11 équivalente créée) ; il est intéractif dans ce cas, car les lignes de commande tapées au clavier sont exécutées une par une avec confirmation systématique (touche <return>).
Un shell est également créé chaque fois que la commande à exécuter se trouve être un fichier ascii : le shell lit successivement toutes les lignes du fichier, supposant que ce sont des commandes ;
il disparaît (“rend la main”) une fois la fin de fichier atteinte.
Si le shell est intéractif, on peut représenter son fonctionnement sous la forme de l’algorithme simplifié suivant :
TANT QUE < durée de la connexion>
lire_ligne_au_clavier(ligne)
traiter_ligne(ligne)
afficher_resultats()
afficher_prompt()
FIN TANT QUE
Si le shell n’est pas intéractif, alors on peut considérer qu’il est lancé directement à travers la fonction traiter_ligne() dont le paramètre est le nom du fichier à traiter ;
Voici l’algorithme de la fonction traiter_ligne() :
traiter_ligne(ligne)
POUR <premier champ de la ligne> DANS
* <nom de fichier ascii> :
TANT QUE <fichier ascii> NON VIDE
traiter_ligne(<ligne courante du fichier ascii>)
FIN TANT QUE
* <nom de binaire executable> :
charger_et_lancer(<fichier binaire>)
* <commande shell> :
executer_commande()
* AUTRE :
afficher_message(“command not found”)
FIN POUR
Le shell est lui même un fichier binaire, qu’il est possible de lancer comme n’importe quelle autre commande UNIX. Ainsi, lorsqu’on parle du “shell”, cela recouvre en fait un ensemble de troisinterpréteurs différents, possédant chacun un exécutable spécifique :
iii.Le Ksh (commande “ksh”), est le dernier né et le plus complet ; il accepte toutes les commandes du Bourne Shell, possède un historique de commandes perfectionné, et la plupart des fonctionnalitésdu Csh.
C’est ce dernier qui servira de base pour les exemples qui suivent, bien que beaucoup deremarques soient valables quelque soit le shell utilisé.
Un processus UNIX est un fichier binaire en train de s’exécuter avec un certain
environnement :
Pour démarrer un nouveau processus, c’est à dire chaque fois que l’on veut lancer une nouvelle commande (grep, sort, compile, ..), une technique standard est appliquée ;
Cette méthode peut paraître complexe, mais c’est exactement ce qui se passe chaque fois que le shell lance une commande (par la fonction charger_et_lancer() de notre pseudo-interpréteur vu plus haut).
On y découvre à l’occasion la raison pour laquelle le vocabulaire lié aux processus est souvent tiré de tout ce qui touche la descendance ; c’est une constante : tout processus a été lancé par un processus père (sauf le premier !) et il hérite de la plupart des ses caractéristiques.
Si votre shell possède un paramétrage dont vous ne savez attribuer la provenance en étudiant vos fichiers de configuration, il y a de grandes chances que ce soit un processus “ancêtre” qui l’ai positionné.
Mais la création d’un nouveau processus est une action coûteuse en ressources système, et nous verrons plus loin quelles sont les règles à appliquer pour éviter d’y faire trop souvent appel lors de l’exécution de programmes.
Par contre, lorsque la commande grep commence à “travailler”, sa première action est de faire le tri et de tester les paramètres qui lui été fournis. C’est à ce moment que le type des arguments a une signification : certains doivent commencer par un tiret (ce sont des “options” de la commande) et d’autres non (ce sont des “paramètres” de la commande).
En résumé, le shell sépare et fournit à la commande les différents champs constituant la ligne ;la commande récupère les champs et vérifie qu’ils correspondent bien à sa syntaxe.
exemple 2 : sed ’s/^#//’ /tmp/toto
Cette commande renvoie le fichier /tmp/toto sans caractère “#” en début de ligne.
Elle se décompose en :
<champ0> = “sed” : la commande
<champ1> = “s/^#//” : le premier argument
<champ2> = “/tmp/toto” : le second
On voit ainsi que la commande sed utilise le premier argument comme la commande à exécuter sur le fichier dont le nom est indiqué par le second argument.
élémentaire
Pour faire dialoguer une commande (un processus) avec l’extérieur, et le shell en particulier, ondispose de différentes solutions ;
en ENTREE : Les paramètres de position1:
de 0 à N, comme on a vu au paragraphe précédent. (le nom de la commande est passé aussi en tant qu’argument 0).
Si la ligne de commande est :
cmd arg1 arg2
l’appel de cmd pourrait être assimilé à l’appel d’une fonction
cmd() avec les 3 arguments “cmd”, “arg1”, “arg2” passés par
valeur ;
Les variables exportées :
Toutes les variables faisant partie de l’environnement exporté sont visibles et utilisables par la commande ; par contre, la modification du contenu d’une de ces variables exportées n’aura qu’une portée locale (pas de possibilité de retourner une valeur de cette manière).
On place une variable dans l’environnement exporté par la commande shell :
export NOM_DE_LA_VARIABLE
en SORTIE : le code de retour :
La variable shell $? contient le code de retour de la dernière commande lancée par le shell. Si la commande s’est terminée normalement, le code de retour vaut généralement 0 ; (le manuel de référence indique le code de retour de chaque commande).
Si la commande est elle-même un programme shell qui s’est terminé sur une instruction exit <valeur>, le code de retour vaut <valeur>, sinon il vaut le code de retour de la dernière commande exécutée dans le programme.
en ENTREE / SORTIE : les fichiers.
Par défaut, il y en a trois d’ouverts par le shell avant le
lancement de chaque commande :
- 1 en entrée : l’entrée standard
- 2 en sortie : la sortie standard et la sortie d’erreur.
Il est rappelée que le shell qui lance une commande positionne le nécessaire pour que ces
entrées-sorties soient disponibles : mais, une fois lancée, il est du ressort de la commande de les
utiliser et de retourner des valeurs couramment attendues1.
En général, une commande UNIX lit les données sur l’entrée standard, les modifie en fonction des arguments spécifiés, (options ou paramétrage) et renvoie le résultat sur la sortie standard. Si une erreur intervient, elle affiche un message sur la sortie d’erreur, et renvoie un code de retour différentde zéro.
Une bonne habitude consiste à respecter cette convention, ce qui permet ainsi d’écrire et d’intégrer facilement de nouvelles commandes au milieu de celles déjà existantes
I.5. La manipulation des fichiers
I.5.1. Description
La manipulation d’un fichier passe toujours par les étapes suivantes :
1/ ouvrir le fichier
2/ écrire, lire dans le fichier
3/ fermer le fichier
Sous UNIX, on ne spécifie dans les commandes le nom du fichier physique utilisé que lors del’étape 1 ; le système renvoie alors un numéro (un entier de 0 à 20) qui sert ensuite de référence logique pour toutes les autres actions liées à ce fichier : c’est le descripteur.
Lorsqu’une commande standard d’UNIX travaille, elle lit les données sur le descripteur 0, écrit le résultat sur le 1, et éventuellement les erreurs sur le 2, sans rien connaître en fait du nom des
fichiers physiques qui lui sont associés.
Le shell, lui, ouvre en fonction de certaines directives (> < >> |, etc) les fichiers spécifiés en lecture ou en écriture, leur affecte à chacun un descripteur, lance la commande, attend la fin, puis ferme les fichiers associés aux descripteurs.
Par exemple, la commande echo écrit par convention sur le descripteur 1 ; mais ce résultat peut être dirigé vers n’importe quel fichier pourvu que le shell ait auparavant associé ce fichier au descripteur 1.
Une caractéristique importante des descripteurs est qu’ils font partie de l’héritage lors de la création d’un nouveau processus : un processus qui “naît” a des descripteurs qui pointent sur les mêmes fichiers que ceux de son père ; le shell de login (le premier lancé lors de votre connexion) affecte le descripteur 0 au clavier, et les descripteurs 1 et 2 à la console ; tant que vous ne modifierez pas la sortie standard (descripteur 1), tous les processus fils créés auront leur sortie standard associée à la console et leurs résultats s’y afficheront.
commande : grep truc <toto 2>trace
Le shell qui évalue la ligne interprète :
<toto par : ouvrir le fichier toto en lecture et l’affecter au descripteur No 0 (entrée standard) du nouveau processus grep
2>trace par : ouvrir le fichier trace en écriture et lui affecter le descripteur 2.
Comme aucune directive ne touche le descripteur 1, on ne le modifie pas. L’ancêtre du shell actuel, le shell de login par exemple, l’avait déjà affecté à la console : il y est encore par héritage.
Le nouveau processus grep lit les lignes présentes sur son entrée standard (descripteur 0), et recopie celles contenant la chaîne “truc” sur la sortie standard (descripteur 1).
Résultat : les lignes du fichier toto qui contiennent la chaîne “truc” s’affichent àl’écran, et le fichier trace est vide.
Le shell est un interpréteur de commandes. Il permet de lancer les commandes UNIX disponibles sur votre station, en leur affectant un certain nombre de paramètres d’exécution, de contrôler les données d’entrée et de sortie, les messages d’erreur éventuels, et surtout de lesenchaîner de manière efficace et pratique.
Un shell est démarré pour chaque console ouverte (ou fenêtre X11 équivalente créée) ; il est intéractif dans ce cas, car les lignes de commande tapées au clavier sont exécutées une par une avec confirmation systématique (touche <return>).
Un shell est également créé chaque fois que la commande à exécuter se trouve être un fichier ascii : le shell lit successivement toutes les lignes du fichier, supposant que ce sont des commandes ;
il disparaît (“rend la main”) une fois la fin de fichier atteinte.
Si le shell est intéractif, on peut représenter son fonctionnement sous la forme de l’algorithme simplifié suivant :
TANT QUE < durée de la connexion>
lire_ligne_au_clavier(ligne)
traiter_ligne(ligne)
afficher_resultats()
afficher_prompt()
FIN TANT QUE
Si le shell n’est pas intéractif, alors on peut considérer qu’il est lancé directement à travers la fonction traiter_ligne() dont le paramètre est le nom du fichier à traiter ;
Voici l’algorithme de la fonction traiter_ligne() :
traiter_ligne(ligne)
POUR <premier champ de la ligne> DANS
* <nom de fichier ascii> :
TANT QUE <fichier ascii> NON VIDE
traiter_ligne(<ligne courante du fichier ascii>)
FIN TANT QUE
* <nom de binaire executable> :
charger_et_lancer(<fichier binaire>)
* <commande shell> :
executer_commande()
* AUTRE :
afficher_message(“command not found”)
FIN POUR
Le shell est lui même un fichier binaire, qu’il est possible de lancer comme n’importe quelle autre commande UNIX. Ainsi, lorsqu’on parle du “shell”, cela recouvre en fait un ensemble de troisinterpréteurs différents, possédant chacun un exécutable spécifique :
iii.Le Ksh (commande “ksh”), est le dernier né et le plus complet ; il accepte toutes les commandes du Bourne Shell, possède un historique de commandes perfectionné, et la plupart des fonctionnalitésdu Csh.
C’est ce dernier qui servira de base pour les exemples qui suivent, bien que beaucoup deremarques soient valables quelque soit le shell utilisé.
Un processus UNIX est un fichier binaire en train de s’exécuter avec un certain
environnement :
Pour démarrer un nouveau processus, c’est à dire chaque fois que l’on veut lancer une nouvelle commande (grep, sort, compile, ..), une technique standard est appliquée ;
Cette méthode peut paraître complexe, mais c’est exactement ce qui se passe chaque fois que le shell lance une commande (par la fonction charger_et_lancer() de notre pseudo-interpréteur vu plus haut).
On y découvre à l’occasion la raison pour laquelle le vocabulaire lié aux processus est souvent tiré de tout ce qui touche la descendance ; c’est une constante : tout processus a été lancé par un processus père (sauf le premier !) et il hérite de la plupart des ses caractéristiques.
Si votre shell possède un paramétrage dont vous ne savez attribuer la provenance en étudiant vos fichiers de configuration, il y a de grandes chances que ce soit un processus “ancêtre” qui l’ai positionné.
Mais la création d’un nouveau processus est une action coûteuse en ressources système, et nous verrons plus loin quelles sont les règles à appliquer pour éviter d’y faire trop souvent appel lors de l’exécution de programmes.
Par contre, lorsque la commande grep commence à “travailler”, sa première action est de faire le tri et de tester les paramètres qui lui été fournis. C’est à ce moment que le type des arguments a une signification : certains doivent commencer par un tiret (ce sont des “options” de la commande) et d’autres non (ce sont des “paramètres” de la commande).
En résumé, le shell sépare et fournit à la commande les différents champs constituant la ligne ;la commande récupère les champs et vérifie qu’ils correspondent bien à sa syntaxe.
exemple 2 : sed ’s/^#//’ /tmp/toto
Cette commande renvoie le fichier /tmp/toto sans caractère “#” en début de ligne.
Elle se décompose en :
<champ0> = “sed” : la commande
<champ1> = “s/^#//” : le premier argument
<champ2> = “/tmp/toto” : le second
On voit ainsi que la commande sed utilise le premier argument comme la commande à exécuter sur le fichier dont le nom est indiqué par le second argument.
élémentaire
Pour faire dialoguer une commande (un processus) avec l’extérieur, et le shell en particulier, ondispose de différentes solutions ;
de 0 à N, comme on a vu au paragraphe précédent. (le nom de la commande est passé aussi en tant qu’argument 0).
Si la ligne de commande est :
cmd arg1 arg2
l’appel de cmd pourrait être assimilé à l’appel d’une fonction
cmd() avec les 3 arguments “cmd”, “arg1”, “arg2” passés par
valeur ;
Les variables exportées :
Toutes les variables faisant partie de l’environnement exporté sont visibles et utilisables par la commande ; par contre, la modification du contenu d’une de ces variables exportées n’aura qu’une portée locale (pas de possibilité de retourner une valeur de cette manière).
On place une variable dans l’environnement exporté par la commande shell :
export NOM_DE_LA_VARIABLE
en SORTIE : le code de retour :
La variable shell $? contient le code de retour de la dernière commande lancée par le shell. Si la commande s’est terminée normalement, le code de retour vaut généralement 0 ; (le manuel de référence indique le code de retour de chaque commande).
Si la commande est elle-même un programme shell qui s’est terminé sur une instruction exit <valeur>, le code de retour vaut <valeur>, sinon il vaut le code de retour de la dernière commande exécutée dans le programme.
en ENTREE / SORTIE : les fichiers.
Par défaut, il y en a trois d’ouverts par le shell avant le
lancement de chaque commande :
- 1 en entrée : l’entrée standard
- 2 en sortie : la sortie standard et la sortie d’erreur.
Il est rappelée que le shell qui lance une commande positionne le nécessaire pour que ces
entrées-sorties soient disponibles : mais, une fois lancée, il est du ressort de la commande de les
utiliser et de retourner des valeurs couramment attendues1.
Une bonne habitude consiste à respecter cette convention, ce qui permet ainsi d’écrire et d’intégrer facilement de nouvelles commandes au milieu de celles déjà existantes
I.5. La manipulation des fichiers
I.5.1. Description
La manipulation d’un fichier passe toujours par les étapes suivantes :
1/ ouvrir le fichier
2/ écrire, lire dans le fichier
3/ fermer le fichier
Sous UNIX, on ne spécifie dans les commandes le nom du fichier physique utilisé que lors del’étape 1 ; le système renvoie alors un numéro (un entier de 0 à 20) qui sert ensuite de référence logique pour toutes les autres actions liées à ce fichier : c’est le descripteur.
Lorsqu’une commande standard d’UNIX travaille, elle lit les données sur le descripteur 0, écrit le résultat sur le 1, et éventuellement les erreurs sur le 2, sans rien connaître en fait du nom des
fichiers physiques qui lui sont associés.
Le shell, lui, ouvre en fonction de certaines directives (> < >> |, etc) les fichiers spécifiés en lecture ou en écriture, leur affecte à chacun un descripteur, lance la commande, attend la fin, puis ferme les fichiers associés aux descripteurs.
Par exemple, la commande echo écrit par convention sur le descripteur 1 ; mais ce résultat peut être dirigé vers n’importe quel fichier pourvu que le shell ait auparavant associé ce fichier au descripteur 1.
commande : grep truc <toto 2>trace
Le shell qui évalue la ligne interprète :
<toto par : ouvrir le fichier toto en lecture et l’affecter au descripteur No 0 (entrée standard) du nouveau processus grep
2>trace par : ouvrir le fichier trace en écriture et lui affecter le descripteur 2.
Comme aucune directive ne touche le descripteur 1, on ne le modifie pas. L’ancêtre du shell actuel, le shell de login par exemple, l’avait déjà affecté à la console : il y est encore par héritage.
Le nouveau processus grep lit les lignes présentes sur son entrée standard (descripteur 0), et recopie celles contenant la chaîne “truc” sur la sortie standard (descripteur 1).
Résultat : les lignes du fichier toto qui contiennent la chaîne “truc” s’affichent àl’écran, et le fichier trace est vide.