Why Switch to Zsh?
Smarter Tab Completion
Zsh completes command flags, git branches, SSH hostnames, Docker container names, and more — with an interactive menu you can arrow-key through.
Spell Correction
setopt CORRECT catches typos like grpe and asks "Did you mean grep?" before running the wrong command.Extended Globbing
Recursive
**/*, negation ^*.log, glob qualifiers like *(Om) for sort-by-mtime, and null glob *(N) — no more broken scripts on empty matches.Better History
Shared history across all terminal sessions, timestamps, deduplication, and per-session append so you never lose commands from parallel windows.
Prompt Theming
Full PROMPT_SUBST support and a rich theme ecosystem — Powerlevel10k renders git status, language versions, exit codes, and more, all in milliseconds.
Plugin Ecosystem
oh-my-zsh, Zinit, and Prezto give you hundreds of plugins: autosuggestions, syntax highlighting, fzf integration, and per-tool completions.
Directory Stack
setopt AUTO_PUSHD turns every cd into a push, letting you jump back with cd -2 or ~3 — a permanent nav history.Suffix Aliases
alias -s pdf=zathura lets you open files just by typing their name — no open or xdg-open needed.macOS Default
Apple switched macOS from Bash 3.2 to Zsh as the default login shell in macOS Catalina (2019) — it's already there, no install needed.
Zsh vs Bash — Feature Comparison
| Feature | Bash | Zsh |
|---|---|---|
| Tab completion | Basic | Advanced (menus, context-aware) |
| Spelling correction | ✗ | ✓ (setopt CORRECT) |
| Recursive glob ** | Requires globstar | Built-in, extended qualifiers |
| Glob negation | ✗ | ✓ (^*.log with EXTENDED_GLOB) |
| Array indexing | 0-indexed, sparse | 1-indexed, slice syntax |
| Prompt expansion | Limited | Full PROMPT_SUBST, RPROMPT |
| Plugin ecosystem | Minimal | oh-my-zsh, Zinit, Prezto |
| Float arithmetic | Requires bc | Built-in (zsh/mathfunc) |
| History sharing | Per-session only | Cross-session with SHARE_HISTORY |
| Suffix aliases | ✗ | ✓ (alias -s ext=app) |
| Global aliases | ✗ | ✓ (expand anywhere in line) |
| Directory stack | Manual pushd/popd | AUTO_PUSHD, ~1 ~2 expansion |
| Parameter flags | Limited | ${(U)(L)(q)(f)(o)(u)…} flags |
| POSIX compliance | ✓ default | emulate sh for compat |
| Scripting portability | More portable | Prefer bash for scripts |
Quick Reference
Keyboard Shortcuts (emacs mode)
Ctrl+A beginning of line
Ctrl+E end of line
Ctrl+U kill to beginning
Ctrl+K kill to end
Ctrl+W kill word backward
Alt+. insert last arg of prev cmd
Ctrl+R history search (fzf if installed)
Ctrl+Z suspend / fg to resume
Tab Tab show completion menu
Useful One-Liners
# time a command
time cmd
# run in background, disowned
cmd &!
# check exit codes of each pipe stage
cmd1 | cmd2; echo ${pipestatus[@]}
# repeat command N times
repeat 5 cmd
# jump to frecent dir (z plugin)
z proj
Origin
Who Created Zsh?
Zsh was created by Paul Falstad in 1990 while he was a student at Princeton University. He released the first version (0.1) in late 1990 on the comp.sources.misc Usenet newsgroup.
The name "zsh" comes from Zhong Shao, a Yale professor whose login name was zsh — Falstad thought it made a good shell name. Shao himself had nothing to do with creating the shell.
Falstad later said he wrote Zsh to have a shell "with all the features I wanted" — pulling from csh, ksh, and bash, then adding his own ideas on top.
Shell Family Tree
1971 sh — Thompson shell (Bell Labs)
1979 sh — Bourne shell (replaces Thompson)
1983 csh — C shell (Bill Joy, BSD)
1983 ksh — KornShell (David Korn, AT&T)
1989 bash — Bourne Again Shell (FSF/GNU)
1990 zsh — Z Shell (Paul Falstad)
1993 tcsh — TENEX C Shell update
2003 fish — Friendly Interactive Shell
2019 zsh — becomes macOS default shell
Version Timeline
1990
Zsh 0.1 — Paul Falstad posts first version to comp.sources.misc at Princeton. Features prompt customization, command completion, and spelling correction from day one.
1992
Zsh 2.0 — Richard Coleman takes over maintenance. Adds history sharing between sessions, extended globbing, and associative arrays. Project moves to a public mailing list.
1995
Zsh 2.6 / 3.0 — Zoltan Hidvegi joins as co-maintainer. Module system introduced (zsh/mathfunc, zsh/zle, zsh/complete). ZLE (Zsh Line Editor) becomes a first-class subsystem.
1997
Zsh 3.1 (development) — Peter Stephenson becomes lead. Introduces zstyle for configurable completion, the zcompdump cache, and the completion system (compsys) that still ships today.
2000
Zsh 4.0 — Major completion rewrite. The new compsys (compdef, compctl replacement) ships, giving context-aware completion with zstyle configuration. This is the completion engine still in use.
2003
Zsh 4.2 — Stable release with improved Unicode support, parameter flags, and the prompt theme system formalized. oh-my-zsh-style themes become practical.
2009
oh-my-zsh launched — Robby Russell releases oh-my-zsh on GitHub. Within years it becomes the dominant way to manage Zsh config — 170,000+ GitHub stars today.
2015
Zsh 5.0 — Unicode fully supported, multibyte characters in prompts and completion. zsh/zutil module adds zparseopts for getopts-style flag parsing.
2017
Zsh 5.4 — Pattern matching improvements, new modules, and stability fixes. Zinit (formerly zplugin) emerges as a faster alternative to oh-my-zsh for plugin management.
2019
macOS Catalina — Zsh becomes default — Apple replaces Bash 3.2 (GPLv2) with Zsh 5.7 (MIT) as the default shell for new macOS accounts, instantly making Zsh the most widely distributed interactive shell in the world.
2022
Zsh 5.9 — Current stable release. Continued improvements to completion, Unicode, and the module system. The codebase is now over 30 years old and actively maintained by a small core team.
Today
Standard on macOS, popular on Linux — Zsh ships by default on macOS, is the preferred shell for millions of developers, and is the default in many Docker images and dev containers. oh-my-zsh alone has over 170K GitHub stars.
Zsh Configuration (.zshrc)
Options (setopt)
setopt AUTO_CD # cd by typing dirname
setopt CORRECT # spell correction
setopt GLOB_DOTS # glob matches dotfiles
setopt EXTENDED_GLOB # *, **, ^, (#…)
setopt NO_CASE_GLOB # case-insensitive glob
setopt HIST_IGNORE_DUPS # no duplicate history
setopt SHARE_HISTORY # sync across sessions
setopt AUTO_PUSHD # pushd automatically
setopt PUSHD_IGNORE_DUPS # no dup dirstack
History Config
HISTFILE=~/.zsh_history
HISTSIZE=50000
SAVEHIST=50000
setopt HIST_EXPIRE_DUPS_FIRST
setopt HIST_FIND_NO_DUPS
setopt INC_APPEND_HISTORY # write immediately
# Ctrl+R — interactive history search
# !! — repeat last command
# !$ — last argument of prev cmd
Completion Setup
autoload -Uz compinit
compinit
# use menu selection
zstyle ':completion:*' menu select
# case-insensitive matching
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'
# group by category
zstyle ':completion:*' group-name ''
# colored completion
zstyle ':completion:*' list-colors ''
Key Bindings
bindkey -e # emacs mode (default)
bindkey -v # vi mode
bindkey '^[[A' history-search-backward
bindkey '^[[B' history-search-forward
bindkey '^[.' insert-last-word
bindkey '^U' backward-kill-line
Zsh-Specific Syntax
Extended Globbing
setopt EXTENDED_GLOB
ls **/*.py # recursive glob
ls ^*.py # negate: not .py files
ls *.(py|js) # alternation
ls *.py(.) # plain files only
ls *.py(Om) # sort by mtime oldest
ls *.log(Lm+1) # files > 1MB
ls *(/) # directories only
ls *(N) # null glob (no error)
Arrays (1-indexed)
arr=(a b c d)
echo $arr[1] # a (1-indexed!)
echo $arr[-1] # d (last)
echo $arr[2,3] # b c (slice)
echo ${#arr} # length: 4
echo ${arr:u} # UPPERCASE
echo ${(j:,:)arr} # join: a,b,c,d
echo ${(s:,:)csv} # split on comma
Parameter Flags
${(U)var} # UPPERCASE
${(L)var} # lowercase
${(C)var} # Capitalize Words
${(q)var} # shell-quote
${(Q)var} # unquote
${(r:10:)var} # right-pad to 10
${(l:10:)var} # left-pad to 10
${(f)var} # split by newlines → array
${(o)arr} # sort array
${(u)arr} # unique elements
Aliases
# regular alias
alias ll='ls -lah'
# global alias (expands anywhere)
alias -g G='| grep'
alias -g L='| less'
alias -g T='| tail -f'
dmesg G error # grep errors
# suffix alias (open by extension)
alias -s pdf=evince
alias -s py=python3
doc.pdf # opens in evince
Prompt Customization
autoload -Uz colors && colors
setopt PROMPT_SUBST
# simple prompt with git branch
PROMPT='%F{blue}%~%f $(git branch --show-current 2>/dev/null | sed "s/.*/ [&]/")
%F{green}❯%f '
# prompt codes
# %~ = cwd %n = user %m = host
# %? = exit code %# = $ or #
# %F{color}…%f = foreground color
Floating Point
zmodload zsh/mathfunc
typeset -F x=3.14
echo $(( x * 2.0 )) # 6.28
echo $(( sqrt(2.0) )) # 1.4142...
echo $(( log(M_E) )) # 1.0 (natural log of e)
Functions & Autoload
# define inline
mkcd() { mkdir -p "$1" && cd "$1"; }
# autoload from fpath
fpath=(~/.zfuncs $fpath)
autoload -Uz myfunc # lazy-load
# hook functions
chpwd() { ls -la; } # runs on cd
precmd() { } # before prompt
preexec() { } # before each cmd
Directory Stack
setopt AUTO_PUSHD
setopt PUSHD_IGNORE_DUPS
DIRSTACKSIZE=20
dirs -v # show stack
cd -2 # go to entry 2
popd # pop top
# ~1 ~2 etc expand to stack entries
Oh-My-Zsh Plugins
git
Hundreds of git aliases: gst, gco, gcb, glog, gp, gl, etc.
z
Jump to frecent directories:
z proj jumps to your most-used projects dir.docker
Tab-completion for docker commands, images, containers, and compose.
kubectl
Kubectl completion + k alias. Show current context in prompt.
zsh-autosuggestions
Fish-style greyed-out suggestions from history. Press → to accept.
zsh-syntax-highlighting
Colors commands as you type: green for valid, red for unknown.
fzf
Fuzzy file/history search. Ctrl+R for history, Ctrl+T for files.
nvm
Auto-switch Node version when entering directories with .nvmrc.
sudo
Press Esc twice to prepend sudo to the current command.
web-search
Search from terminal:
google zsh tips, ddg bash arrays.extract
Universal extract:
extract file.tar.gz — detects format automatically.colored-man-pages
Colorizes man pages for much better readability.
Popular Themes & Plugin Managers
Powerlevel10k
# install
git clone --depth=1 \
https://github.com/romkatv/powerlevel10k.git \
${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/themes/powerlevel10k
# .zshrc
ZSH_THEME="powerlevel10k/powerlevel10k"
# then: p10k configure
Starship (cross-shell)
# install
curl -sS https://starship.rs/install.sh | sh
# add to .zshrc
eval "$(starship init zsh)"
# configure ~/.config/starship.toml
# shows git, language, exit code etc
Zinit (fast plugin manager)
# install Zinit
bash -c "$(curl -fsSL https://git.io/zinit-install)"
# .zshrc
zinit light zsh-users/zsh-autosuggestions
zinit light zsh-users/zsh-syntax-highlighting
zinit ice depth=1
zinit light romkatv/powerlevel10k
zsh — ~/projects/myapp
bck-i-search:
~/projects/myapp main ❯