I started working on shed because I have yet to find an unopinionated shell with genuinely smooth out-of-the-box line editing features. bash and zsh are both POSIX compliant in their syntax, but bash's readline and zsh's zle are both really clunky to work with (in my opinion). fish has pretty decent line editing, but wants me to learn their scripting language instead of the one that everyone else uses. There just wasn't a perfect solution. shed is the answer I came up with for this problem.
shed includes a built-in modal line editor, written from scratch. It supports both vim and emacs editing modes. shed's line editor distinguishes itself by treating multi-line operations as first class. It's effectively a terminal-embedded text editor, rather than a traditional shell line editor.
The default. Supports the usual readline-style bindings (Ctrl+A/Ctrl+E/Ctrl+W, transposition, kill ring, etc.), with multi-line awareness layered on top.
Alt+; provides access to ex mode
Enable with shopt set.vi=true or set -o vi. Implements normal, insert, visual, and replace modes with wide coverage of the standard vi motions and operators, including several vim-specific extensions like g?, and also implements registers and macro recording.
: from normal mode enters ex mode.
A secondary command prompt that has many tools for both controlling the line editor, and executing shell commands without losing the one you are currently working on.
| Command | Description |
|---|---|
:!cmd |
Executes cmd as though it had been submitted from the main prompt. Triggers pre-cmd and post-cmd autocmds. |
:h topic |
Opens the help page relevant to the given topic, e.g. :h variables. |
:stash |
Stores the main editor's buffer, then clears the main editor. :stash pop restores the buffer. |
:normal <keys> |
Runs a normal-mode key sequence. Useful for keymaps. |
:w file / :w !cmd |
Write the current buffer to a file, or pipe to a shell command. e.g. :w !wl-copy to pipe it to your clipboard. |
:r file / :r !cmd |
Read a file into the buffer, or read shell output, e.g. :r !wl-paste to read clipboard contents into the buffer. |
For a comprehensive list, as well as more in-depth stuff like line-addressing, check out the ex mode docs
shed ships with documentation for all of its builtin utilities, unique features, common commands, and POSIX stuff that's easy to forget like parameter expansion. This documentation is accessible via the help builtin.
The help topics are opened in a custom interactive pager that uses a basic hypertext markup language. The content contains links to other topics that can be clicked on, or navigated to by using Tab to show link hints.
Examples:
help params # opens the params page
help cd # opens the builtins page and jumps to the 'cd' entryAdditionally, the help pages can be reached using ex mode like :h some-topic. This allows you to open the pager without losing the command that you're currently editing. Really convenient for when you forget what the tar flags do.
shed comes with fuzzy completion and history searching out of the box.
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 '<leader>a' 'ggVG' # select all with leader + a in normal mode
keymap -n '<C-L>' '<CMD>clear<CR>' # Ctrl+L runs clear in normal mode
keymap -e '<C-O>' '<CMD>my_function<CR>' # Ctrl+O runs a shell function in emacs mode
keymap -n 'ys' '<CMD>function1<CR><CMD>function2<CR>' # Chain two functions together
keymap -v '<C-x>' '<CMD>!wl-copy<CR>' # Ctrl+X cuts the current visual selection to the clipboard
keymap -v '<C-c>' '<CMD>!tee >(wl-copy)<CR>' # Ctrl+C copies the current visual selection to the clipboard
keymap -i '<C-v>' '<CMD>r!wl-paste<CR>' # Ctrl+V in insert mode pastes from the clipboardMode flags: -n normal, -i insert, -v visual, -x ex, -o operator-pending, -r replace, -e emacs. 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 that matches the given key sequence.
Similar to zsh's line editor widgets, shell commands run via keymaps have read-write access to the line editor state through special variables:
$BUFFER- Current line contents$CURSOR- Cursor position, can be written back as either a raw byte index or as 'row:col'$ANCHOR- Visual selection anchor$KEYS- Keys that the line editor will execute upon returning. This can be used to script arbitrary input.
Modifying these variables from within the command updates the editor when it returns.
The autocmd builtin registers shell commands to run on specific events. Many events expose context variables that autocmds can use for conditional logic:
autocmd post-change-dir 'echo "moved to $NEW_DIR"'
autocmd on-exit 'echo goodbye'
autocmd on-time-report 'echo "$TIME_CMD took $TIME_REAL_FMT"'Available events:
| Event | When it fires |
|---|---|
pre-cmd, post-cmd |
Before/after command execution |
pre-change-dir, post-change-dir |
Before/after cwd changes |
pre-prompt, post-prompt |
Before/after prompt display |
pre-mode-change, post-mode-change |
Before/after editor mode switch |
on-history-open, on-history-close, on-history-select |
History search events |
on-completion-start, on-completion-cancel, on-completion-select |
Tab completion events |
on-job-finish |
Background job completes |
on-time-report |
time-prefixed command completes |
on-exit |
Shell is exiting |
Use -c to clear all autocmds for an event. Context variables (e.g. $NEW_DIR, $TIME_REAL_MS) are scoped to the autocmd execution and documented in help autocmd.
shed uses an sqlite database to store your command history. While this is slightly heavier than the usual flat text file approach used by shells like bash and zsh, it has some advantages:
- Shared across sessions: All open
shedinstances read from and write to the same history in real time - commands entered in one terminal are immediately available in all the others viahist --pullorCtrl + Rhistory searching. - Queryable: Power users can query the database directly with any SQLite tool for custom analysis, rather than needing a custom history file parser
- Richer metadata: Each entry stores timestamp, working directory, runtime duration in milliseconds, and exit code.
- Safe writes: SQLite's transaction model means a hard kill mid-write won't leave your history file in a broken state.
- Direct access via
hist: Thehistbuiltin allows you to interact with the database directly, and exposes flags that can be composed to create pseudo-SQL queries, e.g.hist --starts-with 'echo' --after '10 minutes ago' --deletewill delete all commands starting with echo that were entered within the last 10 minutes.
Additionally, shed implements a unique feature for interacting with your history. Consecutive commands can be concatenated with ; or && as separators if you scroll with Ctrl or Shift respectively. Useful if you need to re-run a batch of commands.
shed supports fish-style alias expansion on the prompt. When enabled (shopt prompt.expand_aliases=true, the default), aliases expand visually as you type. Press space or enter after an alias and the real command appears in the buffer before execution. This lets you see and edit the expanded form before running it. Useful for keeping your command history readable.
Expansion only applies to words in command position (not arguments).
shed's syntax highlighter is fully configurable through shopt highlight.*:
shopt highlight.valid_command="bold green"
shopt highlight.string="yellow"
shopt highlight.variable="#89b4fa"
shopt highlight.comment="italic bright black on white"
shopt highlight.operator="bold magenta"Style descriptions support named colors, bright variants, modifiers (bold, italic, underline, dim, strikethrough), hex colors (#rrggbb), and backgrounds with on. Raw ANSI escapes are also accepted.
shed provides an API for implementing your own status line, using the shopt statline.* options:
shopt statline.enable=true # enables the status line, anchors the prompt to it
shopt statline.left_string="\u@\h"
shopt statline.middle_string='$?'
shopt statline.right_string=""The status line strings expand prompt escape sequences.
An example implementation of a shed status line can be found here.
shed exposes a Unix socket that other processes can use to interact with it. The path to this socket is held per-instance in the $SHED_SOCK environment variable. Subscribing to the socket gives you a stream of event data, and there are several requests that can be written to the socket to control shed in various ways.
Among other things, it's possible to read from and write to the line editor directly via the socket. This enables total extensibility of the editor by anything that can interact with a Unix socket. The remote editing mode causes input keys to be broadcast over the socket, to be consumed by subscribers that can use those inputs to control the editor remotely.
More info can be found in the socket help page.
shed's scripting language follows the specification laid out by IEEE Std 1003.1-2024 Shell & Utilities.
It is capable of sourcing any POSIX-portable shell script, or I'll eat my hat.
shed implements the usual shell job control utilities.
- Background execution with
& - Suspend foreground jobs with Ctrl+Z
fg,bg,jobs,disownwith flags (-l,-p,-r,-s,-h,-a)
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 line.highlight=false # toggle syntax highlighting
shopt set.vi=true # editor mode
shopt core.max_hist=5000 # history size
shopt highlight.valid_command="bold green" # customize highlight colorsThe rc file is loaded from ~/.shedrc on startup.
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 |
\c{desc} |
Named color styling (e.g. \c{bold green}, \c{#ff5733}, \c{reset}) |
\@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 \$ 'If shed receives SIGUSR1 while in interactive mode, it will refresh and redraw the prompt. This can be used to create asynchronous, dynamic prompt content.
Additionally, echo has a -p flag that expands prompt escape sequences. This provides an interface for accessing the information provided by these escape sequences from any context. For instance, using \c{...} instead of raw ANSI codes:
echo -p '\c{bold green on black}Build succeeded\c{reset} in \T'shed also provides a PSR for expanding content that is justified to the right side of the prompt.
yay -S shed-shOr your favorite AUR helper (paru -S shed-sh, etc).
Requires Rust (edition 2024).
git clone https://github.com/km-clay/shed.git
cd shed
cargo build --releaseThe binary will be at target/release/shed.
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
];
};- The expanded content from the
PSRvariable doesn't work well with multi-line content - The line editor hasn't been optimized for very large buffers yet (3000+ lines or so), so its pretty slow/unpredictable with those.
- Aliases can't be used in the same script that defines them.
AI has been used to assist with development in some areas of this codebase. Full disclosure can be found here: AI_POLICY.md.
shed is experimental software and is currently under active development. Using an experimental shell is inherently risky business, there is no guarantee that your computer will not explode when you run this. That being said, I've been daily driving it for 8 months at the time of writing and my computer has not exploded yet. Use it at your own risk, the software is provided as-is.

