shed

A Unix shell written in Rust. The name is a nod to the two oldest Unix utilities — sh and ed — reflecting the shell's emphasis on a capable built-in line editor alongside standard shell functionality.

Features

Line Editor

shed includes a built-in vim emulator as its line editor, written from scratch — not a readline wrapper or external library. It aims to provide a more precise vim-like editing experience at the shell prompt.

  • Normal mode — motions (w, b, e, f, t, %, 0, $, etc.), verbs (d, c, y, p, r, x, ~, etc.), text objects (iw, aw, i", a{, is, etc.), registers, . repeat, ;/, repeat, and counts
  • Insert mode — insert, append, replace, with Ctrl+W word deletion and undo/redo
  • Visual mode — character-wise selection with operator support
  • Real-time syntax highlighting — commands, keywords, strings, variables, redirections, and operators are colored as you type
  • Tab completion — context-aware completion for commands, file paths, and variables

Prompt

The prompt string supports escape sequences for dynamic content:

Escape Description
\u Username
\h, \H Hostname (short / full)
\w, \W Working directory (full / basename, truncation configurable via shopt)
\$ $ for normal users, # for root
\t, \T Last command runtime (milliseconds / human-readable)
\s Shell name
\e[... ANSI escape sequences for colors and styling
\!name Execute a shell function and embed its output

The \! escape is particularly useful — it lets you embed the output of any shell function directly in your prompt. Define a function that prints something, then reference it in your prompt string:

gitbranch() { git branch --show-current 2>/dev/null; }
export PS1='\u@\h \W \!gitbranch \$ '

Additionally, echo now has a -p flag that expands prompt escape sequences, similar to how the -e flag expands conventional escape sequences.

Shell Language

shed's scripting language contains all of the essentials.

  • Control flowif/elif/else, for, while, until, case with pattern matching and fallthrough
  • Functions — user-defined with local variable scoping, recursion depth limits, and return
  • Pipes and redirections|, |& (pipe stderr), <, >, >>, << (heredoc), <<< (herestring), fd duplication (2>&1)
  • Process substitution<(...) and >(...)
  • Command substitution$(...) and backticks
  • Arithmetic expansion$((...)) with +, -, *, /, %, **
  • Parameter expansion${var}, ${var:-default}, ${var:=default}, ${var:+alt}, ${var:?err}, ${#var}, ${var#pattern}, ${var%pattern}, ${var/pat/rep}
  • Brace expansion{a,b,c}, {1..10}, {1..10..2}
  • Glob expansion*, ?, [...] with optional dotglob
  • Tilde expansion~ and ~user
  • Logical operators&&, ||, & (background)
  • Test expressions[[ ... ]] with file tests, string comparison, arithmetic comparison, and regex matching
  • Subshells(...) for isolated execution
  • Variable attributesexport, local, readonly

Job Control

  • Background execution with &
  • Suspend foreground jobs with Ctrl+Z
  • fg, bg, jobs, disown with flags (-l, -p, -r, -s, -h, -a)
  • Process group management and proper signal forwarding

Builtins

echo, cd, pwd, read, export, local, readonly, unset, source, eval, exec, shift, alias, unalias, trap, jobs, fg, bg, disown, pushd, popd, dirs, shopt, builtin, command, return, break, continue, exit, true, false, :

Configuration

Shell options are managed through shopt:

shopt core.autocd=true          # cd by typing a directory path
shopt core.dotglob=true         # include hidden files in globs
shopt prompt.highlight=false    # toggle syntax highlighting
shopt prompt.edit_mode=vi       # editor mode
shopt core.max_hist=5000        # history size

The rc file is loaded from ~/.shedrc on startup.

Building

Cargo

Requires Rust (edition 2024).

cargo build --release

The binary will be at target/release/shed.

Nix

A flake is provided with a NixOS module and a Home Manager module.

# Build and run directly
nix run github:km-clay/shed

# Or add to your flake inputs
inputs.shed.url = "github:km-clay/shed";

To use the NixOS module:

# flake.nix outputs
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
  modules = [
    shed.nixosModules.shed
    # ...
  ];
};

Or with Home Manager:

imports = [ shed.homeModules.shed ];

Status

shed is experimental software and is currently under active development. It covers most day-to-day interactive shell usage and a good portion of POSIX shell scripting, but it is not yet fully POSIX-compliant.

Why shed?

This originally started as an educational hobby project, but over the course of about a year or so it's taken the form of an actual daily-drivable shell. I mainly wanted to create a shell where line editing is more frictionless than standard choices. I use vim a lot so I've built up a lot of muscle memory, and a fair amount of that muscle memory does not apply to vi modes in bash/zsh. For instance, the standard vi mode in zsh does not support selection via text objects. I wanted to create a line editor that includes even the obscure stuff like 'g?'.

Description
No description provided
Readme 130 MiB
Languages
Rust 97.5%
Lua 1.3%
Nix 1.2%