pagedmov 9bd9c66b92 implemented '<>' redirects, and the 'seek' builtin
'seek' is a wrapper around the lseek() syscall

added noclobber to core shopts and implemented '>|' redirection syntax

properly implemented fd close syntax

fixed saved fds being leaked into exec'd programs
2026-03-14 20:04:20 -04:00
2026-02-27 11:08:42 -05:00

shed

A Linux shell written in Rust. The name is a nod to the original Unix utilities sh and ed. It's a shell with a heavy emphasis on smooth line editing.

shed

Features

Line Editor

shed includes a built-in vim emulator as its line editor, written from scratch. It aims to provide a more precise vim-like editing experience at the shell prompt than conventional vi mode implementations.

  • 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 and visual line 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.


I Can't Believe It's Not fzf!

shed comes with fuzzy completion and history searching out of the box. It has it's own internal fuzzyfinder implementation, so fzf is not a dependency.

shed_comp shed_search

Keymaps

The keymap builtin lets you bind key sequences to actions in any editor mode:

keymap -i 'jk' '<Esc>'                           # exit insert mode with jk
keymap -n '<C-L>' '<CMD>clear<CR>'               # Ctrl+L runs clear in normal mode
keymap -i '<C-O>' '<CMD>my_function<CR>'         # Ctrl+O runs a shell function
keymap -n 'ys' '<CMD>function1<CR><CMD>function2<CR>' # Chain two functions together
keymap -nv '<Leader>y' '"+y'                     # Leader+y yanks to clipboard

Mode flags: -n normal, -i insert, -v visual, -x ex, -o operator-pending, -r replace. Flags can be combined (-ni binds in both normal and insert). The leader key can be defined using shopt prompt.leader=<some_key>.

Keys use vim-style notation: <C-X> (Ctrl), <A-X> (Alt), <S-X> (Shift), <CR>, <Esc>, <Tab>, <Space>, <BS>, arrow keys, etc. <CMD>...<CR> executes a shell command inline.

Use keymap --remove <keys> to remove a binding.

Shell commands run via keymaps have read-write access to the line editor state through special variables: $_BUFFER (current line contents), $_CURSOR (cursor position), $_ANCHOR (visual selection anchor), and $_KEYS (inject key sequences back into the editor). Modifying these variables from within the command updates the editor when it returns.


Autocmds

The autocmd builtin registers shell commands to run on specific events:

autocmd post-change-dir 'echo "now in $PWD"'
autocmd on-exit 'echo goodbye'
autocmd pre-cmd -p 'sudo' 'echo "running with sudo"'

Available events:

Event When it fires
pre-cmd, post-cmd Before/after command execution
pre-change-dir, post-change-dir Before/after cd
pre-prompt, post-prompt Before/after prompt display
pre-mode-change, post-mode-change Before/after vi mode switch
on-history-open, on-history-close, on-history-select History search UI events
on-completion-start, on-completion-cancel, on-completion-select Tab completion events
on-job-finish Background job completes
on-exit Shell is exiting

Use -p <pattern> to filter by regex, and -c to clear all autocmds for an event. The pattern matched by -p changes by context, and not all autocmds have a pattern to match.


Shell Language

shed's scripting language contains all of the essentials.

  • Control flow - if/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 attributes - export, 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

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).

git clone https://github.com/km-clay/shed.git
cargo build --release

The binary will be at target/release/shed.

Nix

A flake is provided with a NixOS module, a Home Manager module, and a simple overlay that adds pkgs.shed.

# 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 ];

And the overlay:

pkgs = import nixpkgs {
	overlays = [
		shed.overlays.default
	];
};

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. There is no guarantee that your computer will not explode when you run this. Use it at your own risk, the software is provided as-is.

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 actually feels like you're in an editor.

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