Skip to content

Latest commit

 

History

55 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mcsh — Modern C Shell

mcsh is a consolidated, modernised fusion of tcsh (by the tcsh-org maintainers, originally by Christos Zoulas and the tcsh community, building on the work of Ken Greer, Mike Ellis, and Paul Placeway) and etcsh (by Krush206) into a single, polished, fully compatible reincarnation of the Berkeley C Shell. Both upstream projects are the authoritative sources of the underlying shell engine and etcsh language extensions respectively — mcsh exists to merge and maintain them.

The installed program is mcsh(1). Everywhere in this repository, in the binary, and in the manual page, the shell identifies itself as Modern C Shell — not as tcsh, etcsh, or csh.


Backward compatibility

mcsh is a drop-in replacement for tcsh and csh:

Compatibility item Behaviour
Start-up files Reads ~/.mcshrc first; falls back to ~/.tcshrc then ~/.cshrc. No existing configuration needs renaming.
Binary Installs as mcsh. A tcsh symlink is created alongside it so scripts that invoke /usr/local/bin/tcsh keep working.
Manual page man mcsh is canonical. man tcsh is a symlink to the same page.
Shell variables Both $mcsh and $tcsh are set to the running version string, so scripts guarded by if ($?tcsh) continue to fire.
$version Banner reads mcsh <ver> (<origin>) … [tcsh baseline <upstream-ver>] options …, preserving the upstream tcsh version that mcsh was consolidated from.

Features added over upstream tcsh

Language

