Your Mac switched you from bash to zsh, and your ~/.zshrc is a few hundred lines pasted from a framework install. Lines such as setopt, bindkey, and zstyle look opaque, and *(om[1]) looks like line noise. This guide shows what stock zsh does on its own, with no plugins, so you can read your own config and decide what belongs in it.
It covers the 20% of zsh that does 80% of the work: globbing and glob qualifiers, parameter expansion and arrays, completion, history expansion, options, key bindings, and ~/.zshrc. A searchable command reference and a curated set of videos, podcasts, books, and online resources close the article.
What You’ll Learn
- When zsh is the right shell, and when bash or Nushell fits better.
- How to match files by name, type, size, and age with globs and glob qualifiers.
- How to change strings and split, join, count, and index arrays with parameter expansion.
- How to turn on completion and make the Tab menu work for you.
- How to repeat and fix earlier commands with history expansion.
- How to try options and key bindings, then keep the ones you like in
~/.zshrc.
When to Use Zsh, Bash, or Nushell
Zsh (the Z shell) is an interactive shell and scripting language that reads most bash syntax and adds its own: recursive globs, glob qualifiers, expansion flags, and a programmable completion system. It has been the default login shell on macOS since Catalina in 2019.
Where Zsh Fits Best
- Typing commands all day. Completion, history expansion, and key bindings save keystrokes.
- Finding files without a separate tool.
**/*.logand*(om[1])replace manyfindinvocations. - Shaping strings and arrays in place, without
sed,cut, orawk. - Reading the shell on a Mac, where zsh is already the default.
When Bash Fits Better
- Scripts that run on servers, in containers, and in CI. Bash is installed almost everywhere, and
#!/bin/bashis the safe default for shared scripts. - Following tutorials and Stack Overflow answers written for bash. Most work in zsh, but arrays start at 1 in zsh and at 0 in bash, and unquoted variables don’t split on spaces in zsh.
When Nushell Fits Better
- Working with JSON, CSV, and other structured data. Nushell passes tables between commands instead of text. Learn Nushell covers it.
- Writing cross-platform scripts that also run on Windows.
Install zsh with your package manager if it isn’t already your shell. On macOS, it already is. On Debian or Ubuntu, this needs the network and administrator rights:
Runs on your machine, not in the practice shell
sudo apt install zshThe examples below run in a small practice home directory. It holds a note, a to-do list, three photos, a tiny website, and three log files:
notes.txt
todo.md
photos/IMG_2041.JPG
photos/IMG_2042.JPG
photos/IMG_2043.JPG
projects/website/index.html
projects/website/style.css
projects/website/app.js
logs/error.log
logs/app.log
logs/app-2026-09.logThe files are one day apart in age, oldest first in the order listed, so logs/app-2026-09.log is the newest and the largest. Every example prints its result with zsh’s own print command, so the output you see matches the output in this guide. Each example starts in the home directory.
Match Files with Globs and Glob Qualifiers
A glob is a pattern the shell expands into matching file names before the command runs. print -l prints one argument per line, which makes it the simplest way to see what a glob matches.
List every log file in any directory below the current one with the recursive glob **/:
print -l **/*.loglogs/app-2026-09.log
logs/app.log
logs/error.logZsh sorts matches by byte value, so - sorts before . and app-2026-09.log comes first.
Add a glob qualifier in parentheses at the end of a pattern to filter matches by file type, size, or age. (/) keeps directories:
print -l *(/)logs
photos
projects(.) keeps plain files, so **/*(.) lists every file and no directories:
print -l **/*(.)logs/app-2026-09.log
logs/app.log
logs/error.log
notes.txt
photos/IMG_2041.JPG
photos/IMG_2042.JPG
photos/IMG_2043.JPG
projects/website/app.js
projects/website/index.html
projects/website/style.css
todo.mdSort and pick with o and a subscript. om sorts by modification time, newest first, and [1] keeps the first match, so this prints the newest log:
print -l logs/*(om[1])logs/app-2026-09.logFilter by size with L. Lk+10 keeps files larger than 10 KB:
print -l **/*(Lk+10)logs/app-2026-09.log
logs/app.logTurn on extended_glob for negation. ^error.log matches everything except error.log:
setopt extended_glob
print -l logs/^error.loglogs/app-2026-09.log
logs/app.logWhat you get: one-line answers to “which files are newest, biggest, or of this type” without reaching for find.
Shape Strings and Arrays with Parameter Expansion
Parameter expansion changes a value as the shell reads it. Zsh adds flags in parentheses right after the opening brace. (U) converts to uppercase:
x=hello
print ${(U)x}HELLOSplit a string into an array with (s:,:), where the character between the colons is the separator. Then index and count the array. Zsh arrays start at 1:
csv=web-01,db-01,cache-01
hosts=(${(s:,:)csv})
print $hosts[1]
print ${#hosts}web-01
3Join an array back into one string with (j:,:):
hosts=(web-01 db-01 cache-01)
print ${(j: | :)hosts}web-01 | db-01 | cache-01Combine globs and arrays. Store matches in an array, then use the :t modifier to keep only each file’s name:
logs=(logs/*.log)
print ${#logs}
print ${logs:t}3
app-2026-09.log app.log error.logThe same modifiers pull a path apart: :t is the tail, :r drops the extension, :e keeps only the extension, and :h is the head:
f=photos/IMG_2041.JPG
print ${f:t} ${f:r} ${f:e} ${f:h}IMG_2041.JPG photos/IMG_2041 JPG photostypeset -p shows a variable’s type and value, which helps when an array isn’t what you expect:
hosts=(web-01 db-01 cache-01)
typeset -p hoststypeset -a hosts=( web-01 db-01 cache-01 )Turn On Completion
Zsh’s completion system loads with two lines that most ~/.zshrc files already hold:
autoload -Uz compinit
compinitautoload -Uz marks compinit as a function zsh loads from its function path the first time you call it. compinit then sets up completion for hundreds of commands.
With completion on, type cd pro and press Tab. Zsh completes the directory name to cd projects/. Press Tab again to step into website/. Type print -l logs/ and press Tab twice to see every log file.
The zstyle command configures completion. This line turns Tab into a menu you move through with the arrow keys:
zstyle ':completion:*' menu selectPrint every completion style that’s set with zstyle -L:
zstyle -L ':completion:*'zstyle ':completion:*' menu selectRepeat and Fix Commands with History Expansion
History expansion reuses parts of earlier commands. Zsh shows the expanded command line before it runs, so you can see what ran.
!! repeats the last command, and !$ stands for its last word:
print -l logs/*.log
!!
print -l !$^old^new reruns the last command with the first old replaced by new. Here it turns app.log into error.log:
print -l logs/app.log
^app^errorList recent commands with their history numbers:
fc -lPress Ctrl-r to search your history as you type, and press Up to step back through earlier commands.
Try Options
Options change how the shell behaves. setopt turns one on, unsetopt turns it off, and [[ -o name ]] tests it. With autocd on, typing a directory’s name changes into it:
setopt autocd
logs
cdThe prompt changes to reader@practice ~/logs % after logs, and cd with no argument returns home. Check and clear the option:
setopt autocd
[[ -o autocd ]] && print on || print off
unsetopt autocd
[[ -o autocd ]] && print on || print offon
offRun setopt with no arguments to list the options that differ from the default. setopt correct makes zsh offer a fix for a mistyped command: type sl and zsh asks zsh: correct 'sl' to 'ls' [nyae]?.
Change Key Bindings
Zsh’s line editor ships with emacs keys (Ctrl-a for line start, Ctrl-e for line end) and vi keys. bindkey shows and changes them. Ask what a key does:
bindkey '^R'"^R" history-incremental-search-backwardSwitch to vi keys, check which keymap is active, then switch back:
bindkey -v
bindkey -lL main
bindkey -e
bindkey -lL mainbindkey -A viins main
bindkey -A emacs mainIn vi mode, press Esc to move into command mode, then 0 to jump to the start of the line and i to type again.
Keep What You Like in ~/.zshrc
Every interactive zsh reads ~/.zshrc at startup. Print the practice home’s copy with zsh’s own file reader, $(<file):
print -r -- "$(<~/.zshrc)"# ~/.zshrc: the practice shell's startup file. Changes last until Reset.
PROMPT='reader@practice %~ %# '
autoload -Uz compinit && compinit
zstyle ':completion:*' menu select
HISTSIZE=1000Add a line and apply it to the current shell with source, without opening a new terminal:
print 'setopt autocd' >> ~/.zshrc
source ~/.zshrc
[[ -o autocd ]] && print on || print offonLoad an extra function the same way compinit loads. zmv renames files by pattern, and zsh doesn’t know it until you autoload it. whence -w shows the change:
whence -w zmv
autoload -Uz zmv
whence -w zmvzmv: none
zmv: functionzmv -n prints the renames without doing them. (*) captures part of each name, and $1 reuses it:
autoload -Uz zmv
zmv -n 'photos/IMG_(*).JPG' 'photos/photo-$1.jpg'mv -- photos/IMG_2041.JPG photos/photo-2041.jpg
mv -- photos/IMG_2042.JPG photos/photo-2042.jpg
mv -- photos/IMG_2043.JPG photos/photo-2043.jpgDrop -n to rename the files for real. Try a line in the current shell first, then add it to ~/.zshrc once it does what you want.
Practice Zsh
Zsh sticks once you type it. A practice shell that runs real zsh in this page is on its way to this section. Until it arrives, run the drill below in your own terminal, in a scratch directory with a few files of your own.
The drill takes about ten minutes:
- List every plain file below the current directory with
print -l **/*(.). - Print the newest file with the
omqualifier and a[1]subscript. - List the files larger than 10 KB with
Lk+10. - Split
web-01,db-01,cache-01into an array, then print its length and its first item. - Turn on
autocd, change into a directory by typing its name, then turn it off. - Switch to vi keys with
bindkey -v, edit a line, then switch back withbindkey -e. - Load
zmvand preview a rename withzmv -n.
Command Reference
Verified with zsh 5.9.2, no plugins. Search the reference to find a command fast. Each entry shows the command, then what it does.
Globbing
- print -l **/*.log - List matching files in every directory below the current one
- print -l **/*(.) - List plain files only, no directories
- print -l *(/) - List directories only
- print -l logs/*(om[1]) - Print the newest file: sort by modification time, keep the first
- print -l **/*(Lk+10) - List files larger than 10 KB
- print -l photos/*(On) - List files in reverse name order
- setopt extended_glob - Turn on extended patterns such as negation
- print -l logs/^error.log - List every file except one (needs extended_glob)
Parameter Expansion and Arrays
- print ${(U)x} - Print a value in uppercase
- arr=(${(s:,:)x}) - Split a string into an array on commas
- print ${(j:,:)arr} - Join an array into one string with commas
- print ${#arr} - Count the items in an array
- print $arr[1] - Print the first item of an array (arrays start at 1)
- print ${f:t} - Print the last part of a path
- print ${f:r} - Print a path without its extension
- typeset -p arr - Show a variable’s type and value
History
- !! - Repeat the last command
- !$ - Reuse the last word of the last command
- ^old^new - Rerun the last command with old replaced by new
- fc -l - List recent commands with their history numbers
Options
- setopt - List the options that differ from the default
- setopt autocd - Turn on an option
- unsetopt autocd - Turn off an option
- [[ -o autocd ]] - Test whether an option is on
Key Bindings
- bindkey -v - Switch to vi keys
- bindkey -e - Switch to emacs keys
- bindkey '^R' - Show what a key does
- bindkey -lL main - Show which keymap is active
Completion
- autoload -Uz compinit && compinit - Load and start the completion system
- zstyle ':completion:*' menu select - Turn Tab into a menu you move through with the arrow keys
- zstyle -L - List every style that’s set
Functions and Startup Files
- autoload -Uz zmv - Load a function from zsh’s function path on first use
- zmv -n 'photos/IMG_(*).JPG' 'photos/photo-$1.jpg' - Preview a pattern rename without doing it
- whence -w zmv - Show whether a name is a builtin, function, command, or none
- source ~/.zshrc - Run a file’s commands in the current shell
Examples
Each example runs on the practice files and shows what zsh prints.
print -l **/*(.om[1])logs/app-2026-09.logprint -l **/*.(html|css)projects/website/index.html
projects/website/style.csshosts=(web-01 db-01 cache-01); print -l ${(o)hosts}cache-01
db-01
web-01files=(photos/*.JPG); print -l ${files:t:r}IMG_2041
IMG_2042
IMG_2043Plugins and Frameworks on Your Machine
Plugins and frameworks are where many ~/.zshrc files come from. They add commands and behavior that stock zsh doesn’t have, so try them on your own machine once the basics feel familiar.
Install the two most popular plugins with Homebrew. This needs the network:
Runs on your machine, not in the practice shell
brew install zsh-autosuggestions zsh-syntax-highlightingOr clone a plugin with git:
Runs on your machine, not in the practice shell
git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestionsThen add a source line for each plugin to the end of ~/.zshrc, using the path the install step printed. The Beyond the Basics section links to each plugin and framework.
Learn Zsh: Beyond the Basics
With globs, expansion, completion, and a tidy ~/.zshrc in hand, these resources take you further. Start with a video, then keep the zsh manual open while you edit your config. The popular add-ons are worth reading about: oh-my-zsh bundles themes and hundreds of plugins, zsh-autosuggestions suggests commands from your history as you type, and zsh-syntax-highlighting colors commands as you type them.
Video
- You’re probably not using zsh’s best feature (yet) by Bread on Penguins, a short tour of globbing in zsh.
- Customize your ZSH (auto-suggestions, prompt theme, syntax highlighting and more) by The Rad Lectures, a setup of
zsh-autosuggestionsandzsh-syntax-highlighting. - Ten
oh-my-zshplugins for productive developers by Travis Media, a tour of the framework’s most installed plugins.
Audio and Podcasts
- Changelog Interviews #61 with Robby Russell on creating
oh-my-zshand growing its community. - Changelog Interviews #465, a return visit with Robby Russell on more than ten years of maintaining
oh-my-zsh.
Books
- From Bash to Z Shell: Conquering the Command Line by Oliver Kiddle, Jerry Peek, and Peter Stephenson, a book-length guide for bash users written by zsh developers.
- Moving to zsh by Armin Briegel, a short guide for Mac users who inherited zsh with macOS Catalina.
- A User’s Guide to the Z-Shell by Peter Stephenson, free online, from first steps to completion and the line editor.
Online
- The Z Shell Manual, the official reference for every option, builtin, and expansion.
- Expansion in the zsh manual, the full list of glob qualifiers and expansion flags.
oh-my-zsh, the most popular zsh framework.- zsh-autosuggestions and zsh-syntax-highlighting, the two plugins most configs add first.
- awesome-zsh-plugins, a curated list of zsh frameworks, plugins, and themes.
Related Content
- Learn Nushell covers a shell built on structured data, for the tasks where zsh’s text pipelines get awkward.
- Learn Tmux pairs with zsh: run a shell in each pane and keep sessions alive after you disconnect.
- Configure your shell walks through shell startup files and settings.
- What Is Zoxide? explains a smarter
cdthat works in zsh.
Related Articles by Category
🎓 Learn X
Begin learning new software frameworks, languages, tools, and techniques then leave with resources for further study.
💻 Development
Software development practices and techniques.

Comments #