Minishell es un proyecto del programa 42 cuyo objetivo es desarrollar un intérprete de comandos básico, similar a Bash, utilizando el lenguaje C.
El proyecto sigue el clásico patrón REPL (Read-Eval-Print Loop) y cubre tokenización, análisis sintáctico, comandos integrados, redirecciones de E/S, tuberías y manejo de señales. 🎓📂🧠
Es una excelente herramienta para comprender los fundamentos de sistemas Unix y la ejecución de procesos en C.
Un shell es una interfaz de línea de comandos que permite a los usuarios interactuar con el sistema operativo escribiendo comandos. Este proyecto reimplementa características centrales de bash para ilustrar cómo un shell lee la entrada, la analiza y ejecuta comandos, manejando tuberías, redirecciones, variables de entorno y comandos integrados. 🛠️🐚🧩
Requisitos previos: Un entorno similar a Unix, GCC o Clang y conocimientos básicos de C. 🐧⚙️📘
# Limpiar
$ make fclean
# Compilar
$ make
# Ejecutar
$ ./minishellEl shell está estructurado en cinco etapas (REPL): 🎯🔁📜
- READ: Leer una línea de entrada del usuario.
- EVAL: Tokenizar y analizar sintácticamente la entrada en comandos y operadores.
- PRINT/EXEC: Ejecutar los comandos analizados o mostrar errores.
- LOOP: Repetir hasta que el usuario salga.
Internamente, el proyecto utiliza la siguiente organización de carpetas y archivos: 🗂️📂🧩
-
include/: Contiene archivos de cabecera, comominishell.h. -
source/: Código fuente dividido en submódulos:builtin/: Implementación de comandos integrados.execution/: Gestión de la ejecución de procesos yexecve().main.c: Función principal y bucle REPL.main_utils.c: Funciones auxiliares paramain.c.parsing/: Análisis sintáctico y construccion de una matriz que servirá para la construcción del AST.tokenization/: Construcción dada la matriz anteriormente mencionada de la estructura AST.envp/: Gestión de variables de entorno.libft/: Funciones reutilizables de apoyo.signals/: Manejo de señales (CTRL-C, CTRL-D, etc.).
-
Línea → Palabras: La línea de entrada se divide en un arreglo de palabras, contando espacios y manejando cadenas entre comillas. 🧮📝🔠
-
Análisis:
-
Comandos (p.ej.,
ls,grep): el primer token es el programa y los siguientes son los argumentos. -
Ejecutables del directorio actual: comandos que comienzan con
./. -
Caracteres especiales:
- Comillas: simples (
') y dobles ("), con las dobles permitiendo expansión de$. - Redirecciones:
<,>,>>,<<(heredoc). - Tuberías:
|. - Variables de entorno:
$VARy$?para el estado de salida.
- Comillas: simples (
-
Representamos comandos y operadores en una estructura de árbol. Los nodos pueden ser: 🧩📊📐
- Tubería/Pipes:
| - Redirecciones:
<,>,>>,<< - Comando: externo o integrado con argumentos
- Archivo/Argumento: destinos para redirecciones o argumentos
[ROOT]
├─ [Pipe |]
├─ [Redirections < > >> <<]
├─ [Cmd]
└─ [File/Arg]
Un ejemplo sencillo sería:
ls -l | grep ".c" > file.txt[ROOT]
└─ [PIPE]
├─ [CMD] ls -l
└─ [REDIRECCION] > file.txt
└─ [CMD] grep ".c"
-
Comandos: Para cada comando (integrado o externo), se crea un proceso hijo utilizando fork().
- Si el comando es externo, el hijo reemplaza su contexto de ejecución usando execve().
- Si es un comando integrado (builtin) y no forma parte de una tubería, puede ejecutarse directamente en el proceso padre para evitar problemas con el estado del shell (por ejemplo, cd, exit).
La salida del comando se controla con el código de retorno, recuperado mediante wait() o waitpid().
-
Redirecciones: Se utilizan las llamadas al sistema open(), dup2() y close() para redirigir adecuadamente la entrada/salida estándar del proceso antes de ejecutar el comando:
- Redirección de entrada (<): redirige stdin desde un archivo especificado.
- Redirección de salida (>): redirige stdout a un archivo, truncando su contenido si ya existe, o creándolo si no.
- Redirección heredoc (<<): se lee entrada del usuario hasta que se encuentra una línea que coincida con un delimitador definido. El contenido se escribe en un archivo temporal, y se redirige stdin a ese archivo.
- Redirección de salida en modo append (>>): redirige stdout a un archivo en modo append, manteniendo el contenido existente y añadiendo la nueva salida al final.
-
Tuberías(|): Se emplea pipe() para conectar el stdout de un comando al stdin del siguiente. Cada comando en una pipeline se ejecuta en un proceso distinto. Se gestiona cuidadosamente el cierre de los extremos de las tuberías para evitar descriptores colgantes o fugas de recursos.
Todos ellos funcionan igual que en bash
echo [-n]: imprime argumentos en stdout.cd [dir]: cambia el directorio de trabajo actual.pwd: muestra el directorio actual.export VAR=value: asigna una variable de entorno.unset VAR: desasinga una variable de entorno.env: muestra todas las variables de entorno.exit: sale del programa.
- Comillas: comillas simples para texto literal, dobles para permitir expansión de
$. 🔠📏📌 - Heredoc (
<<): lee hasta una línea delimitadora; no registra en el historial. 📄📥🛑 - Manejo de errores: errores de sintaxis, comando no encontrado, fallos en redirecciones. 🚫📛📤
📘 Consulta la documentación técnica completa de funciones en docs/FUNCIONES.md
42-MINISHELL
├─ include/
│ └─ minishell.h
├─ source/
│ ├─ builtin/
│ ├─ execution/
│ ├─ main.c
│ ├─ parsing/
│ ├─ tokenization/
│ ├─ envp/
│ ├─ libft/
│ ├─ main_utils.c
│ └─ signals/
└─ Makefile
Javier Fernández Correa |
Penélope Palacios Alvira |
Alberto Jódar Carrasco |
Pablo Alonso García |
Pablo Pérez Aubá Gijón Bonales |
Este proyecto está bajo la licencia MIT. ⚖️📄🆓