The C shell, native to Windows. Familiar csh ergonomics, a self-contained installer, and direct access to Win32 — with no WSL, no Cygwin, and no compatibility layer.
Windows developers have lived for decades with two unsatisfying options for shell work: cmd.exe, frozen in time, and WSL, which is really a separate operating system bolted on. PowerShell occupies its own niche but does not match the ergonomics of a Unix shell.
C Shell for Windows is a native Win32 implementation of csh designed for engineers who want the familiarity and discipline of a Unix shell without leaving Windows behind. It runs natively, integrates with the Windows process model, and ships with 188 bundled man pages.
No GUI launcher, no telemetry, no surprises. Just a fast, scriptable shell that does what you tell it to.
Ships as a self-contained bundle — nothing to install separately. No WSL, no Cygwin, no Windows runtime prerequisites to chase.
Variables, aliases, history substitution, and control flow behave the way csh users expect.
Registers itself as a Windows Terminal profile on install — one click to a new csh tab. ANSI escape sequences and 24-bit color throughout.
Completion for files, paths, and command history.
Scriptable startup file. Write scripts that integrate with the rest of your Windows tooling — with an optional VS Code extension for syntax highlighting.
Approximately twelve megabytes on disk, including bundled regex and compression. Fast startup. No background services.
CShW is in active alpha. Feedback from real users shapes what ships next — we’d like yours.
Installer with a desktop shortcut, Start menu integration, and Windows Terminal profile. Uninstalls cleanly. New in 1.0.8.4: a correctness release, and an upgrade worth taking — running cshw with no arguments never reached the prompt, Ctrl+C at the prompt closed the shell outright, and redirection was quietly wrong in five commands: test, xargs, repeat, tc, and any proc. A proc now honours >, pipes and chainers, $argv inside one is a real array, and cut gained -s. From 1.0.8.3: a quoted argument beginning with < or > was parsed as a redirect instead of as data, so echo ">notes.txt" truncated the file rather than printing the string. From 1.0.8.2: the shell no longer stops accepting input after you resize the window, and App Execution Aliases finally launch, so winget, wt and pwsh run from inside cshw for the first time. Failed launches now report the Windows error in words instead of a bare “failed”. From 1.0.8.1: a correctness and packaging release — fixes output crossing between concurrently running commands, adds spawn (open another cshw in a new window) and the -k switch, makes start's documented switches actually work, and signs every shipped binary. From 1.0.8.0: cshw is a server host — the tsrvd supervisor runs signed, out-of-process protocol handlers: native SMTP, POP3, and IMAP over a shared Maildir mailstore, FTP, and a real HTTP/HTTPS/CGI server (http.dll) that speaks HTTP/1.1 and HTTP/2. A new quicsrv worker serves HTTP/3 over QUIC (msquic + nghttp3) through the same static + CGI serving core, and the fetch client gains native /http2 and /http3 modes that guarantee the protocol. Carries over: concurrent pipelines from 1.0.7, bc -x exact rationals + matrices + complex, POSIX dc, the cascading settings layer, and a 22-chapter book.
A correctness release. Two regressions that stopped the shell starting or kept it running, one long-standing way for redirection to go quietly wrong in five different commands, and a test suite that has stopped reporting skipped tests as passes.
Running cshw with no arguments never reached the prompt. Windows
returns the current executable’s own path when asked to parse an empty command line, and
the test for “is this argument a script?” was “does this file exist”
— which an .exe passes. So the shell read its own binary as a script, and
because naming a script means “do not start interactively”, it printed a parse
error and exited. The delimiter in that error was machine code. A binary is no longer accepted
as a script, and an empty command line is no longer parsed for one.
Ctrl+C at the prompt closed the shell. The console
handler routed the two onintr modes and let the default fall through as
“not handled”, so Windows terminated the process and the session went with it
— history, variables, jobs. Every other shell abandons the line and reprompts, which is
the entire reason anyone presses it. A script with no onintr is still killable,
which is what csh does.
Redirection was wrong in five commands, for one reason. A builtin that parses
its own argument line was handed that line raw — redirect operators included — so
test -z "" > out became a four-argument error, and xargs,
repeat and tc rebuilt child command lines with the redirect still in
them, making every child re-open the file the outer redirect already held. A proc
call was worse: it could be nothing but the entire line, so a redirect, a pipe, a chainer or a
heredoc after one was swallowed as an argument, and the heredoc form hung the shell outright.
sed, awk, grep and cut were measured and
were never affected.
$argv inside a proc is an array. It had been a
single joined string, so $#argv was always 1, $argv[2] was always
empty, and foreach a ($argv) iterated once over everything — while a
script’s own $argv worked correctly the whole time. A proc also no longer
inherits its caller’s positional parameters, so one called with fewer arguments than its
caller stops silently seeing the rest. exit inside a proc is no longer swallowed.
cut gained the documented -s, and log now refuses
cmd | log FILE instead of discarding the output and leaving logging on.
The test suite stopped lying. A skipped assertion reported as a pass, so 34 of them — 24 labelled BROKEN — sat under a green banner. There are three outcomes now, the percentage is of what actually ran, and the totals name the skips. Re-measuring those 24 found that 22 described nothing wrong: either long since fixed, or a test that could not have observed what it claimed. Eleven remain, all environment-dependent.
A quoted argument starting with < or > was treated as
a redirect. The redirect parser looked only at the first character of each token and
never at the token’s type, so a quoted string beginning with an angle bracket was read as
a redirect operator rather than as data. The lexer had always distinguished the two; the
parser simply never asked.
It failed in two ways, and the quiet one is why this shipped the same day. When the text after
> was not a legal Windows filename, the open failed and you saw
'echo' failed (errno 22) with the command’s output dropped. When the text
was a legal filename there was no error at all: echo ">notes.txt"
truncated notes.txt, consumed the token as a redirect, and left echo
with no arguments — so echo wrote its own Echo is ON status into
the file it had just destroyed. Anything quoting an angle bracket was exposed: markdown
blockquotes, grep "<div>" page.html, generated HTML or XML. Quoted tokens are
now skipped when scanning for redirects; real redirects are unaffected.
The shell stopped accepting input after a window resize. The keyboard
helper thread gated the line editor on PeekConsoleInputW, but peeked only the
first 32 records of the console input buffer. Peek never removes anything, and the only
code that drains that buffer is the read loop the gate protects. So 32 or more consecutive
non-key records at the head of the queue — a resize burst of focus, menu and mouse
events will do it — wedged the shell permanently: everything typed afterwards sat at
position 33 and later, unseen, and the buffer could only be cleared by code that only ran
once the buffer was already clear. The gate now scans every pending record. Reproduced in
both conhost and Windows Terminal, at 0% CPU, which is what made it look like a hang.
App Execution Aliases could not be launched. winget,
wt, the Store Python stubs and every other packaged-app alias are zero-byte
IO_REPARSE_TAG_APPEXECLINK reparse points. The launcher used “can I open
this file and read a PE header out of it?” as a proxy for “can this be
executed?”, and aliases fail both tests — they refuse ordinary read opens and
carry no PE header at all — so the launch was refused before CreateProcessW
ever saw them. That parse only ever produced one value, the Subsystem word that decides
whether to wait for the child, so it is a hint now rather than a gate: an unreadable header
means “unknown, assume console”, and the OS decides what can run.
winget install Tropibyte.cshw works from inside cshw as of this release.
Win32 errors are reported on launch failure. A failed
CreateProcessW used to print a bare 'name' failed. The code is
now captured at the instant of failure — it was previously re-read several statements
later, after intervening calls could have reset it — and decoded, so you get
'name' failed (Windows error 2: The system cannot find the file specified).
Uninitialized reads in the PE header parse. None of the three
fread calls checked their return value, and the DOS header and optional-header
magic were uninitialized locals, so a zero-byte or truncated file left the code branching
on stack garbage and seeking to a garbage offset. Every read is checked and every buffer
initialized.
Output no longer crosses between concurrent commands. Two process-global
resources were being used for per-command redirection. Command substitution closed the real
stdout handle when redirecting, and Windows recycled that handle value for the next capture
file — so a background command’s output could land inside an unrelated
`backtick` capture, and a child process could inherit an already-closed handle.
Separately, a background command using > took over the shell’s own stdout,
so interactive output could vanish outright. Redirection state is now per-thread, and every
shipped built-in writes through its own command’s output stream.
New: spawn. Opens another cshw in a new console window, inheriting
the current directory and exported environment — with -c/-k
payloads, /d, /title, /min, /max,
/wait, and /here for a nested shell in the current console. The new
-k switch (cmd’s /K) runs a command and then stays interactive.
Fixes. cshw -c "exit 7" returned 0 instead of 7. The
start command documented /min /max /wait /b /d /i but silently ignored
all six; they work now, and /wait publishes the child’s real exit code.
tokenize reported failure on success.
Everything is signed. quicsrv.exe and the bundled nghttp2, msquic
and nghttp3 libraries shipped unsigned in 1.0.8.0; all of them are signed here, and the portable
zip now carries the native libraries it was missing — so HTTP/2 and HTTP/3 work for
portable and Scoop installs too. The release build now refuses to produce an installer if any
packaged binary is unsigned.
Note on the certificate. The code-signing certificate changed to correct a
malformed publisher name, which previously rendered as Tropibyte\, Inc. with a
stray backslash. It now reads Tropibyte, Inc. Because the certificate is new,
Windows treats this as a new publisher and SmartScreen reputation starts over — you may
see a warning on first run even though the installer is signed.
Shell fundamentals. csh/tcsh-flavored control flow (if/while/foreach/loop/for/switch) with optional then on every header. Procedures with locals and $result. Real arrays and hashes. Chainers (;, &&, ||, and, or, xor, nor, nand). Stderr redirection (2>, 2>>, 2>&1). Regex match (=~ / !~). Persistent history with bang-recall and Ctrl-R reverse search.
Daily-driver commands. Full text-tool suite (grep with PCRE2, sed, awk, sort -t/-k, uniq, cut, tr, wc, head, tail -f). File operations including find with size/mtime/type filters. Job control. genai (OpenAI, Anthropic, Azure, Ollama, LM Studio). Networking primitives. Browse all 225 in the full command reference →
Windows-native. Real concurrency (coroutines plus mutex, event, semaphore, barrier, channel). Plugin DLLs in C/C++. Direct Win32 from the shell (rundllproc, winapi, dllimport). Runs your existing .bat/.cmd scripts through cmd.exe and .ps1 scripts through PowerShell — cshw dispatches them to the right interpreter, it doesn’t reinterpret them.
It’s alpha. Expect rough edges. Some commands have feature gaps relative to GNU equivalents (e.g., awk is a subset of full POSIX awk; sort -k accepts a single field rather than ranges). The Win32 calling-convention surface of rundllproc is generally x64 fastcall; mismatched signatures are still UB. Found something broken? Email bugs@tropibyte.com.
cshw is a developer/admin shell. Plugins are native code; rundllproc calls real Win32; scripts aren’t sandboxed. The trust model is identical to running any .exe: you load it, you ran it, you trust it. No auto-discovery of plugins from the internet, no driver-level code, no hidden privilege escalation.
The SHA-256 of the installer is shown above. Verify in PowerShell with
Get-FileHash cshw-setup-1.0.8.4.exe -Algorithm SHA256.
If the hash doesn’t match, don’t run the installer.
CShW ships incrementally. Here’s the work currently on the path to beta.
Hardening the core interpreter, expanding the test suite, and addressing alpha feedback.
Native binaries for Windows on ARM, alongside the existing x64 release.
Per-project shell configuration with explicit precedence and reload semantics.
Wider release with installer signing chain, automatic updates, and a published EULA.
CShW is being shaped by its early users. Bug reports go to a dedicated address with a fill-in-the-blanks template; general notes go to hello@tropibyte.com. We read everything.