A lightweight, POSIX-aligned command-line interpreter (shell) built from scratch in Rust. Features a custom state-machine tokenizer supporting nested quoting, dynamic executable path resolution, process lifecycle management, and native built-in utility commands.
Designed and implemented to explore low-level systems programming concepts: lexical analysis, process spawning (fork/exec semantics), file system interaction, and standard I/O multiplexing in a memory-safe environment without garbage collection overhead.
The shell follows a classic REPL (Read-Eval-Print Loop) architecture structured into modular stages:
flowchart TD
A["User Input (stdin)"] --> B["REPL Loop (main)"]
B --> C["Lexical Tokenizer<br/>parse_arguments()"]
C -->|Token Stream: Program + Args| D{"Command Dispatcher"}
D -->|Builtin Command| E["Internal Builtin Router"]
D -->|External Command| F["PATH Resolution (which)"]
E --> E1["cd (with ~ / $HOME expansion)"]
E --> E2["pwd (env::current_dir)"]
E --> E3["echo (quote-stripped string)"]
E --> E4["type (symbol reflection)"]
E --> E5["exit (graceful termination)"]
F -->|Found| G["Process Spawning<br/>std::process::Command"]
F -->|Not Found| H["Command Not Found Error"]
G --> I["Child Process Execution & Exit Code Capture"]
E1 --> J["Prompt Return ($ )"]
E2 --> J
E3 --> J
E4 --> J
E5 --> K["Shell Terminated"]
H --> J
I --> J
- Quote-Aware Parsing: Hand-rolled deterministic finite-state scanner (
parse_arguments) that differentiates between single quotes ('...'), double quotes ("..."), and unquoted whitespace delimiters. - Literal Preservation: Properly preserves literal characters, internal whitespace, and empty strings within quotes while stripping surrounding delimiter quotes.
- Memory Efficiency: Utilizes
std::mem::taketo extract token buffers without redundant heap reallocations.
Executes internal commands directly within the shell process to preserve shell state and avoid unnecessary process fork overhead:
cd: Changes working directory viastd::env::set_current_dir, featuring dynamic tilde (~) expansion resolving to$HOME.pwd: Displays the canonical current working directory.echo: Formats and prints tokenized arguments separated by single spaces.type: Inspects command names and reflects whether a given symbol is an internal shell builtin or maps to an executable on the filesystemPATH.exit: Gracefully tears down the REPL session with zero exit status.
- Subprocess Spawning: Dispatches external binaries using Rust's
std::process::Command, executing child processes with inherited stdin/stdout/stderr streams. - PATH Lookup: Integrates path lookup to dynamically discover binaries across system
$PATHentries. - Robust Error Handling: Differentiates between non-existent binaries (
ErrorKind::NotFound) and execution/permission failures, emitting clean POSIX diagnostics.
- Rust Toolchain: Rust 1.80+ (2024 edition) and Cargo.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Clone the repository
git clone https://github.com/<your-username>/rush.git
cd rush
# Build in release mode
cargo build --release
# Run the shell
cargo run --release$ echo "Hello world"
Hello world
$ type cd
cd is a shell builtin
$ type ls
ls is /bin/ls
$ pwd
/Users/developer/rush
$ cd ~
$ pwd
/Users/developer
$ ls -la
total 64
drwxr-xr-x 12 developer staff 384 Oct 8 16:00 .
drwxr-xr-x 6 root admin 192 Sep 20 10:14 ..
...
$ non_existent_cmd
non_existent_cmd: command not found
$ exit- I/O Redirection: Implement file descriptor manipulation for standard streams (
>,>>,2>,<). - Pipeline Execution (
|): Support chaining arbitrary child processes using Unix anonymous pipes (pipe(2)). - Signal Handling: Integrate
signal-hook/tokiofor cleanSIGINT(Ctrl+C) andSIGTSTP(Ctrl+Z) interception without killing the parent shell. - Environment Variable Expansion: Support
$VARsubstitution directly in the tokenizer. - Autocompletion & Line Editing: Integrate
rustylinefor history navigation, reverse search, and tab completion.