Rust · from scratch · no shell crates
Conch is a shell in Rust built from a hand-written tokenizer up through a pipeline parser and real process wiring. Every stage of a command line, from characters to a running pipeline, fits in a codebase small enough to actually read.
Type a command line into the box, or click a preset chip. As you type, the real tokenizer and pipeline parser (ported line for line from src/lexer.rs and src/parser.rs) run in your browser and show the token stream and the parsed pipeline of stages, arguments, and redirects. Try pipes and < > >> redirects to see how each is grouped.
this is the real tokenizer and pipeline parser, ported line for line from src/lexer.rs and src/parser.rs, running client-side as you type
The same three passes, this time spawning real processes and returning real exit codes. Captured from the release build.
A command line becomes a pipeline in three readable passes, each one a single small file.
Words, single quotes, double quotes with escapes, backslash escapes, pipes, and redirects become a flat token stream. A token cap rejects hostile input before it grows unbounded.
Tokens split on pipes into stages, each stage becomes a command with its arguments and redirects. A depth cap stops a pathological chain of pipes from looping the parser.
Each stage spawns with std::process::Command, its stdin and stdout wired to the previous and next stage's pipes, with file redirects opened where the pipeline says to open them.
The five builtins that only make sense running inside the shell's own process, since a child process cannot change its parent's working directory or environment.
Spawned with std::process::Command, stdin and stdout wired between pipeline stages, waited on in order, exit code passed back like any real shell.
A parse error, an unknown command, and a redirect failure are three distinct typed errors, never a panic on bad input.
Run interactively with a prompt, or run a single line with -c "command" and exit, both capped on input length.
Conch is not trying to replace your daily shell. It is trying to be a shell you can read, with the design choices that come from that goal.
The only dependency is clap, for argument parsing. The tokenizer, the pipeline parser, and the process wiring are hand-written across four small modules, so nothing important happens in code you cannot open.
A token cap of 4096, a pipeline depth cap of 512, and a one MiB per-line cap reject pathological input as it is read, before it can grow a token stream or a command list without bound.
One ShellError enum covers parse, unknown command, I/O, and limit failures. Every fallible function returns a Result, so malformed input comes back as a value, it never unwinds the process.
parse returns a plain Pipeline data structure with no process handles in it. Wiring stdin, stdout, and redirects between stages is a separate pass in exec.rs, kept apart from parsing.
The same parser and process wiring behind every interface the crate ships.
conch -c "command" runs a single line and exits, through the same parse and run_pipeline path as the REPL.
conch with no arguments starts a prompt and reads a line at a time, each capped at one MiB before it reaches the tokenizer.
lexer, parser, exec, and error are separate modules in the crate, each usable and testable on its own.
cargo test covers quoting and escaping, building the right pipeline for a | b | c and for redirects, cd changing the directory, export reaching a child, unknown commands returning a typed error, and malformed input never panicking.
The same pipeline the demo above just parsed, actually spawning processes.
# one-shot mode conch -c "cat file.txt | grep foo | sort > out.txt" # interactive REPL conch