Aller au contenu

Minishell

C UNIX Libft Readline Signaux

Interpréteur de commandes UNIX (shell) inspiré de bash, développé en C.


Présentation

Un interpréteur de commandes UNIX qui exécute des commandes, gère les pipes et réagit aux signaux comme le ferait un vrai terminal.


Contexte

Projet du tronc commun de l'école 42, réalisé en binôme avec mon camarade, en 2023.

L'objectif : comprendre ce qui se passe réellement quand on tape une commande dans un terminal : les processus, les descripteurs de fichiers et les signaux.


Mon rôle

Projet réalisé en binôme : j'ai conçu le cœur de l'interpréteur, mon binôme a apporté le découpage des arguments et l'expansion des variables.

  • Conception du REPL (boucle lecture-évaluation-exécution) avec readline pour le prompt et l'historique
  • Parsing de la ligne de commande : substitution des variables, espacement des pipes, découpage en arguments et nettoyage des quotes
  • Implémentation des builtins et de l'exécution des commandes externes via fork / exec / wait
  • Pipes (simples et multiples) et gestion des signaux avec readline

Architecture

Architecture de Minishell

Schéma de l'architecture de Minishell

Le REPL (Read - Eval - Print - Loop) est le cycle fondamental de tout shell interactif :

Étape Rôle Dans le code
Read - Lecture Le shell lit la commande saisie au clavier readline() + add_history()
Eval - Évaluation La commande est parsée puis exécutée parsing() → fork() / execve()
Print - Affichage La sortie s'affiche sur le terminal dup2() vers STDOUT_FILENO
Loop - Boucle Le shell réaffiche le prompt et attend la saisie suivante retour au while(1) avec free()

La boucle tourne jusqu'à ce qu'un signal d'arrêt mette fin au programme : commande exit ou fin de fichier (Ctrl+D).

L'étape Eval correspond au pipeline de parsing, qui enchaîne quatre transformations sur la chaîne lue :

  1. Expansion des variables ($VAR, $?) hors zones entre quotes simples
  2. Espacement des caractères | pour les isoler comme tokens
  3. Découpage en arguments avec un splitter qui ignore les séparateurs entre quotes
  4. Nettoyage des quotes résiduelles de chaque argument

L'exécution distingue ensuite trois cas : une commande simple, une redirection (>), et un pipe - chaque commande d'un pipe étant lancée dans un processus fork relié à son voisin via dup2, avec une gestion dédiée du dernier maillon de la chaîne.


Technologies

Langage
C
Bas-niveau, gestion mémoire manuelle, compilation native
Système
Linux/UNIX
Appels système POSIX : fork, dup2, execve, pipe
Bibliothèques
LIBFT
bibliothèque personnalisée réimplémentant libc
READLINE
édition de ligne interactive
Outils
Makefile
build automatisé
Clang
compilateur LLVM, stricte conformité

Fonctionnalités

  • Commandes builtins (cd, echo, pwd, export, unset, env, exit)
  • Commandes externes via PATH et exécutables locaux ./programme
  • Pipes simples et multiples (|)
  • Redirection de sortie (>)
  • Variables d'environnement ($VAR) et code de retour ($?)
  • Gestion des signaux : Ctrl+C (interrompt la commande, réaffiche le prompt), Ctrl+D (quitte)
  • Gestion des erreurs

Problème | Solution

Problème Solution
Distinguer un \| dans une chaîne echo "a \| b" d'une vraie pipe Découpage avec skip_quotes : les séparateurs entre quotes simples ou doubles sont ignorés
Interrompre une commande qui tourne sans tuer le shell Gestion des signaux ciblée : Ctrl+C transmet l'interruption à l'enfant via un handler readline, le shell reste actif
Enchaîner plusieurs commandes avec des pipes Une boucle de fork qui chaîne les descripteurs : dup2 relie la sortie d'un enfant à l'entrée du suivant, dernier maillon géré séparément
Empêcher les variables d'être développées dans une chaîne simple-quoted L'expansion saute explicitement les zones délimitées par '...'

Résultats

  • REPL complet : l'utilisateur tape, le shell exécute
  • Builtins fonctionnelles : cd, echo, pwd, export, unset, env, exit
  • Signaux correctement gérés : Ctrl+C interrompt, le shell reste vivant
  • Pipes simples et chaînes multi-commandes

Ce que j'ai appris

J'ai compris le pipeline fork → exec → wait : je vois désormais chaque ls comme un processus qui naît, vit et meurt.

La gestion des signaux m'a appris qu'un programme est défini autant par ce qu'il fait que par ce qu'il refuse de faire, et que le parsing d'entrées utilisateur est un terrain miné, une citation mal fermée fait tout exploser.


Lien

Voir le code