Développer un shell Linux en Python

Un shell est un programme qui permet aux utilisateurs d’interagir avec le système d’exploitation en exécutant des commandes. Développer un shell Linux en Python est un excellent exercice pour comprendre le fonctionnement des interfaces en ligne de commande (CLI), la gestion des processus et les interactions avec le système.

Dans cet article, nous allons construire un shell simple en Python qui prend en charge :
L’exécution de commandes système (comme ls, pwd, cat, etc.)
Les commandes intégrées (cd, exit)
La gestion des processus avec subprocess
Les redirections et les pipes (|, >, <)


1. Comprendre les bases d’un shell

Un shell fonctionne selon un cycle REPL (Read-Eval-Print Loop) :
1️⃣ Lire : Accepter une entrée utilisateur.
2️⃣ Analyser : Découper la commande en tokens (mots-clés, arguments).
3️⃣ Exécuter : Lancer la commande en tant que processus.
4️⃣ Afficher : Montrer le résultat à l’utilisateur.

Voici un exemple simple d’un shell fonctionnant en boucle :

pythonCopierModifierwhile True:
    command = input("$ ")  # Lire la commande
    if command == "exit":
        break
    print(f"Vous avez tapé : {command}")

2. Exécution de commandes système avec subprocess

Le module subprocess permet de lancer des programmes externes comme ls, pwd, echo, etc.

Exemple :

pythonCopierModifierimport subprocess

while True:
    command = input("$ ").strip()
    if command.lower() == "exit":
        break
    try:
        subprocess.run(command.split())  # Exécuter la commande
    except FileNotFoundError:
        print("Commande inconnue")

Explication :

  • command.split() découpe l’entrée utilisateur en une liste (ls -l devient ['ls', '-l']).
  • subprocess.run() exécute la commande.
  • try/except gère les erreurs si la commande n’existe pas.

3. Implémentation des commandes intégrées (cd, exit)

Certaines commandes comme cd ou exit sont des commandes intégrées car elles modifient l’environnement du shell. Elles ne peuvent pas être exécutées avec subprocess.

Nous utilisons os pour les gérer :

pythonCopierModifierimport os
import subprocess

def change_directory(path):
    try:
        os.chdir(path)
    except FileNotFoundError:
        print(f"Dossier introuvable : {path}")

while True:
    command = input("$ ").strip()
    if command.lower() == "exit":
        break
    elif command.startswith("cd "):
        path = command.split(maxsplit=1)[1]  # Extraire le chemin
        change_directory(path)
    else:
        try:
            subprocess.run(command.split())
        except FileNotFoundError:
            print("Commande inconnue")

4. Gestion des redirections (>, <, >>)

Un shell permet de rediriger la sortie (> pour écraser un fichier, >> pour ajouter) et l’entrée (< pour lire un fichier).

Exemple :

shCopierModifierecho "Hello" > test.txt  # Écrit "Hello" dans test.txt
cat < test.txt           # Affiche le contenu de test.txt
echo "New Line" >> test.txt  # Ajoute "New Line"

Implémentation en Python

Nous devons :

  • Vérifier si > ou < est présent dans la commande.
  • Ouvrir le fichier et rediriger stdout ou stdin.
pythonCopierModifierdef execute_command(command):
    if ">" in command:  # Redirection de sortie
        parts = command.split(">")
        cmd, file_name = parts[0].strip(), parts[1].strip()
        with open(file_name, "w") as file:
            subprocess.run(cmd.split(), stdout=file)
    
    elif ">>" in command:  # Append (ajout)
        parts = command.split(">>")
        cmd, file_name = parts[0].strip(), parts[1].strip()
        with open(file_name, "a") as file:
            subprocess.run(cmd.split(), stdout=file)
    
    elif "<" in command:  # Redirection d’entrée
        parts = command.split("<")
        cmd, file_name = parts[0].strip(), parts[1].strip()
        with open(file_name, "r") as file:
            subprocess.run(cmd.split(), stdin=file)

    else:
        subprocess.run(command.split())

while True:
    command = input("$ ").strip()
    if command.lower() == "exit":
        break
    elif command.startswith("cd "):
        change_directory(command.split(maxsplit=1)[1])
    else:
        execute_command(command)

5. Gestion des pipes (|)

Les pipes (|) permettent d’enchaîner plusieurs commandes en connectant la sortie de l’une à l’entrée de l’autre.
Exemple :

shCopierModifierls -l | grep py

ici, ls -l affiche une liste de fichiers et grep py filtre ceux contenant « py ».

Implémentation en Python

Nous devons :

  • Séparer les commandes avec |.
  • Utiliser subprocess.Popen() pour créer un pipeline entre elles.
pythonCopierModifierdef execute_piped_commands(command):
    commands = [cmd.strip().split() for cmd in command.split("|")]
    
    prev_process = None
    for cmd in commands:
        if prev_process:
            process = subprocess.Popen(cmd, stdin=prev_process.stdout, stdout=subprocess.PIPE)
            prev_process.stdout.close()  # Fermer la sortie précédente
        else:
            process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
        prev_process = process
    
    if prev_process:
        prev_process.communicate()  # Exécuter la dernière commande

while True:
    command = input("$ ").strip()
    if command.lower() == "exit":
        break
    elif "|" in command:
        execute_piped_commands(command)
    elif command.startswith("cd "):
        change_directory(command.split(maxsplit=1)[1])
    else:
        execute_command(command)

6. Ajout d’un prompt personnalisé

Pour rendre notre shell plus réaliste, ajoutons un prompt qui affiche le répertoire courant :

pythonCopierModifierimport os

def get_prompt():
    return f"{os.getcwd()}$ "

while True:
    command = input(get_prompt()).strip()
    if command.lower() == "exit":
        break
    elif "|" in command:
        execute_piped_commands(command)
    elif command.startswith("cd "):
        change_directory(command.split(maxsplit=1)[1])
    else:
        execute_command(command)

7. Conclusion et améliorations possibles

Nous avons construit un shell Linux simple en Python qui prend en charge :
✅ L’exécution de commandes externes
✅ Les commandes intégrées (cd, exit)
✅ Les redirections (>, <, >>)
✅ Les pipes (|)
✅ Un prompt dynamique

Améliorations possibles 🚀

🔥 Support des alias (alias ll='ls -l')
🔥 Ajout d’un historique de commandes
🔥 Prise en charge des variables d’environnement ($HOME)
🔥 Complétion automatique avec readline

Avec ces fonctionnalités, vous pouvez construire un shell personnalisé pour Linux, macOS ou Windows ! 💻🚀

carle
carle