Installation
Téléchargement des sources
Les sources ainsi qu'une documentation complète sont disponibles sur le Web (cf encadré).
Compilation
Décompressez l'archive :
$tar -xvzf esh-0.8.tar.gz
Déplacez-vous dans le répertoire contenant les sources :
$cd esh
Editez le fichier Makefile, supprimez les # en tête des lignes :
#INC=-Ifake-readline/
#LIB=fake-readline/readline.c
Et commentez (#) les lignes :
INC=-I/usr/include/readline
LIB=-lreadline
Vous pouvez désormais compiler Esh :
$make
Et copier l'exécutable dans /bin, propriétaire root, droits d'exécution rwxr-xr-x :
$su root
$cp esh /bin
$chmod 755 /bin/esh
Shell par défaut
Si vous souhaitez ouvrir vos sessions avec Esh, modifiez la ligne de /etc/passwd associée à votre nom d'utilisateur. Par exemple :
user:fqf1je4Dd2I7g:500:500::/home/user:/bin/esh
Listes
Les listes (l) sont formées d'atomes et d'autres listes (cf série consacrée à Scheme).
Atomes
On distingue constantes et identificateurs.
Types
Nous retrouvons les types classiques. Entier (i),
booléen (b), chaîne de caractères (s), descripteur de fichier (f), quelconque (a).
Exemple : -1, 0, 1
false, true
easy shell
standard, stderr
Primitives
Commandes système
En mode interactif, Esh affiche $ puis attend l'entrée d'une commande externe :
Exemple : $ls
GNUstep mbox ...
ou d'une commande interne tapée entre parenthèses :
(cd [<s>]) Change le répertoire actif
(exit [<i>]) Quitte Esh et retourne le code i
Exemple : $(cd /)
$(exit 1)
Variables d'environnement
Esh hérite de l'environnement de son processus père :
(get <s>) Retourne la valeur de s
Exemple : $(get HOME)
=>/root
et en autorise la modification :
(set <s> <s'>) Change la valeur de s
Exemple : $(set HOME /root)
Calcul
Il se limite aux entiers :
(+ <i> ...) Retourne i + ...
(- <i> ...) Retourne i - ...
(* <i> ...) Retourne i * ...
(/ <i> ...) Retourne i / (* ...)
Exemple : $(+ -1 0 1)
=>0
$(/ 10 5 2)
=>1
Chaînes
Les chaînes peuvent être scindées ou regroupées :
(chars <s>) Retourne les caractères de s
(squish <s> ...) Retourne la concaténation de s ...
Exemple : $(chars esh)
=>e s h
$(squish e s h)
=>esh
Listes
Les listes permettent la représentation de données complexes :
(car <l>) Retourne le premier élément de l
(cdr <l>) Retourne les éléments suivants de l
(list ...) Retourne une liste constituée des éléments ...
(unlist <l>) Retourne les éléments de l
Ex. $(car ~(a b c))
=>a
$(cdr ~(a b c))
=>b c
$(list a b c)
=>(a b c)
$(unlist ~(a b c))
=>a b c
Entrées sorties simples
Elles concernent l'accès à la console :
(print ...) Affiche ...
(nl) Saute une ligne
(read <s>) Lit et retourne une chaîne
Ex. $(print Hello World!)
Hello World!
$(read >)
Fichiers
Nous disposons des descripteurs prédéfinis :
(standard) canaux standards
(stderr) canaux standards d'erreur
Nous pouvons accéder aux fichiers :
(file-open <s> <s'>) Ouvre un fichier s' de type s, retourne
le descripteur associé ou () si erreur
File Mode normal
Append Mode ajout
...
(file-read <f>) Retourne le contenu de f
(file-write <f> <s>) Ecrit s dans f
et en contrôler le type :
(file-type <s>) Retourne une chaîne décrivant le fichier nommé
s s'il existe, false sinon.
Regular Fichier régulier
Directory Répertoire
Link Lien symbolique
...
Ex. $(file-type /etc/issue)
=>regular
$(file-read (file-open file /etc/issue))
=>Red Hat Linux release 5.2 (Apollo)
Programmes
Un programme est une liste lue par l'interprète et soumise à évaluation. Au lancement, Esh lit les fichiers de configuration /etc/eshrc et ~/.eshrc s'ils existent. Vous pouvez y définir de nouvelles commandes:
(define <s> ...) Définit une commande s
Ex. (define hello
~(print Hello World!))
$(hello)
Hello World!
Ici, l'interprète détermine l'association hello <-> (print Hello World) puis lance l'évaluation du terme droit. Aucun marquage ne distingue une fonction d'une liste. D'où la nécessité de quoter les arguments pour qu'ils ne soient pas évalués.
Quotes
Une liste marquée ~ n'est jamais évaluée. Dans l'exemple précédent, nous souhaitions associer à hello la liste (print Hello World!) et non Hello World!, résultat de son évaluation.
Arguments
Esh les stocke dans une pile de données :
(push <a>) Empile a
(pop) Dépile et retourne le sommet de la pile
(top) Retourne une copie du sommet de la pile
(rot) Permute sommet et élément suivant de la pile
Retourne une copie du nouveau sommet
(stack) Retourne une copie de la pile
Exemple : (define arg
~(stack))
$(arg a b c)
=> a b c
Structures de contrôle
Séquence
Un programme simple exécute une suite d'instructions :
(begin ...) Exécution de ...
Sélection
Une sélection opère un aiguillage :
(if <a> <a'> <a''>) Evaluer a' si a = true, a'' sinon
en conjonction avec les prédicats suivants :
(> <i> <i'>) Retourne true si i > i'
(< <i> <i'>) Retourne true si i < i'
(= <s> <s'>) Retourne true si s = s'
(null? <a>) Retourne true si a = ()
(not <b>) Retourne true si b = false
(or ...) Retourne false si tous les arguments valent false
(and ...) Retourne true si tous les arguments valent true
Note : Par défaut, est considérée true toute valeur différente de false.
Récursion
La récursion est une manière élégante de décrire un problème en fonction d'un problème de même nature mais de moindre complexité. Prenons l'exemple de la fonction factorielle :
fac(0) = 1
fac(n) = n * fac(n-1), n > 0
soit, après traduction :
(define fac
~(if ~(> (top) 0)
~(* (top) (fac (- (top) 1)))
1))
$(fac 5)
=> 120
(top) représentant l'ordre de la factorielle calculée (fig. 1). L'écriture peut surprendre... mais on finit par s'y habituer ;-)
fig.1 - Récursion
Itération
Formellement, l'itération peut être envisagée comme variante de la récursion. Considérons une fonction traitant les éléments d'une liste :
(define apply
~(if ~(not (null? (rot)))
~(begin
((rot)(rot))
(apply (cdr (list (stack)))))
()))
$(apply chars easy shell)
=>e a s y s h e l l
Notez la permutation des deux arguments au sommet de la pile (rot). L'espace de travail occupé étant proportionnel au nombre d'appels imbriqués, on emploie parfois la structure :
(while <l> <l'> ...) Evalue l' tant que l = true
... valeur initiale de la pile
Notions avancées
Tubes
Il est possible d'enchaîner les commandes :
(run <b> <f> <f'>...) Execute ... et retourne le code résultat
b = true pour un processus détaché
f et f' canaux d'entrée et de sortie
Ex. $(run (false) (standard) (standard) ~(grep kernel
/var/log/messages) ~(sort))
=>Jul 12 07:15:01 localhost kernel: CSLIP ...
et de rediriger les entrées/sorties :
Ex. $(run (false) (standard) (file-open file /tmp/kernel-log)
~(grep kernel /var/log/messages) ~(sort))
Notons l'existence d'une syntaxe abrégée (run-simple ...) correspondant à (run (false) (standard) (standard) ...).
Exemple récapitulatif
Programmons une fonction loop exécutant une commande
... toutes les i secondes.
(loop <i> ...)
Nous créons une fonction pause invoquant la commande externe sleep :
(define pause
~(run-simple (list sleep (top))))
Et l'intégrons :
(define loop
~(begin
(print Loop:)
(while ~(true)
~(begin
(print (nl))
(run-simple (list(cdr(list(stack)))))
(pause (top)))
(stack))))
Ex. (loop 5 free)
=> Loop :
total used ...
total used ...
Conclusion
Esh offre une alternative séduisante aux adeptes de shells classiques. Sa faible utilisation des ressources système (à peine 400 ko en exécution) le destine particulièrement aux petites configurations.
Cyril Nocton <cyril.nocton@bigfoot.com>
Relecture Danièle Momont <danimom@club-internet.fr>