tomas-trachta/wbsh

Bash-compatible shell for Windows

C++

7

80 commits

updated Oct 11, 2026

See the code

See what people are saying

SourceMessageScoreDate

wbsh - Bash-compatible shell for Windows (r/commandline)

**wbsh: a minimal bash-compatible shell for Windows (plus a terminal built around it)** Hey all. For a while now I've been working on a shell that lets me run bash commands and scripts natively on Windows. I actually need this at my job, which is what kept me going. Think of it as a minimalistic…

0

Oct 10, 2026

README

wbsh

A Bash-compatible shell for Windows: a drop-in replacement for Git Bash, written from scratch in C++17 with a real POSIX shell grammar, a readline-style line editor, and bundled coreutils so a fresh install is useful immediately.

wbsh running the test suite in Windows Terminal

Status: early but released. Tagged 1.0.x releases exist and a golden test suite runs on every build, but there is no CI yet and behavior may change between minor versions.


Why wbsh

Git Bash is a thin re-skin of MSYS2, and its quirks leak through: PATH translation surprises, slow fork emulation, an aging MinTTY. wbsh is built for modern Windows instead:

  • Native console. VT-mode output and immersive dark mode; renders correctly in Windows Terminal, conhost, VS Code, and JetBrains terminals.
  • One self-contained binary. No MSYS or Cygwin layer. ~1 MB exe, ~2.5 MB installer including the VC++ runtime.
  • Real Bash semantics. Lexer, parser, AST and expander, not a wrapper around cmd.exe.
  • Bundled coreutils. ls, grep, sed, awk, find, tar, curl, hashes and more are built in. System git, vim, less are auto-discovered.
  • Path translation. /c/Users/... ↔ C:\Users\... when invoking native Windows executables.

Install

Installer. Download wbsh-setup-x64.exe from Releases. Per-user, no UAC, installs to %LOCALAPPDATA%\Programs\wbsh, with opt-in tasks to add wbsh to your PATH and an "Open wbsh here" Explorer context-menu entry.

wbshterm. Every release also ships wbshterm-setup-x64.exe, a terminal that hosts wbsh on a pseudoconsole with themes, a config file, scrollback and selection, tmux-style panes, and shell integration. It bundles wbsh.exe, so it works on its own; installing both is fine. See wbshterm/README.md.

wbshterm at startup: the screenfetch panel, Catppuccin Mocha theme

Portable ZIP. wbsh-<version>-portable-x64.zip (or the wbshterm- one): extract and run. No registry, no PATH changes.

winget / scoop. Not yet published.


Quick start

$ wbsh
 wbsh 1.0.8 — a Bash-compatible shell for Windows
   type `exit` or press `Ctrl-D` to quit
trach@DESKTOP /c/Users/trach (main)$ ls
Documents  Downloads  Desktop  ...

$ wbsh -c 'for i in {1..3}; do printf "%02d\n" "$((i * 7))"; done'
07
14
21

Pipelines, redirection, control flow, functions, command substitution, arithmetic, brace, parameter and glob expansion, here-docs, here-strings and traps all work.

wbsh                          interactive shell (TTY auto-detect)
wbsh [opts] -c <command>      run / dump the given string
wbsh [opts] <file>            run / dump the file (- for stdin)

  -i, --interactive           force interactive REPL
  -r, --run                   execute the script (default for files is an AST dump)
  -e, --expand                dump expanded words
  -t, --tokens                dump the token stream
  --no-ast                    suppress the AST dump
  -h, --help                  show help

The default AST dump for files is a debugging aid; pass -r to run scripts.


Line editing

Persistent history, kill ring, programmable and per-tool tab completion (git, docker, npm, cargo, kubectl), reverse-incremental search, and inline predictions: the rest of the best matching history entry appears as dim ghost text, and entries whose last run failed are never suggested. Bindings follow readline:

KeysAction
Tabcomplete; second Tab lists candidates
→ at end of lineaccept the inline prediction
← / →, Ctrl-B / Ctrl-Fmove by one character
Home / End, Ctrl-A / Ctrl-Estart / end of line
↑ / ↓, Ctrl-P / Ctrl-Nwalk history
Ctrl-R / Ctrl-Ssearch history backward / forward
Esc / Ctrl-Gcancel the search
Ctrl-U / Ctrl-Kkill to start / end of line
Ctrl-Wkill the previous word
Ctrl-Vpaste from the clipboard
Ctrl-Lclear the screen
Ctrl-Cabandon the line
Ctrl-D (empty line)exit

