A Unix command-line interpreter written from scratch in C, with zero external dependencies.
Built as a deep dive into operating systems internals — process management, signal handling, file descriptors, and terminal I/O — using only POSIX system calls.
_ __ ___ (_) ___ _ __ ___ ___| |__ ___| | |
| '_ ` _ \| |/ __| '__/ _ \/ __| '_ \ / _ \ | |
| | | | | | | (__| | | (_) \__ \ | | | __/ | |
|_| |_| |_|_|\___|_| \___/|___/_| |_|\___|_|_|
- Pipeline execution — chain commands with
|(e.g.ls -la | grep src | wc -l) - I/O redirection —
<,>,>>,2>,2>>for stdin, stdout, stderr - Job control — background processes (
&),jobs,fg,bg,Ctrl+Zto suspend - Signal handling —
Ctrl+Cinterrupts foreground process (not the shell),Ctrl+Dfor EOF - Command history — persistent across sessions, navigable with arrow keys
- Line editing — cursor movement, word-delete (
Ctrl+W), clear (Ctrl+L), kill-line (Ctrl+U/Ctrl+K) - Environment variables —
$VAR,${VAR},$?(last exit status),export,unset - Tilde expansion —
~/resolves to$HOME - Quoting — double quotes (with variable expansion), single quotes (literal), backslash escapes
- Command chaining —
;to run multiple commands sequentially - Built-in commands —
cd(withcd -),exit,help,history,jobs,fg,bg,export,unset - Colorized prompt — shows
user@host:~/cwd$
Requires a C11-compatible compiler (GCC or Clang) and a POSIX system (Linux, macOS, BSDs).
make # build the binary
make debug # build with AddressSanitizer + UBSan
make test # run integration tests (24 tests)
make clean # remove build artifactsThe binary is compiled with -Wall -Wextra -Werror -pedantic — zero warnings.
./microshell# Pipelines
cat /var/log/syslog | grep error | sort | uniq -c | sort -rn | head
# I/O redirection
gcc -o program main.c 2> errors.txt
sort < input.txt > sorted.txt
# Background jobs
sleep 30 &
jobs
fg 1
# Environment variables
export EDITOR=vim
echo "Using $EDITOR"
echo "Last exit code: $?"
# Command chaining
mkdir build; cd build; cmake ..; make ┌─────────────┐
│ main.c │ Initialization, cleanup
└──────┬──────┘
│
┌──────▼──────┐
┌─────│ repl.c │─────┐ Raw terminal mode,
│ │ │ │ line editing, prompt
│ └──────┬──────┘ │
│ │ │
┌──────▼──────┐ │ ┌──────▼──────┐
│ history.c │ │ │ signals.c │
│ │ │ │ │
└──────────────┘ │ └─────────────┘
│
┌──────▼──────┐
│ lexer.c │ Tokenization: quotes,
│ │ escapes, operators
└──────┬──────┘
│
┌──────▼──────┐
│ parser.c │ Build command/pipeline
│ │ structs from tokens
└──────┬──────┘
│
┌──────▼──────┐
┌─────│ executor.c │─────┐
│ └─────────────┘ │
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ builtins.c │ │ pipeline.c │ fork/exec,
│ │ │ │ pipe(), waitpid()
└──────────────┘ └──────┬──────┘
│
┌──────▼──────┐
┌─────│ redirect.c │
│ └─────────────┘
┌──────▼──────┐
│ jobs.c │ Background process tracking
└─────────────┘
| Module | Lines | Responsibility |
|---|---|---|
shell.h |
116 | Core types: Token, Command, Pipeline, Job, ShellState |
main.c |
223 | Entry point, safe allocators, variable/tilde expansion |
repl.c |
373 | REPL loop, raw terminal mode, line editor with history nav |
lexer.c |
151 | Tokenizer — handles "quotes", 'literals', \escapes, operators |
parser.c |
191 | Builds Pipeline/Command structs from token stream |
executor.c |
28 | Orchestrates pipeline execution across semicolons |
pipeline.c |
161 | fork()/execvp(), pipe plumbing, process groups |
builtins.c |
206 | 9 built-in commands (cd, exit, help, history, jobs, fg, bg, export, unset) |
redirect.c |
58 | dup2() based file descriptor redirection |
signals.c |
76 | Signal disposition for shell vs child processes |
jobs.c |
187 | Job table management, foreground/background control |
history.c |
104 | Persistent command history with file I/O |
~2000 lines of C — no external dependencies, pure POSIX.
| Category | System Calls |
|---|---|
| Process | fork(), execvp(), waitpid(), _exit(), setpgid(), getpid() |
| Pipes | pipe(), dup2(), close() |
| File I/O | open(), read(), write(), fopen(), fgets() |
| Signals | sigaction(), sigprocmask(), kill(), signal() |
| Terminal | tcsetpgrp(), tcgetattr(), tcsetattr(), isatty() |
| Environment | getenv(), setenv(), unsetenv(), getcwd(), chdir() |
- No readline/ncurses — line editing and raw terminal handling implemented from scratch to demonstrate low-level terminal I/O
- C11 standard — modern C with designated initializers and strict compilation flags
- Defensive memory management —
xmalloc/xstrdupwrappers that abort on failure; every allocation has a matching free - Process group isolation — each pipeline gets its own process group for correct signal delivery and job control
- SIGCHLD blocking — foreground pipelines block SIGCHLD to prevent the async handler from racing with
waitpid()
24 integration tests covering:
- Built-in commands (cd, exit, help, export/unset, $?)
- Multi-stage pipes
- I/O redirection (input, output, append, stderr)
- Background jobs
make testMIT — see LICENSE.