Skip to content
Kyler Clay edited this page Aug 25, 2026 · 1 revision

shed draws its prompt from the $PS1 variable. Before each prompt is shown, PS1 is run through a pass of prompt escape sequences - backslash codes that expand into dynamic content like the working directory, the last command's runtime, or the output of a shell function. There is also a right-hand prompt ($PSR) and a full status line, both of which are expanded the same way.

If $PS1 is unset, shed falls back to a built-in default (a multi-line, colored prompt showing user, host, working directory, and the prompt symbol).

Escape Sequences

Escape Expands to
\u the current username ($USER)
\h the hostname, up to the first .
\H the full hostname
\w the working directory, with $HOME shown as ~
\W the working directory, truncated to its last few segments (see below)
\s the shell name (shed)
\$ # when running as root (uid 0), otherwise $
\t the last command's runtime, in milliseconds
\T the last command's runtime, human-readable (e.g. 1m 3s)
\j the number of currently-running jobs
\n a newline
\r a carriage return
\\ a literal backslash
\" \' a literal quote character
\c{...} a named color/style (see Colors and Styling)
\e[...] a raw ANSI escape sequence
\@name the output of shell function name (see Embedding Output)
\nnn the byte given by up to three octal digits

Note that \W is not just the basename (as it is in bash). It's the working directory trimmed to its last N path segments, where N is set by shopt prompt.trunc_prompt_path (default 4). So in /usr/local/share/man, \w gives /usr/local/share/man and \W gives local/share/man (with the default of 4). Set prompt.trunc_prompt_path lower for a shorter path, higher to show more.

Colors and Styling

There are two ways to color a prompt.

The \c{...} escape takes a style description and emits the matching ANSI code. This is the same grammar used by the highlight.* options, so it accepts named colors, their bright variants, the modifiers bold/italic/underline/dim/strikethrough, hex colors (#rrggbb), and backgrounds via on:

export PS1='\c{bold green}\u\c{reset}@\c{#89b4fa}\h\c{reset} \W \$ '

\c{reset} clears styling back to the terminal default. Because \c{...} is human-readable and doesn't require memorizing escape codes, it's the recommended form.

If you'd rather write escape codes directly, \e[...] passes a raw ANSI sequence straight through:

export PS1='\e[1;32m\u@\h\e[0m \W \$ '

Embedding Command Output

The \@ escape embeds the output of a shell function directly in the prompt. Define a function that prints something, then reference it by name:

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

The function runs on every prompt draw, and its standard output is spliced in where \@ appears. The name runs until the first character that can't be part of an identifier; if you need to butt it up against following text, use the braced form \@{name}:

export PS1='[\@{gitbranch}] \$ '

\@ only invokes functions, not arbitrary commands - so wrap whatever logic you need (pipelines, conditionals, external tools) inside a function and reference that.

Dynamic and Asynchronous Prompts

Two mechanisms let the prompt reflect live state.

Substitution: When shopt prompt.substitute is enabled (the default), the expanded prompt is run through a second pass of variable and command substitution. That means $VAR and $(...) inside PS1 are evaluated fresh each time the prompt is drawn:

export PS1='[$?] \W \$ '          # shows the last exit code
export PS1='$(date +%H:%M) \$ '   # shows the current time

(Disable it with shopt prompt.substitute=false if you want a literal $ in your prompt without escaping it.)

On-demand Redraw: Sending SIGUSR1 to the shell forces it to redraw the prompt. Combined with \@ or $(...), this enables asynchronous prompt content: kick off a slow computation (a git status on a huge repo, say) in the background, have it stash its result somewhere and signal the shell when it's done, and the prompt updates without blocking your typing. The IPC socket's msg and redraw requests (see IPC-Socket) are a convenient way to drive this from a background job.

The Right Prompt

$PSR is a second prompt string, rendered flush against the right edge of the prompt's last line. It's expanded with exactly the same escape sequences as PS1:

export PSR='\c{dim}\T\c{reset}'   # runtime of the last command, on the right

PSR must fit on a single line - if it expands to multiple lines, only the first is used (and a warning is logged) - and it's only drawn when there's room for it alongside the main prompt.

Prompt Options

The prompt-rendering options are in the prompt.* shopt namespace:

Option Effect
prompt.trunc_prompt_path segments shown by \W (default 4)
prompt.substitute run variable/command substitution over the expanded prompt (default true)
prompt.expand_aliases expand aliases visually on the prompt as you type rather than after submitting (default true)

(The prompt.* namespace also holds a few completion-related options - see Configuration for the full list.)

Using Escapes Anywhere

Prompt escapes aren't limited to PS1. The echo builtin's -p flag runs its argument through the same expander, which makes the escape sequences - especially \c{...} and \T - usable from any script or command:

echo -p '\c{bold green on black}Build succeeded\c{reset} in \T'

This is a tidy way to emit colored output without hand-writing ANSI codes.

The Status Line

The status line is an anchored extension of the prompt: a persistent line (enabled with shopt statline.enable=true) that the prompt is drawn against. It's built from three strings, each expanded with the same prompt escapes and substitution as PS1:

shopt statline.enable=true
shopt statline.left_string="\u@\h"
shopt statline.middle_string='$(git branch --show-current)'
shopt statline.right_string='[$?]'

Note: the above example requires shopt prompt.substitute=true to expand the right and middle strings.

Option Position
statline.left_string flush left
statline.middle_string centered
statline.right_string flush right

Because the strings go through full prompt expansion, everything from the escape table works in them - \T, \c{...}, \e[...], \@func - as do $VAR and $(...). A few variables are especially useful here:

  • $SHED_EDIT_MODE - the current line-editor mode (insert, normal, emacs, …)
  • $EDITOR_LINE / $EDITOR_LINES - the cursor's line and the total line count of the buffer
  • the usual $?, $SHLVL, and friends

The shipped defaults use these to show the edit mode, the last command's runtime, the exit code, and the cursor position - a reasonable starting point to crib from. A fuller worked example lives in examples/status_line.sh.

Clone this wiki locally