Feature Description
Interactive comments # is a comment character in interactive mode as well as in scripts (tcsh PR #89)
Expression short-circuit $?a && "$a" != "" no longer throws when a is unset; variable expansion is deferred until after the short-circuit is resolved (tcsh PR #107)
Pipe-to-variable echo foo | set x and set x < file assign the piped / redirected text to x (tcsh PR #105)
function builtin Named shell functions, declared as function name, then the body, terminated by return (tcsh PR #77). Declaring one also installs an alias shim that dispatches to it, so it is callable like any other command
Redirect in { } blocks if ( { cmd >& /dev/null } ) correctly honours the redirection (tcsh issue #113)

Editor / interactive experience

Feature set variable Description
Fish-style predictive autocomplete set predict As you type, the most recent matching history entry, file path, or command is shown as inline ghost text (dimmed). Press Right-Arrow or ^F to accept the full suggestion. Includes a filesystem/PATH cache to ensure zero latency.
Interactive syntax highlighting set syntax Per-keystroke ANSI colour highlighting of keywords, builtins, aliases, shell functions, commands (ok/bad), operators, variables, strings (double/single/backtick), comments, and unmatched-quote errors. Command classification follows what the shell would actually run: keywords and builtins first, then functions and aliases (which shadow $PATH), then $PATH. Arguments are classified too — options, glob patterns, and paths that exist (including ~ and ~user). Wrapper commands (sudo, env, nohup, time, xargs, …) keep the following word in command position. Also covers assignments, history references (!!, !$), variable subscripts ($argv[1]) and modifiers ($x:h). A 64-entry LRU cache avoids repeated stat(2) calls per $PATH lookup, and filesystem probes are capped per rescan.
Filetype colouring in completion set color Coloured filetype indicators in tab-completion listings, driven by LSCOLORS / LS_COLORS.

Prompt

Feature Description
Git repository status %v shows *n modified tracked files, !n unmerged paths, $n stash entries and ^ when HEAD differs from its configured upstream; %V prepends the branch and operation state. Derived by parsing .git/index and comparing it against the working tree — the same stat comparison git's own fast path makes — plus the stash log, config, and the refs (honouring packed-refs). Still no git process. Computed lazily, so a prompt using only %g never pays for it. Measured ~0.5 ms over 536 tracked files, once per poll interval.
Native git branch %g expands to the current branch name (or the 7-character object name on a detached HEAD); %G also appends the operation state (main|MERGING, main|REBASING-i, main|BISECTING, abc1234|DETACHED, …). Both are empty outside a git repository. No git process is spawned — the control files are read directly. Works from any subdirectory, and in linked worktrees, submodules, and bare repos. Cached per-CWD against the resolved git directory: HEAD is compared by contents (exact — st_mtime has one-second granularity) and the state markers by mtime, tracked independently so merges, rebases and cherry-picks are detected promptly without false refreshes. Poll interval is 2s, overridable with $GIT_POLL_INTERVAL.

Directory stack (zsh-style navigation)

Feature Description
Numbered tree display pushd, popd, and cd show the directory stack as a numbered vertical list after every navigation. The current directory (index 0) is marked with .
dirs -v arrow marker dirs -v marks index 0 with so the current position is always visible at a glance.
cd -N Jumps to stack entry N counted from the bottom (oldest entry), mirroring zsh's cd -N semantics. Complements the existing cd +N (forward from current). A bare cd - still switches to $owd.
pushd +N / popd +N Unchanged: rotate or pop the Nth entry from the top.

Syntax highlighting token colours

Token Default colour
Keyword (if, while, foreach, …) Bold cyan
Builtin (set, alias, cd, …) Bold green
Alias (user-defined) Bold blue
Shell function (user-defined) Bold magenta
Option argument (-v, --long) Cyan
Path argument that exists Blue
Glob pattern (*, ?, […]) Yellow
Command — found on $PATH Green
Command — not found Bold red
Operator (|, ;, &&, …) Yellow
Variable ($var, $?var) Magenta
Double-quoted string Yellow
Single-quoted string Yellow
Backtick substitution Cyan
Comment Bright black (grey)
Unmatched quote / error Bold red

Bug fixes over upstream tcsh

Fix Description
%j prompt token Counts only live job leaders, not all process-list entries
getn() overflow @ x = (1 << 63) no longer raises "Badly formed number"; uses strtoll with overflow/errno checking
Shift operator UB << and >> use unsigned arithmetic to eliminate signed-shift undefined behaviour
crypt link failure AC_SEARCH_LIBS([crypt], [crypt xcrypt]) handles the modern libxcrypt split
vms.termcap.c OOB scans Colon-scan loops stop at '\0'; sscanf uses `%[^
vms.termcap.c tgoto Static buffer enlarged to 64 bytes; %d uses snprintf; bounds checked throughout
vms.termcap.c octal Octal digits 47 handled; continuation digits validated as <= '7'
acaux/install-sh name patterns Case patterns use * suffix to catch multi-character names beginning with -, =, (, ), !
m4/lib-prefix.m4 dn; comment typo corrected to dnl
m4/po.m4 C# DLL cleanup Error cleanup removes the actual DLL target, not the .msg source
configure.ac patchlevel PACKAGE_PATCHLEVEL stripped of leading zeros, preventing invalid C integer literals like 08
configure.ac baseline version TCSH_BASELINE_VERSION correctly expands TCSH_VERSION to a quoted C string literal in config.h
sh.func.c doif truncation doif() uses tcsh_number_t to avoid truncating wide expression results
ed.defns.c catalog collision predict-accept uses NLS catalog ID 124 (was 122, colliding with newline-and-hold)
ed.screen.c SGR desync SetSGRColor() emits ESC[22;39m (not ESC[0m) for default-fg/no-bold, preserving cur_atr synchronisation
ed.refresh.c ghost SGR DrawGhost() resets with ESC[22;39m (not ESC[0m) so cur_atr stays consistent on the incremental path
ed.inputl.c colours never drawn The CC_NORM path called syntax_colorize() after e_insert() had already painted the character via RefPlusOne(), and nothing redrew afterwards — so highlighting was invisible during normal typing and only appeared after an unrelated full redraw (^L, history recall, completion). The colours are now computed and repainted together, and e_insert() skips its one-character fast path while set syntax is active (that path draws raw and cannot recolour earlier characters, which a single keystroke routinely does)
ed.screen.c colour gating T_CanColor came solely from the termcap Co capability, so a missing terminfo entry silently disabled highlighting outright — the common case for alacritty, kitty, foot and wezterm in containers, on servers and over ssh. Falls back to $COLORTERM, a color substring in $TERM, and a list of known colour-capable emulators. Genuinely monochrome terminals (dumb, vt100) still end up disabled
tc.prompt.c HEAD staleness HEAD was compared by st_mtime, which has one-second granularity: two HEAD writes inside the same second (scripted checkouts, a TUI git client, rebase stepping through commits) left the prompt permanently stale. HEAD's contents are now compared directly — it is a ~41 byte file, so the read costs about what the stat() did and is exact
tc.prompt.c marker mtime Git cache tracks HEAD mtime and state-marker max-mtime independently — a live MERGE_HEAD no longer forces a refresh on every prompt
tc.prompt.c git cache scope Staleness polling watched $cwd/.git/…, a path that exists only at the root of a non-worktree checkout. The cache was therefore permanently stale in every subdirectory and in every linked worktree — %g froze on the branch that was current when you entered the directory. It now watches the resolved git directory reported by git_get_info()
tc.prompt.c marker coverage The staleness watch list omitted REVERT_HEAD and BISECT_LOG, so entering or leaving a revert or bisect was never noticed. The watch list now covers every state %G can report
tc.prompt.c double fclose The linked-worktree path closed the .git file, then fell through to a second fclose() on the same handle when the resolved gitdir exceeded MAXPATHLEN
tc.prompt.c detached HEAD Printed the full 40-character object name: xsnprintf() parses a . immediately after % as a zero-pad flag, so "%.7s" silently meant "%07s". Truncation is now explicit
tc.prompt.c poll throttle GIT_POLL_INTERVAL was defined but never used (the literal 2 was still hard-coded), the environment override was parsed with unchecked atoi(), and the throttle window was not started on a refresh so the first poll always fired regardless of the configured interval
sh.h CHAR_EOF type Defined as a plain (-2) and compared against eChar, which is unsigned wint_t in the wide-character build; now cast to eChar
dch-template.in distribution Template uses UNRELEASED instead of unstable
alacritty.toml portability Shell invoked by name via PATH; pywal import commented out as optional

Prompt reference

Escape Expands to
%g Current git branch name, or the 7-char object name on a detached HEAD (empty outside a git repo)
%G Branch name plus operation state: main|MERGING, main|REBASING-i, abc1234|DETACHED, etc. (empty outside a git repo)
%v Repository status indicators only: *n modified tracked files, !n unmerged paths, $n stash entries, ^ HEAD differs from its configured upstream
%V %G plus %v — the full at-a-glance summary, e.g. main *3 $1 ^
%? Exit status of the last command
%B / %b Bold on / off
%U / %u Underline on / off
%S / %s Standout (reverse video) on / off
%{…%} Literal (zero-width) escape sequences
%n Username
%m Hostname (first component)
%c02 / %~ Trailing 2 components of CWD / CWD with ~ substitution
%j Number of running jobs
%# # for root, % otherwise

Example — right-prompt showing git branch in standout:

set rprompt = '%S%G%s'

Example — full colour prompt with git and exit status:

set red   = "%{\033[1;31m%}"
set green = "%{\033[1;32m%}"
set blue  = "%{\033[1;34m%}"
set reset = "%{\033[0m%}"
set prompt = "${green}%n@%m${reset}:${blue}%B%c02%b${reset} [${red}%?${reset}] %# "

Directory stack navigation

mcsh adds zsh-style directory stack tree display and cd -N navigation.

% pushd ~/projects/foo      # push new directory
0→  ~/projects/foo
1   ~/projects
2   ~

% pushd ~/etc
0→  ~/etc
1   ~/projects/foo
2   ~/projects
3   ~

% cd -2                     # jump to entry 2 from bottom (oldest visible non-cwd)
0→  ~/projects/foo
1   ~/etc
2   ~/projects
3   ~

% popd
0→  ~/etc
1   ~/projects
2   ~

% dirs -v                   # explicit numbered listing
0→  ~/etc
1   ~/projects
2   ~

Keybindings / aliases set by dot.mcshrc:

Alias Command
pd pushd
po popd
d dirs -v
.. cd ..
... cd ../..

Source layout

The tree preserves the traditional tcsh flat layout:

Prefix / file Purpose
sh.*.c / sh.*.h Core shell (parser, executor, history, jobs, directory stack, …)
ed.*.c / ed.*.h Command-line editor (readline equivalent, syntax highlighting, ghost text)
tc.*.c / tc.*.h tcsh extensions (prompts, key bindings, NLS, completion, …)
tw.*.c / tw.*.h Tab / word completion and filetype colouring
glob.c / glob.h Pattern globbing
dotlock.c / dotlock.h History file locking
mi.*, ma.setp.c POSIX / BSD compatibility shims
gethost.c, host.defs Host-table generator
ed.syntax.c / ed.syntax.h Native interactive syntax highlighting engine
nls/ National Language Support catalogues
system/ Per-platform compile-time config fragments
acaux/, m4/ Autoconf / autotools auxiliary files
configure.ac, Makefile.in GNU Autotools build system
complete.mcsh, complete.tcsh Programmable completion rules
csh-mode.el Emacs major mode for csh/mcsh scripts
tcsh.man.in Manual page template
dot.login, dot.tcshrc, dot.mcshrc Example user start-up files

Building

Quick dev build (FreeBSD / Linux / macOS)

make              # build in-tree; produces ./mcsh
sudo make install # installs mcsh + tcsh symlink

From a clean checkout

autoreconf -fi   # regenerate configure (needed once after editing configure.ac)
./configure
make
sudo make install

WSL (Windows Subsystem for Linux)

mcsh has no native Win32 support. Build inside WSL:

sudo apt install build-essential autoconf automake
autoreconf -fi && ./configure && make

Cygwin

autoreconf -fi && ./configure && make

dot.mcshrc reference

dot.mcshrc is the canonical start-up file. Copy it to ~/.mcshrc:

cp dot.mcshrc ~/.mcshrc

Sections and what they provide:

Section Key settings
1 — Display server Wayland env vars gated behind /dev/dri/card0 + $WAYLAND_DISPLAY presence check. Machine-specific GPU overrides go in ~/.mcshrc.local.
2 — System environment Prepends ~/.local/bin to $path; sets EDITOR, VISUAL, PAGER, LESS, BLOCKSIZE, CLICOLOR, LSCOLORS.
3 — Core execution engine set autorehash, autolist=ambiguous, autoexpand, autocorrect, color, syntax, correct=cmd, ellipsis, filec, listjobs=long, listlinks, listmax=100, matchbeep=never, rmstar, symlinks=chase; history 10 000 entries with merge-dedup to ~/.mcsh_history.
4 — Key bindings Emacs mode; Up/Down arrow history-search; Ctrl+Arrow word navigation; Home/End for xterm/vt100/rxvt/application-cursor; magic-space, backward-delete-word, run-fg-editor, kill-region.
5 — Completions cc/clang/gcc (file extensions + -I/-L); make (reads live target list); man, kill, sysctl, service, ifconfig, cd, tar/gzip/xz/bzip2.
6 — Aliases ls -F -G, l, ll, df -h, du -ch, .., ..., pd/po/d (pushd/popd/dirs), dis (objdump Intel syntax), cclean, h, j, m, g.
7 — Prompt %g/%G git escapes; colour-coded prompt with user@host, CWD, exit status; rprompt='%S%G%s'; prompt2 and prompt3 for multi-line and correction.
8 — Host completion Builds $hosts from ~/.hosts, ~/.rhosts, ~/.ssh/known_hosts for SSH/rlogin completion.
9 — System-specific Sets stty status ^G + binds stuff-char on BSD/Darwin/FreeBSD/NetBSD; set time coloured format at the end so startup commands are not timed.
Root guard Unsets savehist; sets LESSHISTFILE=- and VIMINIT='set viminfo=' when $uid == 0.
Local overrides Sources ~/.mcshrc.local last if it exists — machine-specific GPU vars, paths, tokens go there.

Compatibility notes

  • mcsh sources ~/.mcshrc on startup, falling back to ~/.tcshrc then ~/.cshrc. No existing configuration needs to be renamed.
  • complete.mcsh is the mcsh-native completion file. complete.tcsh is retained for legacy setups that test $?tcsh.
  • The tcsh binary symlink created by make install ensures existing scripts and /etc/shells entries keep working.

Resolved regressions

Unicode / wide-character handling (Round 9)

Multi-byte characters — emoji, CJK, Latin Extended, and any character whose UTF-8 encoding is longer than one byte — were previously silently dropped or corrupted during filename glob expansion and variable assignment. This was a byte-vs-character length bug inherited from tcsh 6.24.14 (tracked as tcsh issues #117 / #121).

Resolution (sh.lex.c, sh.dol.c): the mbtowc accumulation loops in wide_read() and the $< line-read primitive now bound partial-byte lookahead by the runtime MB_CUR_MAX instead of the compile-time MB_LEN_MAX. After a stray invalid byte the loop no longer over-reads up to 15 bytes of subsequent valid UTF-8.

Tests: tests/t009_unicode_vars.sh through tests/t014_unicode_script_source.sh cover variable round-trip, $% character count, glob expansion, $< stdin read, backquote substitution, invalid-byte recovery, and sourced-script Unicode. See ISSUES.md Round 9 for details.

Short-circuit evaluation (dev4, improved in Round 7)

if ($?a && "$a" != "") previously threw "Undefined variable" even when $a was unset because Dfix() expanded all $ tokens before && short-circuiting could suppress evaluation. Fixed in sh.dol.c: unset variables now silently expand to "", matching bash/zsh semantics. Modifier expressions like ${unset:h} and dimen expressions like $#unset also no longer error — they return "" and 0 respectively. See ISSUES.md Rounds 6–7.

Test coverage

An initial regression suite is in tests/ covering startup variables, arithmetic overflow/shift semantics, short-circuit evaluation, pipe-to-var, directory stack, the function builtin, signed right-shift, and unset variable modifier handling. This covers the core new features but is not exhaustive. Run with:

tests/run_tests.sh

Licensing

mcsh is BSD 3-Clause (see LICENSE). The upstream tcsh / etcsh source is also BSD 3-Clause (see UPSTREAM-COPYRIGHT). Redistribution must carry both notices — see NOTICE for details.


Credits & Upstream

mcsh is built on the shoulders of two excellent upstream projects:

Project Repository Role in mcsh
tcsh github.com/tcsh-org/tcsh The authoritative upstream shell engine. Originally written by Ken Greer and Mike Ellis at Carnegie Mellon, extended by Paul Placeway, and now maintained by Christos Zoulas and the tcsh community. The bulk of sh.*, ed.*, tc.*, tw.*, NLS, and build system derive from this project.
etcsh github.com/Krush206/etcsh An enhanced fork of tcsh by Krush206, adding function declarations, interactive comments, pipe-to-variable, expression short-circuit fixes, and other language improvements — all of which are incorporated into mcsh.

All original copyright notices are preserved in UPSTREAM-COPYRIGHT and NOTICE. Bug fixes and feature PRs that originated in the upstream repositories are credited in ISSUES.md and PLAN.md with their upstream issue/PR numbers.


Status

See ISSUES.md for the running log of bugs, compatibility items, and modernisation tasks.
See PLAN.md for the full phased execution plan.

About

Modern C Shell is an enhanced fusion of Etcsh and Tcsh, a modern and completely compatible version of the Berkeley C-Shell.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages