Tabla de contenido
Una vez más aquí. Hace un tiempo ya había realizado un curso sobre C y Python en el que se había quedado en el tintero la idea de escribir una pequeña shell en C para practicar ciertos conceptos. Como he vuelto a otro curso, me puse a recuperar la idea. Esto es algo que hice en los últimos tres días antes de solicitar la exención de la parte teórica; de ahí nació el nombre de esta shell: Termina-L-ndo.
Aquellos que me conocen saben que soy de esa clase de gente que, siempre que puede, utiliza la terminal para todo, y que al mismo tiempo, también me pregunté toda la vida qué ocurre realmente cuando escribes ls | grep .c y pulsas Enter. Detrás de esa línea hay un intérprete de comandos que lee texto, lo analiza, crea procesos y conecta tuberías.
Termina-L-ndo es una shell en C minimalista pensada para aprender y practicar esos mecanismos sin la complejidad de bash o zsh: todo en menos de mil líneas de código, comentadas en español (creo que hay más comentarios que código real), que cubren desde fork y exec hasta historial con flechas y autocompletado con TAB. Vamos, lo que vienen siendo las cuatro funcionalidades básicas de todo… teniendo en cuenta que solo tuve 15 horas de aula y alguna extra que le dediqué en casa con un manual viejuno que tenía aparcado en una estantería, me ha gustado el resultado final.
En las siguientes líneas vamos a ver cómo funciona todo esto (hace ya algo más de una semana que no veo el código, pero así también sabré si me entero con mi propios comentarios), qué módulos del código son imprescindibles y cómo compilar y ejecutar el proyecto en Linux.
¿Qué es Termina-L-ndo y para qué sirve?
Como decía líneas atrás, Termina-L-ndo es una shell interactiva escrita en C11 para sistemas POSIX (Linux). Este pequeño shell no pretende sustituir a bash ni ser usado para nada más que practicar y servir como laboratorio: un espacio para ver en código real cómo un programa se convierte en un intérprete de comandos.
✨ Características principales
- ⚙️ Comandos externos: Ejecución en paralelo mediante las llamadas del sistema
fork+execvp. - 📂 Comandos internos (Builtins): Soporte nativo para órdenes que deben afectar al propio proceso de la shell, como
cd,pwd,export,echo,history,help, etc. - 🔀 Operadores de flujo: Tuberías (
|), redirecciones de entrada/salida (<,>,>>) y ejecución en segundo plano (&). - 💲 Dinámica de variables: Gestión de variables de entorno (
$VAR,$?), sustitución de comandos ($(pwd)) y expansión de comodines (*.c). - ⌨️ UX de terminal: Historial en memoria con navegación mediante flechas ↑↓, autocompletado inteligente con
TABy un prompt dinámico coloreado con la ruta actual.
🛠️ Cómo compilar y ejecutar Termina-L-ndo
Los únicos requisitos son disponer del compilador GCC (o cualquiera compatible con C11) y un entorno Linux con las llamadas al sistema POSIX habituales.
- Descarga el proyecto: Tienes el código fuente disponible en el repositorio de desarrollo:
- 🔗 Ver Repositorio de Termina-L-ndo en GitHub (Sustituye por tu enlace real)
- Compilación y arranque: En la carpeta raíz del proyecto, ejecuta en tu terminal:
make ./termina-L-ndo
Nota: También puedes usar make run (compila si hace falta y lanza el shell directamente) o make clean para borrar los archivos objeto y el ejecutable generado.
Al arrancar, verás un prompt con la ruta actual en color cian y el sufijo $>. Este sufijo cambiará de color: verde si el último comando tuvo éxito o rojo si hubo un error. Escribe help para ver la ayuda integrada o exit para salir.

