.TH ZISH 1 "August 2026" "zish 0.16.1" "User Commands" .SH NAME zish \- a fast shell with vim editing and job control .SH SYNOPSIS .B zish [\fB\-l\fR] [\fB\-c\fR \fIcommand\fR] [\fB\-\-profile\fR \fIname\fR] [\fB\-\-allow\-write\fR \fIpaths\fR] [\fB\-\-version\fR] [\fB\-\-help\fR] [\fIscript\fR [\fIarguments\fR...]] .SH DESCRIPTION .B zish is a Unix shell written in Zig, featuring vim-style modal editing, job control, arrays, process substitution, statistical benchmarking, intelligent tab completion, syntax highlighting, and ghost text completion drawn from history and completion candidates. .PP When invoked without arguments, zish starts an interactive session. When given \fB\-c\fR, it executes the command string. When given a script path, it executes the script. .SH OPTIONS .TP .BR \-c " " \fIcommand\fR Execute \fIcommand\fR and exit. .TP .BR \-\-version Print version information and exit. .TP .BR \-\-help Print usage information and exit. .TP .BR \-l ", " \-\-login Start as a login shell. .TP .BR \-d ", " \-\-debug\-log\-file " " \fIfile\fR Write internal debug information to \fIfile\fR. .TP .BR \-\-profile " " \fIname\fR Restrict what this session and every process it starts may touch on the filesystem. See .B SANDBOXING below. .TP .BR \-\-allow\-write " " \fIpaths\fR Additional directories the restricted session may write to, separated by colons as in \fBPATH\fR. Requires a restrictive \fB\-\-profile\fR. .SH SANDBOXING .B \-\-profile applies a Landlock restriction to the shell process at startup. Landlock restrictions are inherited by children and cannot be lifted, so the limit applies to every command the session runs, and to zish itself. .TP .B none No restriction. This is the default. .TP .B readonly The filesystem may be read and executed from, but not written to. .TP .B workdir As \fBreadonly\fR, plus write access beneath the current directory at the time zish started. .PP .PP .B \-\-allow\-write adds writable roots to \fBreadonly\fR or \fBworkdir\fR, colon\-separated. A grant covers the named directory and everything beneath it, and nothing else \- not its parent, not a sibling. Wrapping another program is the usual reason to need it: an agent or build tool that cannot write its own state or cache directory fails in ways that look like bugs in that program rather than denials by the sandbox. .PP A path that does not exist is refused rather than skipped, and .B \-\-allow\-write without a restrictive profile is an error: a grant that nothing enforces is worse than no grant, because the caller stops checking. .PP Write\-only device sinks (\fI/dev/null\fR, \fI/dev/zero\fR, \fI/dev/tty\fR, \fI/dev/urandom\fR and similar) remain writable under every profile, because redirecting to them is a write. .PP The restriction fails closed rather than degrading to no restriction: an unknown profile name exits with status 2, and a kernel that cannot enforce the requested profile exits with status 1. \fBnone\fR needs no kernel support and always starts. .PP Every restrictive profile additionally installs a seccomp syscall filter that denies .BR ptrace , .BR process_vm_readv / process_vm_writev and .B kexec \(em syscalls a shell's children have no legitimate use for. Denied calls return .B EPERM rather than killing the caller. The filter is inherited across exec like the Landlock restriction. .PP Only writes are restricted on the filesystem. Every profile can still read the whole filesystem, including credentials, so this bounds damage rather than disclosure. Network access, process creation and signals are not restricted. It is a blast radius, not a jail. .SH TRACING When file descriptor 3 is open, zish writes one JSON record per submitted command to it, so a program driving zish does not have to parse prompts or ANSI escapes to learn what happened: .PP .RS .nf $ zish \-c 'make test' 3>trace.jsonl $ cat trace.jsonl {"ts":1786246738163,"cmd":"make test","cwd":"/src","exit":0,"ms":842,"sandbox":"none"} .fi .RE .PP The fields are the start time in milliseconds since the epoch, the command as submitted, the working directory, the exit status, the elapsed milliseconds, and the restriction profile in force (see .BR SANDBOXING ). String fields are JSON\-escaped; invalid UTF\-8 becomes U+FFFD, so a value containing quotes or newlines cannot break the record apart. The .B sandbox field lets a harness confirm the session it launched is the one it is reading. .PP There is no flag and no configuration: the feature is off unless the descriptor is open, and standard output is left exactly as the command wrote it. Only what was actually submitted is recorded \- sourcing of startup files, command substitutions and other internal evaluation are not. .PP Set .B ZISH_TRACE_FD to use a different descriptor. A value of 2 or below, or one that is not a number, disables tracing entirely rather than falling back to descriptor 3: writing records into the command's own output is the failure this exists to avoid. .SH SHELL GRAMMAR .SS Simple Commands A simple command is a sequence of words separated by spaces. The first word is the command name, subsequent words are arguments. .PP .RS .nf ls \-la /tmp echo "hello world" .fi .RE .SS Pipelines Commands can be connected with pipes: .PP .RS .nf cat file | grep pattern | wc \-l .fi .RE .SS Lists Commands can be chained: .PP .RS .nf cmd1 && cmd2 # cmd2 runs only if cmd1 succeeds cmd1 || cmd2 # cmd2 runs only if cmd1 fails cmd1 ; cmd2 # cmd2 runs regardless cmd & # run cmd in background .fi .RE .SS Redirections .TP .B > file Redirect stdout to file (overwrite) .TP .B >> file Redirect stdout to file (append) .TP .B < file Redirect stdin from file .TP .B 2> file Redirect stderr to file .TP .B 2>> file Redirect stderr to file (append) .TP .B 2>&1 Redirect stderr to stdout .TP .B &> file Redirect both stdout and stderr to file .TP .B &>> file Redirect both stdout and stderr to file (append) .SS Process Substitution .TP .B <(command) Substitute with a file descriptor containing command's output .TP .B >(command) Substitute with a file descriptor that feeds into command's input .PP Example: .RS .nf diff <(sort file1) <(sort file2) .fi .RE .SS Quoting .TP .B 'single quotes' Literal string, no expansion .TP .B "double quotes" Variables and command substitution expanded .TP .B $'escape sequences' Supports \\n, \\t, \\e, \\xHH, etc. .TP .B \e Escape next character .SS Variables .PP .RS .nf VAR=value # assignment echo $VAR # expansion echo ${VAR} # braced expansion echo ${VAR:\-def} # default if unset echo ${VAR:=def} # assign default if unset echo ${VAR:+alt} # alternate if set echo ${VAR:?err} # error if unset echo ${#VAR} # length echo ${VAR#pat} # remove shortest prefix echo ${VAR##pat} # remove longest prefix echo ${VAR%pat} # remove shortest suffix echo ${VAR%%pat} # remove longest suffix .fi .RE .SS Arrays .PP .RS .nf arr=(one two three) # declare array echo ${arr[0]} # first element echo ${arr[@]} # all elements echo ${#arr[@]} # array length arr+=(four) # append arr[1]=TWO # modify element .fi .RE .SS Command Substitution .PP .RS .nf $(command) # preferred form \`command\` # legacy form .fi .RE .SS Arithmetic .PP .RS .nf $((expression)) # arithmetic expansion .fi .RE Supports: + \- * / % ** (power), parentheses, variables. .SS Globbing .TP .B * Match any string .TP .B ? Match any single character .TP .B [abc] Match any character in set .TP .B [a\-z] Match any character in range .TP .B [!abc] Match any character not in set .SS Control Flow .PP .RS .nf if command; then commands elif command; then commands else commands fi while command; do commands done for var in words; do commands done case word in pattern) commands ;; *) default ;; esac .fi .RE .SS Functions .PP .RS .nf func() { commands } function func { commands } .fi .RE .SH BUILTINS .SS Simple Returns .TP .B true Return success (0). .TP .B false Return failure (1). .TP .B : No-op, always succeeds. .TP .B continue Continue next loop iteration. .TP .B break Break from loop. .SS Directory .TP .B cd \fR[\fIdir\fR] Change directory. With no argument, change to $HOME. Supports \fBcd \-\fR to return to previous directory. .TP .B pwd Print working directory. .TP .B pushd \fR[\fIdir\fR] Push directory onto stack and change to it. With no argument, swap top two stack entries. .TP .B popd Pop directory from stack and change to it. .TP .B dirs List directory stack. .TP .B .. Change to parent directory. Shorthand for \fBcd ..\fR. .TP .B ... Change to grandparent directory. Shorthand for \fBcd ../..\fR. .TP .B \- Change to previous directory. Shorthand for \fBcd \-\fR. .SS I/O .TP .B echo \fR[\fB\-n\fR] [\fB\-e\fR] [\fIargs\fR...] Print arguments. \fB\-n\fR omits newline, \fB\-e\fR enables escapes. .TP .B printf \fIformat\fR [\fIargs\fR...] Formatted output. Supports %s, %d, %x, %o, %f, %c, %b, %q and width/precision. .TP .B read \fR[\fB\-r\fR] [\fB\-p\fR \fIprompt\fR] [\fB\-t\fR \fIsec\fR] [\fB\-n\fR \fIcount\fR] [\fB\-s\fR] [\fB\-d\fR \fIdelim\fR] [\fB\-a\fR \fIarray\fR] [\fIvar\fR...] Read line into variables. \fB\-r\fR raw mode (no backslash escapes), \fB\-p\fR display prompt, \fB\-t\fR timeout in seconds, \fB\-n\fR read exactly N characters, \fB\-s\fR silent (no echo), \fB\-d\fR use delimiter instead of newline, \fB\-a\fR read into array. .SS Conditionals .TP .B test \fIexpr\fR, [ \fIexpr\fR ] Evaluate conditional expression. .TP .B [[ \fIexpr\fR ]] Extended conditional with pattern matching and regex. .SS Variables .TP .B export \fR[\fIname\fR[=\fIvalue\fR]...] Export variables to environment. .TP .B unset \fIname\fR... Remove variables. .TP .B local \fIname\fR[=\fIvalue\fR]... Declare local variables in functions. .TP .B declare \fR[\fB\-a\fR] \fIname\fR[=\fIvalue\fR]... Declare variables. \fB\-a\fR declares array. .TP .B readonly \fIname\fR[=\fIvalue\fR]... Declare read-only variables. .TP .B set \fR[\fB\-euxo\fR] [\fB\-\-\fR \fIargs\fR...] Set shell options or positional parameters. .TP .B shift \fR[\fIn\fR] Shift positional parameters left by n (default 1). .TP .B getopts \fIoptstring\fR \fIname\fR Parse positional parameters. .SS Aliases .TP .B alias \fR[\fIname\fR[=\fIvalue\fR]...] Define or list aliases. .TP .B unalias \fIname\fR... Remove aliases. .SS Sourcing and Execution .TP .B source \fIfile\fR, . \fIfile\fR Execute commands from file in current shell. .TP .B eval \fIarg\fR... Concatenate arguments and execute as command. .TP .B exec \fIcommand\fR Replace shell with command. .TP .B command \fIcmd\fR Run cmd bypassing functions and aliases. .TP .B builtin \fIcmd\fR Run builtin bypassing functions and aliases. .SS Information .TP .B type \fIname\fR... Show how names would be interpreted. .TP .B which \fIname\fR... Show path to executables. .TP .B hash Command hash table (currently no-op). .TP .B history \fR[\fB\-c\fR] Show or clear command history. .TP .B help Print available builtins. .SS Job Control .TP .B jobs \fR[\fB\-l\fR] List background jobs. .TP .B fg \fR[\fI%job\fR] Bring job to foreground. .TP .B bg \fR[\fI%job\fR] Resume job in background. .TP .B wait \fR[\fIpid\fR...] Wait for background processes. .TP .B kill \fR[\fB\-\fIsignal\fR] \fIpid\fR... Send signal to process. Supports %jobid. .TP .B disown \fR[\fIjob\fR] Remove job from job table. .TP .B trap \fR[\fIaction\fR] [\fIsignal\fR...] Set signal handlers. Signals: EXIT, INT, TERM, HUP, etc. .SS Shell Control .TP .B exit \fR[\fIn\fR] Exit shell with status n (default 0). .TP .B return \fR[\fIn\fR] Return from function with status n. .SS Benchmarking .TP .B time \fR[\fB\-n\fR \fIiter\fR] [\fB\-w\fR \fIwarmup\fR] [\fB\-v\fR] [\fB\-q\fR] [\fB\-H\fR] \fIcommand\fR Time command execution with optional benchmarking. \fB\-n\fR run multiple iterations for statistical analysis, \fB\-w\fR number of warmup runs (discarded), \fB\-v\fR verbose output (detailed resource usage), \fB\-q\fR quiet (suppress command output), \fB\-H\fR show histogram of timing distribution. .PP Default output format: .RS 0.00s user 0.00s sys 2336 KB 0.101s total .RE .PP With \fB\-n\fR, shows statistics: mean, median, stddev, min, max, percentiles. .SS Miscellaneous .TP .B let \fIexpr\fR Evaluate arithmetic expression. .TP .B chpw Change password (handled internally). .SH SHELL OPTIONS Set with \fBset \-o\fR \fIoption\fR or \fBset \-\fIx\fR. Unset with \fBset +o\fR \fIoption\fR or \fBset +\fIx\fR. .TP .BR errexit " (" \-e ")" Exit immediately if a command exits with non-zero status. .TP .BR nounset " (" \-u ")" Treat unset variables as an error. .TP .BR xtrace " (" \-x ")" Print commands and arguments as they are executed. .TP .B pipefail Return value of pipeline is the status of the last command to exit with non-zero status, or zero if all succeed. .SH LINE EDITING .B zish uses vim-style modal editing by default. The cursor shape changes between modes: block in normal mode, bar in insert mode. .SS Insert Mode (Default) .TP .BR Ctrl\-A Move to beginning of line .TP .BR Ctrl\-B Move cursor left .TP .BR Ctrl\-C Cancel (clear input or exit if empty) .TP .BR Ctrl\-D Exit shell (EOF) .TP .BR Ctrl\-E Move to end of line .TP .BR Ctrl\-F Move cursor right .TP .BR Ctrl\-H ", " Backspace Delete character before cursor .TP .BR Ctrl\-J ", " Alt\-Enter Insert newline (multiline input) .TP .BR Ctrl\-K Kill from cursor to end of line .TP .BR Ctrl\-L Clear screen .TP .BR Ctrl\-M ", " Enter Execute command / submit input .TP .BR Ctrl\-N Next history entry .TP .BR Ctrl\-P Previous history entry .TP .BR Ctrl\-R Reverse incremental history search .TP .BR Ctrl\-S Forward incremental history search .TP .BR Ctrl\-T Transpose characters .TP .BR Ctrl\-U Kill from cursor to beginning of line .TP .BR Ctrl\-W Delete word backward .TP .BR Ctrl\-Y Paste from kill buffer .TP .BR Ctrl\-Z Suspend shell .TP .BR Tab Trigger tab completion .TP .BR Shift\-Tab Cycle completions backward .TP .BR Ctrl\-Right Move word forward / accept one word of ghost text .TP .BR Ctrl\-Left Move word backward .TP .BR Up / Down Navigate command history .TP .BR Home / End Move to beginning/end of line .TP .BR Alt\-. Insert last argument from previous command .TP .BR Escape Enter vi normal mode .SS Normal Mode (Vi) Enter normal mode by pressing .BR Escape . .TP .BR i ", " a Enter insert mode (before/after cursor) .TP .BR I ", " A Enter insert mode at beginning/end of line .TP .BR o ", " O Open line below/above and enter insert mode .TP .BR h ", " l Move cursor left/right .TP .BR j ", " k Navigate history down/up .TP .BR w ", " b ", " e Move forward/backward by word, end of word .TP .BR W ", " B ", " E Move by WORD (whitespace-delimited) .TP .BR 0 ", " ^ Move to beginning of line / first non-blank .TP .BR $ Move to end of line .TP .BR x ", " X Delete character at/before cursor .TP .BR D Delete from cursor to end of line .TP .BR dd Delete entire line .TP .BR dw ", " d$ Delete word / to end of line .TP .BR C Change from cursor to end of line .TP .BR cc ", " S Change entire line .TP .BR cw Change word .TP .BR r\fIchar\fR Replace character under cursor with \fIchar\fR .TP .BR / Search history forward .TP .BR ? Search history backward .TP .BR n ", " N Repeat search forward/backward .TP .BR gg ", " G Go to first/last history entry .TP .BR u Undo .TP .BR p ", " P Paste after/before cursor .SH COMPLETION .B zish provides context-aware tab completion with ghost text prediction. .SS Completion Types .IP \(bu 2 Commands (builtins, aliases, executables in PATH) .IP \(bu 2 File and directory paths (with trailing slash for directories) .IP \(bu 2 Variables ($VAR) .IP \(bu 2 Git branches (local, remote, packed-refs, tags, stash entries) .IP \(bu 2 Git subcommands .IP \(bu 2 Docker containers and images .IP \(bu 2 Make targets (from Makefile) .IP \(bu 2 Just recipes (from justfile) .IP \(bu 2 Systemctl units .IP \(bu 2 SSH hosts (from ~/.ssh/config) .IP \(bu 2 Process IDs and signal names (for kill) .IP \(bu 2 Zig build targets .IP \(bu 2 Cargo binaries .IP \(bu 2 npm scripts .IP \(bu 2 Command flags with descriptions (parsed from \-\-help output) .IP \(bu 2 Pipe targets (common commands after |) .IP \(bu 2 History-based prefix matching .SS Ghost Text As you type, zish suggests the rest of the command from your history and from completion candidates. The part completing the token you are typing is shown in cyan; the rest is italic, so a suggestion never reads as committed input. .TP .BR Right ", " End Accept the full prediction .TP .B Ctrl\-Right Accept one word .TP .B Alt\-E Accept one character .TP .B Ctrl\-O Toggle ghost text on or off .PP There is no model involved. Earlier versions shipped a GGUF inference engine for this; it was removed in favour of history matching, which is where the useful suggestions came from anyway. .SS Keybindings .B ~/.zish/keybindings.json .PP Custom keybinding overrides loaded at shell startup. .SH ENVIRONMENT .TP .B ZISH_TRACE_FD File descriptor to write session trace records to, instead of 3. Must be a number greater than 2, or tracing is disabled. See .B TRACING above. .TP .B HOME Home directory for cd and tilde expansion. .TP .B PATH Colon-separated list of directories to search for commands. .TP .B PS1 Primary prompt string (supports escape sequences). .TP .B OLDPWD Previous working directory (for cd \-). .TP .B PWD Current working directory. .TP .B ? Exit status of last command. .TP .B $ Process ID of shell. .TP .B ! Process ID of last background command. .TP .B # Number of positional parameters. .TP .B @ All positional parameters as separate words. .TP .B * All positional parameters as single word. .TP .B 0 Name of shell or script. .TP .B 1, 2, ... Positional parameters. .SH PROMPT ESCAPES The PS1 variable supports these escape sequences: .TP .B \eu Username .TP .B \eh Hostname (short) .TP .B \eH Hostname (full) .TP .B \ew Working directory (~ for home) .TP .B \eW Basename of working directory .TP .B \e$ # if root, $ otherwise .TP .B \en Newline .TP .B \et Time (HH:MM:SS) .TP .B \ed Date .TP .B \ee Escape character .TP .B \e[, \e] Begin/end non-printing sequence .SH FILES .TP .B ~/.zishrc Startup file for interactive shells. .TP .B ~/.zish_history Command history file. .TP .B ~/.zish/keybindings.json Custom keybinding overrides. .SH EXIT STATUS The exit status is that of the last command executed, or 0 if no command was executed. With \fB\-c\fR, the exit status is that of the command string. .SH EXAMPLES Run a command: .RS .nf zish \-c 'echo hello' .fi .RE .PP Benchmark a command with histogram: .RS .nf time \-n 100 \-w 5 \-H ./myprogram .fi .RE .RE .PP Process substitution: .RS .nf paste <(cut \-f1 file1) <(cut \-f2 file2) .fi .RE .PP Array operations: .RS .nf files=(*.txt) for f in "${files[@]}"; do echo "Processing $f" done .fi .RE .PP Natural language to command: .RS .nf ?find all rust files larger than 1MB .fi .RE .SH SEE ALSO .BR bash (1), .BR zsh (1), .BR sh (1), .BR screen (1), .BR tmux (1) .SH AUTHOR Written in Zig.