Unix Shell

A command interpreter written in C.

Design goal

Build a working Unix shell from scratch: parsing, process control, pipes and redirection.

What it handles

Fork and execve, pipes and dup2, input and output redirection including heredocs, signal handling, and built-in commands.

Terminal listing the shell's source files before the refactor (shell0.c to shell23.c) and after (parser.c, execution.c, signals.c and others)
The shell running in a terminal: listing files, printing environment variables, changing directory and echoing text

The rewrite

The first version worked and was unmaintainable — 1,800 lines spread across thirty files with names like shell1.c, shell12.c and shell0_33.c. The second version is 1,265 lines organised by responsibility: parser, execution, builtins, redirection, signals, environment, pipes. Same functionality, half the cognitive load. Splitting it that way meant every file could be reasoned about on its own, and it made the parts I had got wrong visible, because a function in the wrong module is obvious in a way that a function in shell17.c is not.

Main learnings

Process creation and management, file descriptors, signal handling, modular architecture. Rewriting it taught me more than building it did.

Academic project · C, Unix system calls, Make · 2024