The Interactive Shell
ps-bash ships two things: a module you import into PowerShell (Import-Module PsBash),
and a standalone interactive shell (ps-bash) — a full REPL that reads bash, runs it on a
warm PowerShell runspace, and gives you the line-editing, history, completion, and prompt
experience a bash user expects. This page documents the interactive shell.
# Launch the interactive shellps-bashLine editing
Section titled “Line editing”The line editor is a VT100 editor with emacs-style keybindings, a kill ring, and a persistent, CWD-aware history.
| Key | Action |
|---|---|
← / → | Move cursor one character (← / →; → also accepts an autosuggestion at end of line) |
Ctrl-A / Home | Jump to start of line |
Ctrl-E / End | Jump to end of line (also accepts an autosuggestion) |
Alt-B / Alt-F | Move back / forward one word |
Backspace / Delete | Delete character left / right |
Ctrl-K | Kill to end of line (text → kill ring) |
Ctrl-U | Kill to start of line (text → kill ring) |
Ctrl-W | Kill the word before the cursor (text → kill ring) |
Ctrl-Y | Yank (paste) the kill ring at the cursor |
Ctrl-D | Delete forward; on an empty line, exit the shell (EOF) |
↑ / ↓ | Previous / next command in history (CWD-filtered by default) |
Ctrl-R | Reverse history search (full-screen — see below) |
Tab | Complete; press again to cycle matches |
F1 | Open the man-page flag browser for the command under the cursor |
Ctrl-L | Clear the screen |
Ctrl-C | Cancel the current line, echo ^C, redraw a fresh prompt |
Enter | Submit |
Reverse history search (Ctrl-R)
Section titled “Reverse history search (Ctrl-R)”Ctrl-R opens a full-screen, fuzzy, ranked search over your command history — closer to
fzf/atuin than bash’s one-line (reverse-i-search).
- Type to filter. Matching is incremental and ranked: exact > prefix > substring > fuzzy subsequence, with a score that boosts commands run in the current directory, run recently, and run often.
- Rich rows. Each result shows the command, the directory it ran in (
~-shortened), a relative time (2m ago), the exit code (green0/ red non-zero), and its history id. Duplicate command lines are collapsed to the best-ranked occurrence.
| Key | Action |
|---|---|
Type / Backspace | Edit the query; results re-filter live |
Ctrl-R / Ctrl-S | Cycle to the next / previous match (wraps) |
↑ / ↓ | Move the selection up / down |
PgUp / PgDn | Page the selection |
Home / End | Jump to the first / last result |
Ctrl-G | Toggle between current-directory and all-history scope |
Tab | Edit the selected command in place before running |
Enter | Run the selected command |
Esc / Ctrl-C | Cancel, leaving your original line untouched |
If a current-directory search returns nothing, it automatically falls back to global history.
Inline autosuggestions
Section titled “Inline autosuggestions”As you type, the shell suggests the most recent matching history command as gray ghost text (fish-style) after the cursor.
- The suggestion is the suffix needed to complete the match; it appears only when the cursor is at the end of the line and a match exists.
- Accept it with
→(Right arrow),End, orCtrl-E. Any other key dismisses it and edits normally. - Suggestions prefer commands from the current directory, then fall back to global history.
Tab completion
Section titled “Tab completion”Completion is context-aware. The engine composes a static base set with live queries against the runspace, and every runspace round-trip is bounded by a 200 ms deadline — Tab never hangs.
| You’re typing… | Tab completes with… |
|---|---|
| The first word | Command names — bash commands ∪ live Get-Command results ∪ your aliases |
- after a bash command | That command’s flags (from the bundled flag specs) |
- after a PowerShell cmdlet | The cmdlet’s parameter names (live introspection) |
A value after -Param | Parameter values — [ValidateSet], enum names, or anything a Register-ArgumentCompleter provides |
An argument of a complete-registered command | That command’s word list (see below) |
A pattern after grep -e | A small set of regex snippets matched to the dialect (-E/-F) |
| Anything else | File paths |
Floating flag panel + man-page browser
Section titled “Floating flag panel + man-page browser”You don’t have to press Tab to discover flags. As you type a flag token (find -), a dim panel
appears below the prompt listing the matching flags and their descriptions — IDE-style parameter
help that updates every keystroke. For PowerShell cmdlets it shows each parameter with its type
and any ValidateSet/enum values (tnc -C → -CommonTCPPort <String> HTTP, RDP, SMB, WINRM).
- Press
↓to focus the panel, then↑↓/PgUp/PgDnto scroll andEnterto insert the selected flag.Escreturns to typing. - Press
F1(at the prompt) or→(on a focused panel row) to open a scrollable man-page browser for the command’s flags — description, detail paragraph, and examples.↑↓switches flags,PgUp/PgDn(orj/k) scrolls,Enterinserts,Esc/qcloses.
Programmable completion (complete)
Section titled “Programmable completion (complete)”Bash’s complete is supported for static word lists (Tier 1):
complete -W 'start stop restart status' svcsvc st⇥ # → start, status, stopcomplete -p # print registered specscomplete -r svc # removeAliases
Section titled “Aliases”alias and unalias work with full bash syntax in the value — including pipes and redirects —
because the shell expands the alias before transpiling, so the expansion is seen as ordinary
bash.
alias ll='ls -la'alias gs='git status | head -20' # pipes are fine in shell modealias # list all (also: alias -p)alias ll # show oneunalias gs # remove one (also: unalias -a)Aliases defined in your startup file (~/.psbashrc) and via interactive source FILE land in the
same table the completion engine reads, so they’re immediately tab-completable.
History expansion (bang commands)
Section titled “History expansion (bang commands)”The shell expands bash “bang” history references on the typed line, before alias expansion — matching bash’s order. The expanded line is echoed so you see what actually ran.
| Form | Expands to |
|---|---|
!! | The previous command |
!n | Command number n in this session (1-based) |
!-n | The n-th command back (!-1 == !!) |
!str | The most recent command starting with str |
!?str? | The most recent command containing str |
!$ / !^ / !* | Last / first / all argument words of the previous command |
!ev:$ :^ :* :n | A word designator applied to any event |
^old^new | Quick substitution on the previous command (first match) |
git commit -m wipsudo !! # → sudo git commit -m wipcat !$ # → cat wip (last arg of previous command)^wip^done # → git commit -m doneExpansion is suppressed inside single quotes and after a backslash, and a ! followed by a
space, =, (, or end-of-line stays literal — so the ! cmd negation operator and a != b
comparisons are untouched. Expansion does happen inside double quotes (bash semantics). A failed
reference prints ps-bash: !x: event not found and does not run.
Persistent history
Section titled “Persistent history”Every command is recorded in a SQLite database at ~/.psbash/history.db (override the home dir
with PSBASH_HOME). Each row stores the typed command, the working directory, exit code, duration,
timestamp, and a per-session id — which is what powers the CWD-aware ranking in Ctrl-R, the
arrow-key navigation, and the inline autosuggestions. Consecutive duplicates are de-duplicated and
the store uses WAL mode for safe concurrent access.
Directory navigation: cd and $OLDPWD
Section titled “Directory navigation: cd and $OLDPWD”cd updates $env:PWD, [System.Environment]::CurrentDirectory, and PowerShell’s location
together, and records the directory you’re leaving into $env:OLDPWD on every successful cd.
| Command | Behavior |
|---|---|
cd / cd ~ | Change to $HOME |
cd ~/path | Change relative to home |
cd - | Return to the previous directory ($OLDPWD) and echo it, like bash |
cd nope | cd: nope: No such file or directory, exit code 1 |
cd /var/logcd /etccd - # → /var/log (printed), and another `cd -` toggles backIf $OLDPWD is unset, cd - reports cd: OLDPWD not set instead of failing on an empty path.
The tilde forms ~+ ($PWD) and ~- ($OLDPWD) are also recognized.
Prompt customization and hooks
Section titled “Prompt customization and hooks”ps-bash bridges bash’s prompt model onto PowerShell’s prompt function.
PROMPT_COMMAND
Section titled “PROMPT_COMMAND”A bash PROMPT_COMMAND="cmd" assignment is translated into a registered prompt hook that fires on
every prompt. Appends (PROMPT_COMMAND+="other") register additional hooks, preserving bash’s
additive semantics; unset PROMPT_COMMAND unregisters it.
Prompt + chpwd hooks
Section titled “Prompt + chpwd hooks”You can register hooks directly (these work in module mode too):
# Run on every promptRegister-BashPromptHook -Name 'clock' -ScriptBlock { $host.UI.RawUI.WindowTitle = (Get-Date) }
# Run when the directory changes — receives $OldPath, $NewPathRegister-BashChpwdHook -Name 'direnv' -ScriptBlock { direnv export pwsh | Invoke-Expression }Register-BashChpwdHook -Name 'fnm' -ScriptBlock { fnm use --silent-if-unchanged }
Get-BashHook # list registered hooksUnregister-BashChpwdHook -Name 'fnm'chpwd hooks fire between prompts when $PWD changes, run in the global process scope (so
$env: and $global: mutations persist), fire in name-sorted order, and a throwing hook is
caught (the error is appended to $global:BashHookErrors, never crashing the shell).
Coexisting with oh-my-posh, Starship, and PSReadLine
Section titled “Coexisting with oh-my-posh, Starship, and PSReadLine”ps-bash installs its prompt as a wrapper: it stores any pre-existing prompt function in
$global:__BashOriginalPrompt, runs the hooks, then calls the original. This means oh-my-posh and
Starship prompts render fine alongside ps-bash hooks. Tools driven by PROMPT_COMMAND or directory
changes — direnv, fnm, nodenv/rbenv, pyenv-virtualenv, zoxide — work through the prompt
and chpwd hooks.
Shell options and traps
Section titled “Shell options and traps”| Construct | Maps to |
|---|---|
set -e / set -o errexit | $ErrorActionPreference = 'Stop' (+ an errexit guard) |
set -x / set -o xtrace | Set-PSDebug -Trace 1 |
set -u / set -o nounset | Set-StrictMode -Version Latest |
set -- a b c | Reset positional parameters |
trap '…' ERR | Fires the handler when a command returns non-zero |
Combined flags like set -euo pipefail are decomposed (pipefail has no PowerShell equivalent and
is ignored — see the pipeline exit-code note). trap '…' DEBUG has no faithful
mapping (no pre-command event in PowerShell) and is a documented gap.
AI command assist (Ctrl-^) opt-in
Section titled “AI command assist (Ctrl-^) ”Press Ctrl-^ (also bindable as Ctrl-backtick or Ctrl-6) to send your current buffer to an external AI CLI and
get back a bash command. The shell shows the suggestion, flags risky patterns (rm, git push --force, sudo, curl … | sh, package installs, Invoke-Expression), and asks what to do:
execute (a flagged command requires typing EXECUTE to confirm), insert into the buffer for
editing, retry, switch providers, or cancel. Your original line is restored on cancel or error.
- Default provider is the
claudeCLI (claude -p "{{prompt}}"); configure others in~/.psbash/ai-providers.json(or$PSBASH_AI_CONFIG). - Prompts are redacted for obvious secrets (
API_KEY=…→<redacted>) and the buffer/cwd are truncated before sending. - Disable the feature entirely with
PSBASH_AI_DISABLE=1.
Startup file
Section titled “Startup file”On launch the shell sources ~/.psbashrc (override with PSBASH_HOME; skip with --no-profile).
alias/unalias/complete lines are applied to the interactive tables; the rest is transpiled
and run. This is the same path used by interactive source FILE, so sourced aliases and completions
are immediately live.
alias ll='ls -la'alias gl='git log --oneline | head -20'complete -W 'start stop restart status' svcexport EDITOR=code