Écrivez un shell qui doit :
- Afficher un prompt en l’attente d’une nouvelle commande
- Chercher et lancer le bon executable (basé sur une variable d’environnement
PATH
ou en utilisant un path absolu), comme dansbash
- Vous devez implémenter les builtins suivants :
echo
et l’option-n
cd
uniquement avec un chemin absolu ou relatifpwd
sans aucune optionexport
sans aucune optionunset
sans aucune optionenv
sans aucune option ni argumentexit
sans aucune option
;
dans la ligne de commande doit séparer les commandes'
et"
doivent marcher comme dans bash, à l’exception du multiligne- Les redirections
<
,>
et>>
doivent marcher comme dans bash, à l’exception des aggrégations de fd |
doit marcher comme dans bash- Les variables d’environnement (
$variable
) doivent marcher comme dans bash $?
doit marcher comme dans bashctrl-C
,ctrl-D
etctrl-\
doivent afficher le même résultat que dans bash
fork
: créé un nouveau processus enfantwait
: attend un signal d'arrêt d'un processus, wait4 pour attendre la fin d'un processus précisexit
: termine le processusgetcdw
: pour obtenir le pwdstat
: donne toutes les infos d'un fichier comme un lslstat
: same sauf que dans le cas d'un lien donne les infos sur le lien et pas le fichier pointéftat
: donne toutes les infos sur un fichier comme un ls, mais cette fois on lui passe en argument le file descriptorkill
: envoie un signal d'arrêt au processus indiquésignal(sig, fun)
: exécutefun
sisig
est reçuexecve
: exécuter un exécutable, avec ses arguments et l'environnement en paramètresdup
: duplique le descripteur d'un objet, i.e. attribue un nouveau descripteur qui pointe vers le même objet, ainsi lorsque l'on read sur l'un on avance sur les deuxdup2
: permet en plus de spécifier la valeur du descripteurchdir
: change le répertoire courantopendir
: ouvre le fichier descriptif du dossierreaddir
: lit l'entrée suivante dans le fichier descriptif du dossierclosedir
: referme le ficher descriptif du dossierpipe
: rempli un tableau avec deux nouveaux file descriptors qui vont être utiles pour changer l'entrée standard et la sortie standard
- il y a des commandes suffisamment simples pour être exécutées directement par minishell ce sont les commandes builtin
- a contrario les commandes externes doivent etre exécutées dans un nouveau processus et nécessitent donc un
fork
- faire un
fork
pour chaque commande qui écrit sur la sortie standard, on close la sortie standard et on duplique l'output, donc lorsque qu'on écrira sur le fd 1 ça redirigera sur l'output désiré (same pour l'input)
lsof | grep "minishell"
: pour voir tous les fd ouverts parminishell
, voir que tout est bien close à la fin de chaque commande
Value | Signification |
---|---|
1 | Catchall for general errors impermissible operations |
2 | Misuse of shell builtins |
126 | Command invoked cannot execute (permission problem or not an executable) |
127 | Command not found |
128 | Invalid argument to exit |
128+n | Error signal n |
130 | Script terminated by Control-C |
255 | Exit status out of range |
- Analyse lexicale : transforme les instructions en token
- Analyse syntaxique : le parseur demande les tokens correspondant au fur et a mesure et construit un AST (cf partie suivante)
- Expansion : remplace les alias, les variables d'environnement, ...
- Execution : lecture de l'arbre suivant un parcours postfixe
On va créer une liste chaînée à partir de l'environnement contenant à chaque maillon le nom de la variable et sa valeur.
On split selon les quotes et les opérateurs rencontrés. On construit un tableau de structure split
avec comme attribut le caractère séparateur ainsi que la chaîne contenue entre les deux délimiteurs.
mini@shell /home/lucie/Code/minishell$ that 's' a | " \"wonderfuuul" >$tree ;with '"an' |AMAZING parsing
[ ] [that]
['] [s]
[ ] [a]
[|] [|]
["] [ \"wonderfuuul]
[>] [>]
[ ] [$tree]
[;] [;]
[ ] [with]
['] ["an]
[|] [|]
[ ] [AMAZING]
[ ] [parsing]
Informations principales contenues dans les noeuds :
- input : fd depuis lequel on va lire
- output : fd sur lequel on va écrire
- start : position dans le split
- number : nombre d'arguments
- sep : token représentant le séparateur ou l'opérateur (
|
,>
,'
...)
Prenons comme exemple A | B > C ; D
. On va construire l'arbre suivant :
|
A ;
B > C D
Remplissage gauche puis droite. On aura donc toujours qu'un fils sans enfant à gauche. On va créer l'arbre selon le contenu de split
. Lors de la création on va remplir les informations suivante : l'indice correspondant au noeud dans split
, le nombre d'arguments composant la commande quand on se trouve sur une feuille (noeud sans enfant donc ici A,B,C,D
), le type d'opérateur (|
ou ;
) si l'on se trouve sur un noeud principal. Exemple :
mini@shell /home/lucie/Code/minishell$ that 's' a | " \"wonderfuuul" >$tree ;with '"an' |AMAZING parsing
[ ] [that]
['] [s]
[ ] [a]
[|] [|]
["] [ \"wonderfuuul]
[>] [>]
[ ] [$tree]
[;] [;]
[ ] [with]
['] ["an]
[|] [|]
[ ] [AMAZING]
[ ] [parsing]
--- parcours postfixe de l'arbre ---
type : 0 0[3] operator : that s a
type : 1 3[1] operator : | |
type : 0 4[3] operator : " \"wonderfuuul > $tree
type : 5 7[1] operator : ; ;
type : 0 8[2] operator : with "an
type : 1 10[1] operator : | |
type : 0 11[2] operator : AMAZING parsing
--- recursive execution ---
input 0 output 3 that s a
input 4 output 1 \"wonderfuuul > $tree
input 0 output 5 with "an
input 6 output 1 AMAZING parsing
Avant d'exécuter les commandes on a besoin de connaître input et output pour chaque noeud. On va parcourir à nouveau l'arbre pour remplir ces attributs.
- si on a un
|
appelle la fonctionpipe
qui va nous donner deux fd pour effectuer la liaison entre les commandes, on les donnera comme output au fils gauche et comme input au fils droit. - lorsque l'on rencontre un
;
on remet l'input et l'output aux entrées et sorties standard (0 et 1)
Pour l'expansion, on étend les variables d'environnement lorsque "
ou ' '
et on remplace sep
dans la structure par ' '
. Attention à certains cas un peu tricky lorsque la variable correspond à un fichier de redirection :
lucie@lucie-XPS:~/Code/minishell$ var="file 2"
lucie@lucie-XPS:~/Code/minishell$ echo bl > $var
bash: $var: ambiguous redirect
Attention aussi à :
lucie@lucie-XPS:~/Code/minishell$ var="echo hey"
lucie@lucie-XPS:~/Code/minishell$ $var
hey
Lorsque l'on est sur une commande/feuille, on regarde le premier mot après expansion :
- si c'est une commande built-in go to la fonction dédiée
- si on commence par
./
on essaye de lancer l'exécutable./name
fork needed - s'il y a un
=
on créé une variable d'environnement - sinon on essaye de lancer l'exécutable $(PATH)/name fork needed
- prend en argument l'ensemble des chaînes de caractères en suivant, attention au cas où les arguments ne sont pas séparés par un espace :
lucie@lucie-XPS ~/Code/minishell$ echo "'"'$a'"'"
'$a'
lucie@lucie-XPS ~/Code/minishell$ echo "'" '$a' "'"
' $a '
- si on a une redirection
<
tente d'ouvrir le fichier mais ignore son contenu, donc lève une exception si le fichier n'existe pas par exemple - pour l'option
-n
:
lucie@lucie-XPS ~/Code/minishell$ echo -n "-n"
lucie@lucie-XPS ~/Code/minishell$ echo -n -n '-n' -n
lucie@lucie-XPS ~/Code/minishell$ echo -n -n -n -n "hey"
hey%
lucie@lucie-XPS ~/Code/minishell$ echo -n "hey " -n -n -n "hey"
hey -n -n -n hey%
a
oua 1
ou3.14
:exit(2)
+ erreurnumeric argument required
1 a
ou1 2
: pas d'exit + $? = 1 + erreurtoo many arguments
- si juste un argument numérique -> go le convertir % 256 et exit avec cette valeur