← Retour aux projets

École 42 · 125/100

Minishell

Un interpréteur de commandes façon bash, écrit en C : pipes, redirections, expansion de variables et gestion fine des signaux.

Minishell reconstruit les mécanismes essentiels d'un shell Unix : analyse de la ligne de commande, exécution de processus via fork/pipe/execve, et une gestion des signaux qui reproduit fidèlement le comportement de bash. Pour être validé, chaque comportement est comparé directement à bash sur des dizaines de cas : pipes, redirections, quotes, expansion de variables, built-ins (cd, echo, export, unset, env, exit) et signaux (SIGINT, SIGQUIT). Un seul écart de comportement fait échouer le test. Projet réalisé en binôme.

Architecture

Plutôt que de traiter une ligne de commande comme une simple liste de mots à exécuter dans l'ordre, elle est transformée en arbre syntaxique (AST) : chaque opérateur (pipe, ET, OU) devient un nœud interne, chaque commande devient une feuille. Parcourir cet arbre depuis la racine donne naturellement le bon ordre d'évaluation, sans avoir à coder les priorités entre opérateurs à la main.

Par exemple, cat file | grep pattern && echo ok donne l'arbre ci-contre : le nœud racine (&&) ne déclenche echo ok que si tout le sous-arbre du pipe a réussi, lui-même construit à partir de cat file et grep pattern.

Expansion des variables

L'expansion suit l'ordre réel de bash : substitution des guillemets et des variables, nettoyage des chaînes vides, découpage en mots, expansion des jokers, puis suppression des guillemets restants.

Heredocs

Les heredocs sont lus dans un processus fils dédié, avec sa propre gestion de SIGINT et une détection des délimiteurs entre guillemets pour désactiver l'expansion quand nécessaire.

Signaux

Le comportement des signaux reproduit celui de bash au détail près : SIGINT au niveau de l'invite efface la ligne en cours sans quitter le shell, tandis que SIGQUIT n'affiche « Quit: 3 » que lorsqu'un processus fils est en cours d'exécution.

Ce que ça m'apporte aujourd'hui

Avoir construit un vrai pipeline lexer, parseur, exécuteur change ma façon de lire n'importe quel système qui interprète quelque chose, que ce soit un moteur de règles métier, un langage de configuration, ou la façon dont un framework résout ses décorateurs. Je ne le vois plus comme une boîte magique, mais comme un arbre qu'on parcourt.

Ça m'a aussi donné une vraie discipline sur les processus et les signaux, directement transférable à l'exploitation de services en production : un arrêt propre d'un conteneur avant qu'il soit tué répond exactement à la même logique que gérer SIGINT ou SIGQUIT ici. Et avoir écrit moi-même l'expansion des variables me donne une intuition concrète de la façon dont naissent les failles d'injection shell : je sais précisément quels caractères sont dangereux, et pourquoi.