🧠 El corazón del shell: el bucle REPL
Toda shell interactiva sigue un patrón REPL (Read–Eval–Print Loop): leer una línea, evaluarla, imprimir el resultado y volver a mostrar el prompt. En el archivo main.c ese bucle es explícito y sigue un orden estricto:
- Leer con
shell_read_line(&history)(configura la terminal en modo raw si detecta un entorno interactivo). - Guardar la entrada en el historial con
history_add(). - Expandir sustituciones del tipo
$(...)y comillas invertidas. - Parsear la línea de texto convirtiéndola en un Pipeline de comandos estructurado.
- Expandir los globs (
*.c, etc.) antes de ejecutar. - Ejecutar la orden, discriminando si es un comando interno (builtin) o un pipeline externo.
- Actualizar el código de salida (
$?) y refrescar el color del prompt.
⚠️ El orden importa: La sustitución de comandos ocurre antes del parseo, pero el globbing ocurre después. Esto es así porque la expansión de asteriscos la debe resolver la shell en su contexto, no el programa hijo que va a ser ejecutado.
📂 Anatomía del código: Partes importantes
El proyecto está modularizado para mantener un código compacto y fácil de leer
parser.c— De texto a estructura ejecutable: Convierte una cadena plana comols -la | grep .c > out.txten estructuras limpias de datos (CommandyPipeline). Separa tokens respetando comillas simples y dobles, detecta operadores (|,<,>,>>,&) y expande las variables$VARen los argumentos. Sin un parser sólido, la shell no sabría qué está intentando hacer el usuario.executor.c— Fork, exec, pipes y señales: Aquí es donde se concentra gran parte del trabajo relacionado con procesos y comunicación entre ellos. Crea tuberías (pipe) entre procesos consecutivos, invoca afork()para bifurcar el proceso, redirige los descriptores standard (stdin/stdout) en el hijo y ejecutaexecvp(). El proceso padre gestiona el cierre de descriptores sobrantes y espera conwaitpid(). También capturaSIGINT(Ctrl+C) para evitar que la shell se muera si cancelas un proceso hijo.builtins.c— Comandos sin nuevo proceso: Órdenes comocdoexportno pueden delegarse en un hijo, deben ejecutarse en el proceso de la propia shell para poder alterar su directorio de trabajo o sus variables de entorno.is_builtin()comprueba el comando yrun_builtin()lo despacha directamente sin hacerfork.expand.c— Más allá del lanzador: Dos funciones elevan este software de un simple «lanzador» a una herramienta útil. La sustitución$(comando)ejecuta un subproceso, captura su salida estándar mediante un pipe e inyecta el texto resultante en la línea. El Globbing aprovecha la libreríaglob.hpara expandir patrones comosrc/*.cen múltiples argumentos antes delexecvp.input.c,history.cycomplete.c— Experiencia de terminal:input.cpone la terminal en modo raw modificando la estructuratermios. Así podemos pintar el prompt, bloquear el borrado del prefijo y capturar las secuencias de las flechas.history.cimplementa una cola circular de 50 líneas ycomplete.cintercepta elTABpara sugerir comandos internos, archivos de la ruta actual o ejecutables indexados en elPATH.utils.cycolors.c— Abstracción y estética:shell_print_prompt()usagetcwdy maquilla la ruta sustituyendo el prefijo del$HOMEpor el caracter~.shell_set_status()almacena el estado de salida para alimentar a$?y cambiar el color del prompt.colors.ccentraliza las secuencias de escape ANSI respetando el estándar modernoNO_COLOR.
💻 Ejemplos prácticos dentro de Termina-L-ndo
Una vez que tengas la shell compilada y abierta, puedes probar este flujo secuencial para ver cómo interactúan los módulos:
# 1. Consulta la ayuda interna help # 2. Revisa tu ubicación e inyéctala en una cadena expandida pwd echo "Directorio actual de trabajo: $(pwd)" # 3. Comprueba el funcionamiento del globbing ls src/*.c # 4. Pon a prueba el detector de errores del prompt false echo $? # Mostrará '1' y verás cómo el prompt pasa a color rojo # 5. Navega por tu sesión history exit
💡 Truco: Prueba a pulsar las flechas del teclado para recuperar órdenes anteriores o escribe ec + TAB para ver cómo se autocompleta la palabra echo.
🎯 Conclusión: ¿Por qué programar tu propia shell?
Escribir una shell minimalista es uno de esos proyectos pendientes que todo desarrollador apasionado por los sistemas debería experimentar. Te obliga a tocar de golpe los pilares fundamentales de la ingeniería de software y los sistemas operativos: el ciclo de vida de los procesos, el aislamiento de descriptores de archivos, la sincronización mediante tuberías, el control de señales y el diseño de interfaces de usuario en modo texto. … y como comenté en líneas anteriores, esto es algo que tenía pendiente … y ya puedo hacer check sobre ello.
Termina-L-ndo concentra toda esa complejidad en un entorno compacto, legible y estructurado. Modificar un módulo (por ejemplo, programar tu propio comando interno) y ver el resultado de inmediato tras un make run es una forma muy didáctica de aprender.
Si te atrae el desarrollo bajo entornos Linux y el estándar POSIX, te invito a clonar el repositorio, echarle un ojo a main.c y executor.c, y trastear con el código. Al fin y al cabo, es la mejor forma de entender qué pasa realmente en tu sistema cada vez que abres una terminal.
