Zsh Scripting Cheat Sheet
Zsh-specific syntax for arrays, globbing, parameter expansion, and prompt/plugin config that goes beyond POSIX sh scripting.
Arrays (1-indexed!)
Zsh arrays are 1-indexed by default, unlike bash.
arr=(one two three)echo $arr[1] # "one" (1-indexed, unlike bash's 0-index)echo ${arr[1]} # same, braces optional for simple indexecho ${#arr} # 3, length of arrayecho ${arr[-1]} # "three", negative indices work# Slicingecho ${arr[1,2]} # "one two"# Append / iteratearr+=(four)for item in $arr; do echo "$item"done# Associative arraystypeset -A colorscolors=(red "#f00" green "#0f0")echo $colors[red] # "#f00"
Extended Globbing
Enable setopt extendedglob for powerful pattern matching without find.
setopt extendedglobls *.txt~backup.txt # all .txt except backup.txtls **/*.py # recursive glob, any depthls *(.) # only regular files in cwdls *(/) # only directoriesls *(.om[1]) # most recently modified regular filels *.log(.N) # .N suppresses "no match" error instead of failing# Qualifiers: (.)=file (/)=dir (@)=symlink (x)=executable# (Lxx)=size, (mxx)=modified within xx days
Parameter Expansion
Zsh-flavored modifiers for defaults, substitution, and case conversion.
name=""echo ${name:-default} # use "default" if name is unset or emptyecho ${name:=default} # same, and also assigns name=defaultpath="/usr/local/bin/zsh"echo ${path:h} # "/usr/local/bin" (head, like dirname)echo ${path:t} # "zsh" (tail, like basename)echo ${path:e} # "" (extension)echo ${path:r} # "/usr/local/bin/zsh" (root, strip extension)str="Hello World"echo ${(L)str} # "hello world" lowercaseecho ${(U)str} # "HELLO WORLD" uppercaseecho ${(C)str} # "Hello World" capitalize each word# Splitting a string into an arraywords=(${(s: :)"a b c"})
Functions, Autoload & Prompt
Function definitions, autoloaded functions, and PS1-equivalent PROMPT.
greet() { local name=${1:-world} print "Hello, $name"}# Autoloaded functions (in fpath, loaded lazily)autoload -Uz compinit && compinitautoload -Uz colors && colors# PROMPT uses %-escapes instead of bash's \-escapesPROMPT='%F{cyan}%n@%m%f %~ %# '# %n=user %m=host %~=cwd(with ~) %#=# if root else %# Hooksprecmd() { print "runs before each prompt" }
Common setopt Flags
Behavior toggles that differ from bash and are commonly set in .zshrc.
- setopt AUTO_CD- cd into a directory just by typing its name
- setopt NULL_GLOB- unmatched globs expand to nothing instead of erroring
- setopt NO_CASE_GLOB- case-insensitive globbing
- setopt HIST_IGNORE_DUPS- don't record duplicate consecutive history entries
- setopt SHARE_HISTORY- share history live across all open sessions
- setopt AUTO_PUSHD- cd pushes the old dir onto the directory stack
- setopt CORRECT- suggest corrections for mistyped commands
- emulate sh -c '...'- run a snippet in POSIX-compatible mode
Custom Tab Completion (_arguments)
Write a real completion function for a custom tool using the modern compsys API.
#compdef mytool_mytool() { local -a subcmds subcmds=( 'build:compile the project' 'test:run the test suite' 'deploy:ship to production' ) _arguments -C \ '1:command:->cmds' \ '*::arg:->args' case $state in cmds) _describe 'command' subcmds ;; args) case $words[1] in deploy) _arguments '--env=[target environment]:env:(staging prod)' ;; esac ;; esac}compdef _mytool mytool# Quick inline completion without a full functioncompdef '_files -g "*.log"' tailog
Traps & Strict Error Handling
TRAP functions and the zsh-specific error-exit options for robust scripts.
TRAPINT() { print "Caught Ctrl-C, cleaning up..." return 130 # non-zero return from TRAPINT still lets the script exit}TRAPEXIT() { print "Always runs, like a shell-wide 'finally'"}# Fail fast, similar to bash's set -e, but with finer-grained controlsetopt ERR_EXIT # exit on any unhandled non-zero statussetopt PIPE_FAIL # a pipeline fails if any stage fails, not just the lastsetopt ERR_RETURN # inside a function, return instead of exiting the shell# Explicit per-command error handling without relying on ERR_EXITsome_cmd || { print "some_cmd failed: $?" >&2; exit 1 }
Process Substitution, Coprocesses & Job Control
Treat command streams as files, talk to a background process, and manage jobs.
# Process substitution: treat command output as a filediff <(sort file1.txt) <(sort file2.txt)comm -12 <(sort a.txt) <(sort b.txt)# Tee into multiple process substitutions at oncetee >(gzip > out.gz) >(wc -l) < input.txt > /dev/null# Coprocesses: a background process with a two-way pipecoproc mycalc { bc -l }print -p "2 + 2" # write to the coprocess's stdinread -p result # read from the coprocess's stdoutprint $result # "4"# Job controllong_task & # background jobjobs -l # list jobs with PIDsfg %1 # bring job 1 to foregrounddisown %1 # detach job (survives shell exit)wait %1 # block until job 1 finishes
Floating-Point Math & Tied Variables
zsh/mathfunc for real math, plus typeset -T to keep a scalar and array in sync.
zmodload zsh/mathfuncprint $(( sqrt(2) )) # 1.4142135623730951print $(( sin(3.14159/2) )) # ~1# Arithmetic context supports C-style ternaries(( x = 5 > 3 ? 10 : 20 ))print $x # 10# typeset -T ties a scalar and an array together (colon-split, like PATH)typeset -T MY_PATH my_path ':'my_path=(/usr/bin /usr/local/bin)print $MY_PATH # "/usr/bin:/usr/local/bin"MY_PATH="/opt/bin:/opt/sbin"print $my_path[1] # "/opt/bin", array stays in sync# Fixed-width float and integer typingtypeset -F 3 pi=3.14159265 # float, printed with 3 decimalstypeset -i base16 hex=16#ff # integer with a fixed output baseprint $hex # 255
Handy zmodload Modules
Built-in modules that replace external binaries or add scripting capabilities.
- zmodload zsh/mathfunc- adds sqrt/sin/log/etc. for use inside (( ))
- zmodload zsh/stat- stat(2) as a builtin, avoids spawning /usr/bin/stat
- zmodload zsh/datetime- strftime and $EPOCHSECONDS without calling date
- zmodload zsh/system- syserror, sysopen and other low-level syscall access
- zmodload zsh/zutil- zparseopts for getopts-style flag parsing in functions
- zmodload zsh/parameter- introspect functions, aliases and jobs as associative arrays
- zmodload zsh/pcre- Perl-compatible regex matching via pcre_match
- zmodload zsh/net/tcp- open raw TCP sockets directly from a script
Use zsh -xv script.sh (or set -x inside the script) to trace execution with expanded variables when a glob or parameter expansion isn't doing what you expect — zsh's expansion rules are powerful but easy to misread.