Configuration

~/.wbshrc is sourced at the start of every interactive session:

export EDITOR=code
alias ll='ls -lah'
alias gs='git status'
PS1='\[\e[35;1m\]\w\[\e[0m\]\g \$ '
VariableEffect
PS1, PS2Primary and continuation prompts. Escapes below.
PATHStored in POSIX form, auto-translated for spawns.
HOMEDefaults to %USERPROFILE% in POSIX form.
HISTFILEHistory file (default $HOME/.wbsh_history).
COLUMNS, LINESUpdated on resize; fires the WINCH trap.
WBSH_GIT_NO_DIRTYSkip the working-tree check in the \g prompt (huge repos).
WBSH_NO_PATHCONV, MSYS_NO_PATHCONVSuppress POSIX→Win32 argument translation for spawned commands.
WBSH_PAGERbuiltin makes less use the bundled pager even when a less is on PATH.

Prompt escapes: \u user, \h / \H short / long hostname, \w / \W working directory / its basename, \g (branch) inside a git repo, \t time, \s wbsh, plus \n \r \a \e \\ \$ and the \[ \] non-printing markers. Default PS1:

\[\e[32;1m\]\u@\h\[\e[0m\] \[\e[36;1m\]\w\[\e[0m\]\g\$

Bundled builtins

Shell: :, true, false, echo, printf, exec, pwd, cd, exit, return, break, continue, export, unset, shift, set, eval, source / ., type, command, read, test / [, local, alias, unalias, history, trap, getopts, declare / typeset, mapfile / readarray, shopt, let, umask, hash, times, caller, help, compgen, complete, compopt, readonly, jobs, wait, fg, bg, disown.

Files: ls, cat, cp, mv, rm, mkdir, rmdir, touch, ln, link, unlink, stat, chmod, chattr, lsattr, du, df, realpath, readlink, basename, dirname, find, dd, split, truncate, shred, sync, fsync, flock, mktemp, pushd, popd, dirs.

Text: head, tail (-f), wc, sort, uniq, tr, cut, tee, paste, join, tac, rev, nl, fold, column, expand, unexpand, comm, shuf, tsort, strings, grep / egrep / fgrep, sed, awk / gawk, diff (-r), cmp, patch, dos2unix, unix2dos, iconv, xargs, less, more.

Encoding and hashing: base64, base32, uuencode, uudecode, xxd, od, hexdump, hd, md5sum, sha1sum, sha256sum, sha384sum, sha512sum, sha3sum (all with -c), cksum, sum, crc32.

Archives and compression: tar (gzip, bzip2, xz, lzma and compress are detected on read; -z / -j / -a on write), gzip, gunzip, zcat, bzip2, bunzip2, bzcat, xz / unxz / xzcat, lzma / unlzma / lzcat, uncompress, zip, unzip, cpio, ar (read-only). xz and lzma decompress only, as busybox's do.

Processes and system: ps, pgrep, pkill, pidof, kill, killall, timeout, watch, uptime, free, nproc, env (-i, -u, NAME=value command), printenv, whoami, logname, groups, id, hostname, uname, date, cal, sleep, usleep, stty, reset, tput, ttysize, clear.

Other: echo, printf (%b, %q, -v), seq, expr, bc, dc, factor, getopt, uuidgen, ascii, ipcalc, ts, yes, which, curl, wget, fzf, tmux, utils.

/dev/zero, /dev/random, /dev/urandom and /dev/full exist for redirections and for every bundled command that reads a file.

less hands over to a real less when one is on PATH and stdout is a console; otherwise (or with WBSH_PAGER=builtin) the built-in pager runs.

tmux is not the real tmux: under wbshterm it opens pane mode (splits, status bar, Ctrl-B prefix; see wbshterm/README.md), anywhere else it says so and exits non-zero. There is no server and nothing outlives the window.

fzf is a fuzzy picker in the spirit of junegunn/fzf: pipe lines in, type to narrow, Enter prints the selection. With no input it lists the directory tree. It works mid-pipeline because it talks to the console device directly. Typed bare at the prompt it acts on the pick: a directory is entered, a file opens through its file association. Captured or piped uses keep printing the selection.

git, vim, less, ssh and the like are expected on PATH; git is auto-discovered from the standard install locations.


Third-party utils

A util is a DLL dropped into plugins: its commands join the shell and its segments join the terminal's status bar, with no rebuild or registration.

$ utils
hello            1.0.0      A worked example: one command and one status segment.
$ hello Tomas | tr a-z A-Z
HELLO, TOMAS!
FolderFor
<install>\plugins\Utils that ship with an install
%APPDATA%\wbsh\plugins\A user's own, no administrator needed
each folder in WBSH_PLUGINSA developer's own build output

A util's command redirects, pipes and sets $? like any other, has help text and Tab completion of its own, and cannot take a bundled command's name. The contract is C (sdk/include/wbshsdk.h), ABI-versioned and unlinked: the host hands a util a table of functions when it loads, so any compiler that makes a DLL will do. A mismatched or non-util DLL is named and skipped without affecting the others, and utils says why. utils load <dll> and utils reload keep the edit-run loop inside one shell. wbsh.exe reaches the SDK through LoadLibrary, so without wbshsdk.dll beside it the shell runs exactly as before.

Every release carries wbsh-sdk-<version>.zip with the header, a template project for MSBuild and CMake, the samples and the SDK's symbols. See sdk/README.md to write one.


Building from source

Requires Windows 10 1903+, Visual Studio 2022 or Build Tools 2022 with the Desktop development with C++ workload, and optionally Inno Setup 6 for the installer.

& "$env:ProgramFiles\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe" `
    .\wbsh\wbsh.vcxproj -p:Configuration=Release -p:Platform=x64

.\installer\build.ps1                        # installers, portable ZIPs and the SDK ZIP, version from version.props

The binary lands at x64\Release\wbsh.exe; installer output goes to installer\output\.

Layout

wbsh/        The shell: wbsh.vcxproj, src/, tests/ (golden suite, run-all.sh)
wbshterm/    The terminal: Win32 window, renderer, VT parser, panes; vendored ConPTY
sdk/         The util SDK: wbshsdk.dll, include/wbshsdk.h, template/, samples/, tests/
installer/   Inno Setup script and build.ps1
tools/       check_style.py (runs before every build), make_icon.py
docs/        Doxygen config; regenerate with `doxygen docs/Doxyfile`

CONTRIBUTING.md has the file-by-file architecture tour.


Testing

# From wbsh/tests/, after building:
../../x64/Release/wbsh.exe -r run-all.sh                  # every script exits 0
WBSH_GOLDEN=1 ../../x64/Release/wbsh.exe -r run-all.sh    # also diff against expected/

Each wbsh/tests/*.sh script exercises one slice of behavior and runs inside wbsh itself, so the suite is an end-to-end check of the whole pipeline. The SDK has its own checks in sdk\tests\sdk.ps1, which need the samples built into x64/Release/plugins.


Comparison

wbshGit BashMSYS2CygwinWSL
Native Windows binaryyesyes (MSYS)yes (MSYS)yesno (Linux VM)
Single-binary installyesnononono
Coreutils bundledyesyes (MSYS)yes (pacman)yesyes
Modern console (VT mode)yesMinTTYMinTTYMinTTYn/a
fork() emulationspawn-onlyfull (slow)full (slow)full (slow)n/a
Real Linux syscallsnonononoyes

wbsh trades full POSIX behavior for being small, fast to start, and clean to integrate. If you need a real Linux environment, use WSL.


Roadmap

  • True ConPTY for child processes.
  • Real job control: Ctrl-Z and stopped jobs.
  • Output process substitution >(...); input-side <(...) already works.
  • CI running the golden suite on every push.
  • winget / scoop publication and code-signed releases.

Contributing

Pull requests welcome; see CONTRIBUTING.md for the architecture tour, conventions, test harness and PR checklist. In short: match the existing style, one change per PR, add a test when changing executor behavior, and have a clean Release build, python tools/check_style.py and the golden suite green before opening it.

Bug reports should include the wbsh version, Windows build, and the smallest reproducing script.


License

MIT © 2026 Tomas Trachta.

tomas-trachta/wbsh

Bash-compatible shell for Windows

C++

7

80 commits

updated Oct 11, 2026

See the code

See what people are saying

SourceMessageScoreDate

wbsh - Bash-compatible shell for Windows (r/commandline)

**wbsh: a minimal bash-compatible shell for Windows (plus a terminal built around it)** Hey all. For a while now I've been working on a shell that lets me run bash commands and scripts natively on Windows. I actually need this at my job, which is what kept me going. Think of it as a minimalistic…

0

Oct 10, 2026

README

wbsh

A Bash-compatible shell for Windows: a drop-in replacement for Git Bash, written from scratch in C++17 with a real POSIX shell grammar, a readline-style line editor, and bundled coreutils so a fresh install is useful immediately.

wbsh running the test suite in Windows Terminal

Status: early but released. Tagged 1.0.x releases exist and a golden test suite runs on every build, but there is no CI yet and behavior may change between minor versions.


Why wbsh

Git Bash is a thin re-skin of MSYS2, and its quirks leak through: PATH translation surprises, slow fork emulation, an aging MinTTY. wbsh is built for modern Windows instead:

  • Native console. VT-mode output and immersive dark mode; renders correctly in Windows Terminal, conhost, VS Code, and JetBrains terminals.
  • One self-contained binary. No MSYS or Cygwin layer. ~1 MB exe, ~2.5 MB installer including the VC++ runtime.
  • Real Bash semantics. Lexer, parser, AST and expander, not a wrapper around cmd.exe.
  • Bundled coreutils. ls, grep, sed, awk, find, tar, curl, hashes and more are built in. System git, vim, less are auto-discovered.
  • Path translation. /c/Users/... ↔ C:\Users\... when invoking native Windows executables.

Install

Installer. Download wbsh-setup-x64.exe from Releases. Per-user, no UAC, installs to %LOCALAPPDATA%\Programs\wbsh, with opt-in tasks to add wbsh to your PATH and an "Open wbsh here" Explorer context-menu entry.

wbshterm. Every release also ships wbshterm-setup-x64.exe, a terminal that hosts wbsh on a pseudoconsole with themes, a config file, scrollback and selection, tmux-style panes, and shell integration. It bundles wbsh.exe, so it works on its own; installing both is fine. See wbshterm/README.md.

wbshterm at startup: the screenfetch panel, Catppuccin Mocha theme

Portable ZIP. wbsh-<version>-portable-x64.zip (or the wbshterm- one): extract and run. No registry, no PATH changes.

winget / scoop. Not yet published.


Quick start

$ wbsh
 wbsh 1.0.8 — a Bash-compatible shell for Windows
   type `exit` or press `Ctrl-D` to quit
trach@DESKTOP /c/Users/trach (main)$ ls
Documents  Downloads  Desktop  ...

$ wbsh -c 'for i in {1..3}; do printf "%02d\n" "$((i * 7))"; done'
07
14
21

Pipelines, redirection, control flow, functions, command substitution, arithmetic, brace, parameter and glob expansion, here-docs, here-strings and traps all work.

wbsh                          interactive shell (TTY auto-detect)
wbsh [opts] -c <command>      run / dump the given string
wbsh [opts] <file>            run / dump the file (- for stdin)

  -i, --interactive           force interactive REPL
  -r, --run                   execute the script (default for files is an AST dump)
  -e, --expand                dump expanded words
  -t, --tokens                dump the token stream
  --no-ast                    suppress the AST dump
  -h, --help                  show help

The default AST dump for files is a debugging aid; pass -r to run scripts.


Line editing

Persistent history, kill ring, programmable and per-tool tab completion (git, docker, npm, cargo, kubectl), reverse-incremental search, and inline predictions: the rest of the best matching history entry appears as dim ghost text, and entries whose last run failed are never suggested. Bindings follow readline:

KeysAction
Tabcomplete; second Tab lists candidates
→ at end of lineaccept the inline prediction
← / →, Ctrl-B / Ctrl-Fmove by one character
Home / End, Ctrl-A / Ctrl-Estart / end of line
↑ / ↓, Ctrl-P / Ctrl-Nwalk history
Ctrl-R / Ctrl-Ssearch history backward / forward
Esc / Ctrl-Gcancel the search
Ctrl-U / Ctrl-Kkill to start / end of line
Ctrl-Wkill the previous word
Ctrl-Vpaste from the clipboard
Ctrl-Lclear the screen
Ctrl-Cabandon the line
Ctrl-D (empty line)exit

Configuration

~/.wbshrc is sourced at the start of every interactive session:

export EDITOR=code
alias ll='ls -lah'
alias gs='git status'
PS1='\[\e[35;1m\]\w\[\e[0m\]\g \$ '
VariableEffect
PS1, PS2Primary and continuation prompts. Escapes below.
PATHStored in POSIX form, auto-translated for spawns.
HOMEDefaults to %USERPROFILE% in POSIX form.
HISTFILEHistory file (default $HOME/.wbsh_history).
COLUMNS, LINESUpdated on resize; fires the WINCH trap.
WBSH_GIT_NO_DIRTYSkip the working-tree check in the \g prompt (huge repos).
WBSH_NO_PATHCONV, MSYS_NO_PATHCONVSuppress POSIX→Win32 argument translation for spawned commands.
WBSH_PAGERbuiltin makes less use the bundled pager even when a less is on PATH.

Prompt escapes: \u user, \h / \H short / long hostname, \w / \W working directory / its basename, \g (branch) inside a git repo, \t time, \s wbsh, plus \n \r \a \e \\ \$ and the \[ \] non-printing markers. Default PS1:

\[\e[32;1m\]\u@\h\[\e[0m\] \[\e[36;1m\]\w\[\e[0m\]\g\$

Bundled builtins

Shell: :, true, false, echo, printf, exec, pwd, cd, exit, return, break, continue, export, unset, shift, set, eval, source / ., type, command, read, test / [, local, alias, unalias, history, trap, getopts, declare / typeset, mapfile / readarray, shopt, let, umask, hash, times, caller, help, compgen, complete, compopt, readonly, jobs, wait, fg, bg, disown.

Files: ls, cat, cp, mv, rm, mkdir, rmdir, touch, ln, link, unlink, stat, chmod, chattr, lsattr, du, df, realpath, readlink, basename, dirname, find, dd, split, truncate, shred, sync, fsync, flock, mktemp, pushd, popd, dirs.

Text: head, tail (-f), wc, sort, uniq, tr, cut, tee, paste, join, tac, rev, nl, fold, column, expand, unexpand, comm, shuf, tsort, strings, grep / egrep / fgrep, sed, awk / gawk, diff (-r), cmp, patch, dos2unix, unix2dos, iconv, xargs, less, more.

Encoding and hashing: base64, base32, uuencode, uudecode, xxd, od, hexdump, hd, md5sum, sha1sum, sha256sum, sha384sum, sha512sum, sha3sum (all with -c), cksum, sum, crc32.

Archives and compression: tar (gzip, bzip2, xz, lzma and compress are detected on read; -z / -j / -a on write), gzip, gunzip, zcat, bzip2, bunzip2, bzcat, xz / unxz / xzcat, lzma / unlzma / lzcat, uncompress, zip, unzip, cpio, ar (read-only). xz and lzma decompress only, as busybox's do.

Processes and system: ps, pgrep, pkill, pidof, kill, killall, timeout, watch, uptime, free, nproc, env (-i, -u, NAME=value command), printenv, whoami, logname, groups, id, hostname, uname, date, cal, sleep, usleep, stty, reset, tput, ttysize, clear.

Other: echo, printf (%b, %q, -v), seq, expr, bc, dc, factor, getopt, uuidgen, ascii, ipcalc, ts, yes, which, curl, wget, fzf, tmux, utils.

/dev/zero, /dev/random, /dev/urandom and /dev/full exist for redirections and for every bundled command that reads a file.

less hands over to a real less when one is on PATH and stdout is a console; otherwise (or with WBSH_PAGER=builtin) the built-in pager runs.

tmux is not the real tmux: under wbshterm it opens pane mode (splits, status bar, Ctrl-B prefix; see wbshterm/README.md), anywhere else it says so and exits non-zero. There is no server and nothing outlives the window.

fzf is a fuzzy picker in the spirit of junegunn/fzf: pipe lines in, type to narrow, Enter prints the selection. With no input it lists the directory tree. It works mid-pipeline because it talks to the console device directly. Typed bare at the prompt it acts on the pick: a directory is entered, a file opens through its file association. Captured or piped uses keep printing the selection.

git, vim, less, ssh and the like are expected on PATH; git is auto-discovered from the standard install locations.


Third-party utils

A util is a DLL dropped into plugins: its commands join the shell and its segments join the terminal's status bar, with no rebuild or registration.

$ utils
hello            1.0.0      A worked example: one command and one status segment.
$ hello Tomas | tr a-z A-Z
HELLO, TOMAS!
FolderFor
<install>\plugins\Utils that ship with an install
%APPDATA%\wbsh\plugins\A user's own, no administrator needed
each folder in WBSH_PLUGINSA developer's own build output

A util's command redirects, pipes and sets $? like any other, has help text and Tab completion of its own, and cannot take a bundled command's name. The contract is C (sdk/include/wbshsdk.h), ABI-versioned and unlinked: the host hands a util a table of functions when it loads, so any compiler that makes a DLL will do. A mismatched or non-util DLL is named and skipped without affecting the others, and utils says why. utils load <dll> and utils reload keep the edit-run loop inside one shell. wbsh.exe reaches the SDK through LoadLibrary, so without wbshsdk.dll beside it the shell runs exactly as before.

Every release carries wbsh-sdk-<version>.zip with the header, a template project for MSBuild and CMake, the samples and the SDK's symbols. See sdk/README.md to write one.


Building from source

Requires Windows 10 1903+, Visual Studio 2022 or Build Tools 2022 with the Desktop development with C++ workload, and optionally Inno Setup 6 for the installer.

& "$env:ProgramFiles\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe" `
    .\wbsh\wbsh.vcxproj -p:Configuration=Release -p:Platform=x64

.\installer\build.ps1                        # installers, portable ZIPs and the SDK ZIP, version from version.props

The binary lands at x64\Release\wbsh.exe; installer output goes to installer\output\.

Layout

wbsh/        The shell: wbsh.vcxproj, src/, tests/ (golden suite, run-all.sh)
wbshterm/    The terminal: Win32 window, renderer, VT parser, panes; vendored ConPTY
sdk/         The util SDK: wbshsdk.dll, include/wbshsdk.h, template/, samples/, tests/
installer/   Inno Setup script and build.ps1
tools/       check_style.py (runs before every build), make_icon.py
docs/        Doxygen config; regenerate with `doxygen docs/Doxyfile`

CONTRIBUTING.md has the file-by-file architecture tour.


Testing

# From wbsh/tests/, after building:
../../x64/Release/wbsh.exe -r run-all.sh                  # every script exits 0
WBSH_GOLDEN=1 ../../x64/Release/wbsh.exe -r run-all.sh    # also diff against expected/

Each wbsh/tests/*.sh script exercises one slice of behavior and runs inside wbsh itself, so the suite is an end-to-end check of the whole pipeline. The SDK has its own checks in sdk\tests\sdk.ps1, which need the samples built into x64/Release/plugins.


Comparison

wbshGit BashMSYS2CygwinWSL
Native Windows binaryyesyes (MSYS)yes (MSYS)yesno (Linux VM)
Single-binary installyesnononono
Coreutils bundledyesyes (MSYS)yes (pacman)yesyes
Modern console (VT mode)yesMinTTYMinTTYMinTTYn/a
fork() emulationspawn-onlyfull (slow)full (slow)full (slow)n/a
Real Linux syscallsnonononoyes

wbsh trades full POSIX behavior for being small, fast to start, and clean to integrate. If you need a real Linux environment, use WSL.


Roadmap

  • True ConPTY for child processes.
  • Real job control: Ctrl-Z and stopped jobs.
  • Output process substitution >(...); input-side <(...) already works.
  • CI running the golden suite on every push.
  • winget / scoop publication and code-signed releases.

Contributing

Pull requests welcome; see CONTRIBUTING.md for the architecture tour, conventions, test harness and PR checklist. In short: match the existing style, one change per PR, add a test when changing executor behavior, and have a clean Release build, python tools/check_style.py and the golden suite green before opening it.

Bug reports should include the wbsh version, Windows build, and the smallest reproducing script.


License

MIT © 2026 Tomas Trachta.