DEV Community

Cover image for Building a Unix Shell from First Principles in Rust
Shinji Ito
Shinji Ito

Posted on

Building a Unix Shell from First Principles in Rust

I’ve been working on a project called Whelk, a Unix shell built from first principles in Rust.

The goal wasn’t to build another Bash replacement.

I wanted to understand what actually happens underneath a shell — from parsing a command line to creating processes, connecting pipes, handling signals, managing jobs, and interacting with the terminal.

The project is intentionally small enough to understand, but deep enough to expose how Unix actually works.

What I Built

Whelk currently covers quite a lot of the machinery involved in a real interactive shell:

  • Command parsing with a lexer and AST
  • Quoting, escaping, and useful error locations
  • Environment and variable expansion
  • Input/output/error redirection
  • Concurrent pipelines
  • Process creation and waiting
  • Signal handling
  • Process groups
  • Job control with jobs, fg, and bg
  • Terminal mode management and resizing
  • History and command completion
  • An event loop using epoll on Linux and kqueue on BSD/macOS
  • Benchmarks and allocation-oriented regression tests

The project is split into relatively small Rust crates so that the boundaries between parsing, execution, processes, jobs, terminals, and events remain visible.

What I Learned

The biggest lesson has been that systems programming is often about understanding boundaries.

A shell looks simple from the outside:

command → process → output
Enter fullscreen mode Exit fullscreen mode

But underneath, there are many moving parts.

Pipelines involve multiple processes communicating through file descriptors.

Job control requires understanding process groups and terminal ownership.

Signals introduce asynchronous behavior.

Terminal interaction requires changing terminal modes and restoring them correctly.

Even something as simple as pressing Ctrl-Z involves much more than detecting a key.

Building these pieces myself forced me to understand the operating system rather than treating it as an abstraction that simply works.

Rust Made the Project Interesting

Rust has been particularly useful for this kind of project.

The shell inevitably has places where it needs to interact directly with operating-system primitives, including fork, exec, file descriptors, signals, and terminal operations.

Those boundaries require unsafe code, but I’ve tried to keep the unsafe parts concentrated in the process and OS-facing layer.

That creates an interesting design constraint:

How much of the system can remain safe Rust while still exposing low-level Unix behavior?

That question has influenced the architecture quite a bit.

Performance Was Another Lesson

I initially expected parsing and command processing to be the interesting performance problems.

The benchmarks showed otherwise.

For example, parsing a pipeline is around microseconds, while starting a process is hundreds of microseconds on the same benchmark setup.

That changes how I think about optimization.

If process creation dominates the cost, spending a huge amount of effort shaving a few nanoseconds from parsing isn't particularly meaningful.

The benchmarks also uncovered a feature with a much larger cost than I expected — exactly the kind of thing that is easy to miss when you only optimize based on intuition.

Why I’m Building It

The main reason is simple:

I want to understand the systems underneath the software I use every day.

High-level abstractions are incredibly useful, but occasionally going one layer deeper is valuable.

Building a shell forced me to learn about processes, file descriptors, signals, terminals, scheduling boundaries, event notification, and the Unix process model in a much more concrete way.

And that's what I enjoy about projects like this.

The final application isn't necessarily the most important result.

The understanding you gain while building it is.

Whelk is still evolving, and there are intentionally many things it doesn't try to implement. It's not intended to become a drop-in replacement for Bash.

It's a learning project, an engineering experiment, and a way for me to make the operating system's process model visible through code.

The source is available on GitHub:

https://github.com/ferris007/whelk

Top comments (0)