A minimal, POSIX‑inspired Unix shell built from scratch in C with zero external dependencies beyond the standard library,
readline, and a customlibft.
This is the final version of the 42 minishell project. It reads user input, breaks it into tokens, builds a command pipeline, expands environment variables and quotes, sets up file redirections, and executes commands with forks, pipes, and execve — just like bash, only smaller.
| Feature | Description |
|---|---|
| Interactive prompt | Colored prompt with user, hostname, and current directory. |
| Command execution | Runs external programs via $PATH resolution or absolute/relative paths. |
| Pipes | Chains commands with |, e.g. ls -l | grep .c | wc -l. |
| Redirections | <, >, >>, and << (heredocs) all supported. |
| Environment | Full env, export, unset, and $VAR expansion including $?. |
| Quotes | Handles single and double quotes, plus quote-aware expansion. |
| Builtins | echo, cd, pwd, export, unset, env, exit. |
| Signals | Ctrl-C and Ctrl-\ behave correctly in the prompt, during execution, and inside heredocs. |
The shell is organized into three clean layers that mirror how a real shell works:
┌─────────────────────────────────────────────────────────────┐
│ REPL loop (main.c) │
│ readline → lexer → parser → executor → cleanup │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌───────────┐
│ Lexer │ │ Parser │ │ Executor │
│ │ │ │ │ │
│ chars │ tokens │ command │ pipeline │ fork/ │
│ ──► │ ───────► │ list │ ──────► │ execve/ │
│ tokens │ │ │ │ builtins │
└─────────┘ └──────────┘ └───────────┘
- Lexer — turns raw input into a flat list of tokens (
COMMAND,PIPE,IN,OUT,HEREDOC,APPEND, quotes, etc.). - Parser — joins arguments and redirections, expands
$VARand quotes, splits pipelines, and wires up file descriptors. - Executor — forks child processes, redirects
stdin/stdout, executes builtins or external binaries, waits for everything to finish, and stores the exit code.
Even though the project is written in C, it applies several classic software-engineering ideas:
Functions like data(), env(), and pipeline() return pointers to static variables. This guarantees a single, globally accessible instance of the shell state, environment table, and pipeline context without scattering globals across files.
The custom hash table (t_hash) is a self‑contained "object": it carries its own function pointers for add, get_value, delete, clean, hash, and print. You interact with it through a consistent interface rather than directly touching its internal buckets.
Builtins are registered in a t_builtins struct that holds a function pointer per command (cd, pwd, env, echo, exit, unset, export). The executor picks the right strategy at runtime based on the command name, making it easy to add or swap builtins.
Pipelines are split into segments, each segment gets its own pipe(), and stdin/stdout are rewired between commands. This lets simple commands be composed into powerful chains without hard‑coding special cases.
The lexer walks the input character by character and transitions between states: normal text, inside single quotes, inside double quotes, or while parsing a redirection. This keeps quote and operator handling predictable.
Every resource that is opened (file descriptors, allocated lists, hash tables, command nodes) has a matching cleanup path. A central data_destroy() routine tears the whole shell state down cleanly on exit.
Lexer, parser, and executor each own one concern and communicate through well‑defined data structures (t_list of tokens → t_command list → pipeline execution). This separation makes the codebase easier to read, test, and extend.
# clone with the custom libft submodule
git clone --recursive git@github.com:Filz0r/minishell.git
cd minishell
# compile
make
# run
./minishellOther useful targets:
| Target | What it does |
|---|---|
make clion |
Rebuild with debug symbols (-g -g3). |
make valgrind |
Run under Valgrind with leak and FD tracking. |
make clean |
Remove object files. |
make fclean |
Remove object files and the binary. |
make re |
Full rebuild. |
Building a shell from the ground up teaches the fundamentals that higher‑level languages hide:
- Process management —
fork,execve,waitpid, and process groups. - File descriptors & inter‑process communication — pipes,
dup2, and careful FD bookkeeping. - Memory safety in C — every allocation has an owner and a cleanup path.
- Signal handling — asynchronous events that must not corrupt shell state.
- Parsing & language design — tokenization, quoting rules, and expansion order.
For non‑technical readers: think of this project as building a tiny operating‑system assistant. It takes the words you type, understands their structure, starts the right programs, connects their inputs and outputs, and reports back whether they succeeded.
This project is released under the MIT License.
Made with ☕, 🧠, and a lot of printf debugging at 42 Lisboa.