A minimal UNIX shell implementation in C that mimics bash functionality. This project implements core shell features including command execution, piping, redirections, environment variables, and built-in commands.
Minishell is a teaching project that recreates essential shell mechanics:
- Lexical analysis (tokenization) and parsing of shell input
- Command execution with pipe support (
|) - Input/Output redirections (
<,>,>>,<<) - Environment variable expansion (
$VAR) - Built-in commands (echo, cd, pwd, export, unset, env, exit)
- Signal handling (SIGINT, SIGTSTP, SIGCHLD)
- Garbage collection wrapper for memory management
- Readline integration for interactive prompt with history
Minishell/
├── minishell.h # main header with structs and function prototypes
├── src/ # source code
│ ├── main.c # entry point, REPL loop
│ ├── init.c # initialization of data structures
│ ├── lexer.c # tokenization of input
│ ├── lexer_helpers.c # quote/expansion helper functions
│ ├── expansion.c # variable expansion ($VAR)
│ ├── expansion_utils.c
│ ├── classify.c # token type classification
│ ├── syntax_errors.c # syntax validation
│ ├── execution.c # command execution logic
│ ├── execution_utils.c
│ ├── pipe.c # pipeline (|) handling
│ ├── redirections.c # I/O redirection (<, >, >>)
│ ├── heredoc.c # here-document (<<) handling
│ ├── signals.c # signal handlers
│ ├── echo.c # echo builtin
│ ├── cd.c # cd builtin
│ ├── pwd.c # pwd builtin
│ ├── env.c # env builtin
│ ├── export.c # export builtin
│ ├── unset.c # unset builtin
│ ├── exit.c # exit builtin
│ ├── garbage_colector.c # memory management / GC
│ ├── gc_wrappers.c # malloc/strdup wrappers
│ ├── basic_linked.c # environment linked list
│ ├── token_linked.c # token linked list
│ ├── utils.c # misc utilities
│ └── error_exit.c # error handling
├── libft/ # custom C library (must be built)
└── Makefile # build rules
typedef struct s_tocken
{
t_token_type type; // COMMAND, ARGS, PIPE, REDIRECT_OUT, etc.
char *value; // token string value
struct s_tocken *next;
struct s_tocken *previous;
} t_token;typedef struct s_data
{
t_token *tokens; // linked list of tokens
char **buildins; // builtin command names
t_gc gc; // garbage collector
char *input; // raw input
char **env_full; // environment as 2D array
char **env_cmd_paths; // PATH directories
t_env *env; // environment as linked list
int exit_code; // last exit status
t_heredoc *heredoc; // heredoc delimiters/fds
} t_data;typedef struct s_gc
{
t_gcobj *objects; // tracked allocations
void **roots[100]; // root pointers for mark-and-sweep
int root_count;
} t_gc;- Linux (tested on Debian/Ubuntu)
- GCC or Clang
- GNU Readline library (
libreadline-devon Ubuntu/Debian) - Make
- Custom libft (provided in
libft/folder)
sudo apt-get install -y build-essential libreadline-devBuild minishell:
makeRun the interactive shell:
./minishellYou'll see a prompt (by default minishell$). Type commands as in bash:
minishell$ echo hello world
hello world
minishell$ pwd
/home/user/Minishell
minishell$ ls -la
# ... file listing ...
minishell$ whoami
userminishell$ echo "apple banana cherry" | grep banana
banana# Output redirect
minishell$ echo "hello" > output.txt
# Append
minishell$ echo "world" >> output.txt
# Input redirect
minishell$ cat < output.txt
hello
world
# Here-document
minishell$ cat << EOF
> line 1
> line 2
> EOF
line 1
line 2minishell$ export MY_VAR=hello
minishell$ echo $MY_VAR
hello
minishell$ MY_VAR=world ./some_command
# MY_VAR set to "world" only for this commandPrint arguments. Supports -n flag (no newline).
minishell$ echo -n "hello"
helloChange directory. Supports ., .., ~, and absolute/relative paths.
minishell$ cd /tmp
minishell$ pwd
/tmpPrint working directory.
minishell$ pwd
/home/user/MinishellDisplay environment variables.
minishell$ env | head
PATH=/usr/local/bin:/usr/bin:...
HOME=/home/user
...Set an environment variable.
minishell$ export USER_NAME=alice
minishell$ echo $USER_NAME
aliceRemove an environment variable.
minishell$ unset USER_NAME
minishell$ echo $USER_NAME
# (empty)Exit the shell with optional exit code.
minishell$ exit 42
# shell exits with code 42- SIGINT (Ctrl+C) — terminates current foreground command, returns to prompt
- SIGTSTP (Ctrl+Z) — pauses current command (if supported)
- SIGCHLD — handles child process termination
- Lexer (
lexer.c) — tokenizes raw input into a linked list of tokens - Quote/Variable Expansion (
expansion.c) — expands$VARand processes quotes - Token Classification (
classify.c) — marks each token (COMMAND, ARGS, PIPE, REDIRECT_OUT, etc.) - Syntax Validation (
syntax_errors.c) — checks for pipe/redirect syntax errors - Execution (
execution.c) — parses and executes the token stream
- User enters command at prompt
- Lexer tokenizes input
- Variable expansion and quote processing
- Token classification and merge
- For each command:
- Check for redirections/here-docs
- If builtin → execute in-process
- If external → search PATH, fork, and exec
- Handle pipes by creating child processes connected via pipes
- Wait for child processes and update exit code
minishell$ cat file1.txt | grep "pattern" > output.txt 2>&1Execution:
- Lexer tokenizes:
cat,file1.txt,|,grep,"pattern",>,output.txt,2>&1 - Parser detects 1 pipe → creates 2 processes
- Process 1:
cat file1.txt→ stdout to pipe - Process 2:
grep "pattern"→ stdin from pipe, stdout tooutput.txt - Main shell waits for both to finish
The GC wrapper tracks all allocations via gc_malloc(), gc_strdup(), etc. On exit or error, gc_destroy() frees all tracked memory automatically. This prevents memory leaks in the shell loop.
char *str = gc_strdup(&data->gc, "hello"); // tracked
// ... use str ...
gc_destroy(&data->gc); // frees all at onceRun the shell and try:
./minishell
minishell$ echo $HOME
/home/user
minishell$ ls -la | grep minishell
minishell$ export TEST=value && echo $TEST
value
minishell$ exit- Processes run sequentially; background jobs (
&) not yet implemented - Job control (fg/bg) not implemented
- History persists only during session (readline library handles this)
Built as a school project (42 Heilbronn). Collaborator(s) Chiara Kappe.