The default shell is fine for beginners. But if you type commands all day, you need a shell that works with you — not against you. Zsh with Oh My Zsh adds smart autocomplete, git status in your prompt, hundreds of plugins, and themes that make your terminal actually enjoyable to use.
:::note[TL;DR]
- Zsh is the modern shell (default on macOS since Catalina)
- Oh My Zsh is a framework with 300+ plugins and themes
- Best theme:
powerlevel10k(fast, customizable, informative) - Essential plugins: git, z, autosuggestions, syntax-highlighting
- Use
aliasand functions to automate common tasks :::
Why Switch to Zsh?
| Feature | Bash | Zsh |
|---|---|---|
| Tab completion | Basic | Smart, menu-based, case-insensitive |
| Auto-suggestions | No | Yes (with plugin) |
| Syntax highlighting | No | Yes (with plugin) |
| Git integration | Manual | Built-in, visual |
| Path expansion | /u/l/b → error | /u/l/b → /usr/local/bin |
| Shared history | Per session | All sessions |
| Globbing | Basic | Advanced patterns |
Zsh is now the default shell on macOS. If you’re still using bash, you’re working harder than you need to.
Installation
macOS
Zsh is pre-installed since macOS Catalina (10.15). Verify:
echo $SHELL
# Should show /bin/zsh
# If not, change it
chsh -s /bin/zsh
Linux
# Ubuntu/Debian
sudo apt update
sudo apt install zsh
# Fedora/RHEL
sudo dnf install zsh
# Arch
sudo pacman -S zsh
# Set as default
chsh -s $(which zsh)
Verify Installation
zsh --version
# Should be 5.8 or higher
Installing Oh My Zsh
# Via curl
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
# Via wget
sh -c "$(wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O -)"
This creates ~/.zshrc and sets up the framework. Restart your terminal or run source ~/.zshrc.
Powerlevel10k: The Best Theme
Most Oh My Zsh themes are slow or ugly. Powerlevel10k is neither.
Installation
# Clone the repository
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git \
${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k
# Set theme in ~/.zshrc
ZSH_THEME="powerlevel10k/powerlevel10k"
Restart terminal, follow the configuration wizard:
p10k configure
Powerlevel10k Features
- Instant prompt — Shell appears immediately
- Git status — Branch, dirty state, ahead/behind
- Command duration — Shows how long commands take
- Error codes — Red prompt when last command failed
- Background jobs — Indicator for running processes
- Context aware — Different styles for SSH, root, etc.
Essential Plugins
Built-in Oh My Zsh Plugins
Enable in ~/.zshrc:
plugins=(
git
z
colored-man-pages
command-not-found
history-substring-search
safe-paste
sudo
)
Must-Have External Plugins
zsh-autosuggestions
Suggests commands based on history as you type:
git clone https://github.com/zsh-users/zsh-autosuggestions \
${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
Add to plugins list:
plugins=(... zsh-autosuggestions)
zsh-syntax-highlighting
Highlights valid/invalid commands in real-time:
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git \
${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
Add to plugins list (must be last):
plugins=(... zsh-syntax-highlighting)
z (Smart Directory Navigation)
Jump to frequently used directories:
z project # Goes to ~/Projects/my-project (most frequent match)
z code src # Goes to ~/Code/project/src
Already included with Oh My Zsh, just add z to plugins.
Custom Aliases
Add to ~/.zshrc after Oh My Zsh is loaded:
Navigation
# Quick navigation
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'
alias ~='cd ~'
alias -- -='cd -'
# List files
alias ls='ls -G' # Colorized (macOS)
alias l='ls -lah'
alias la='ls -lAh'
alias ll='ls -lh'
alias lsa='ls -lah'
# Directory creation
alias mkdir='mkdir -p'
Git Shortcuts
Oh My Zsh provides many, but add your own:
alias g='git'
alias ga='git add'
alias gaa='git add --all'
alias gc='git commit -v'
alias gca='git commit -v -a'
alias gcam='git commit -a -m'
alias gcm='git commit -m'
alias gd='git diff'
alias gds='git diff --staged'
alias gf='git fetch'
alias gl='git pull'
alias gp='git push'
alias gco='git checkout'
alias gcb='git checkout -b'
alias gb='git branch'
alias gba='git branch -a'
alias gbd='git branch -d'
alias gst='git status'
alias glog='git log --oneline --decorate --graph'
alias glogs='git log --oneline --decorate --graph --all'
Development
# Package managers
alias nr='npm run'
alias ni='npm install'
alias nid='npm install --save-dev'
alias nrs='npm run start'
alias nrb='npm run build'
alias nrt='npm run test'
# Python
alias py='python3'
alias py2='python2'
alias pip='pip3'
alias venv='python3 -m venv venv'
alias activate='source venv/bin/activate'
# Docker
alias d='docker'
alias dc='docker compose'
alias dps='docker ps'
alias dpsa='docker ps -a'
alias di='docker images'
alias dex='docker exec -it'
alias dl='docker logs'
alias dlf='docker logs -f'
# Kubernetes
alias k='kubectl'
alias kg='kubectl get'
alias kd='kubectl describe'
alias kdel='kubectl delete'
alias ka='kubectl apply -f'
alias kgp='kubectl get pods'
alias kgs='kubectl get services'
alias kgn='kubectl get nodes'
alias kctx='kubectl config current-context'
alias kcns='kubectl config set-context --current --namespace'
# System
alias reload='source ~/.zshrc'
alias zshconfig='vim ~/.zshrc'
alias path='echo $PATH | tr ":" "\n"'
alias ports='lsof -i -P | grep LISTEN'
alias myip='curl -s https://ipinfo.io/ip'
Custom Functions
Add functions to ~/.zshrc:
Quick File Creation
# Create file and all parent directories
mkfile() {
mkdir -p "$(dirname "$1")" && touch "$1"
}
# Create directory and cd into it
mkcd() {
mkdir -p "$1" && cd "$1"
}
Development Helpers
# Find and replace in files
replace() {
if [ $# -ne 3 ]; then
echo "Usage: replace 'search' 'replace' file_pattern"
return 1
fi
find . -type f -name "$3" -exec sed -i '' "s/$1/$2/g" {} +
}
# Extract any archive
extract() {
if [ -f $1 ]; then
case $1 in
*.tar.bz2) tar xjf $1 ;;
*.tar.gz) tar xzf $1 ;;
*.bz2) bunzip2 $1 ;;
*.rar) unrar x $1 ;;
*.gz) gunzip $1 ;;
*.tar) tar xf $1 ;;
*.tbz2) tar xjf $1 ;;
*.tgz) tar xzf $1 ;;
*.zip) unzip $1 ;;
*.Z) uncompress $1;;
*.7z) 7z x $1 ;;
*) echo "'$1' cannot be extracted via extract()" ;;
esac
else
echo "'\$1' is not a valid file"
fi
}
# Start simple HTTP server
serve() {
local port="${1:-8000}"
python3 -m http.server "$port"
}
# Backup file with timestamp
backup() {
cp "$1" "${1}.backup.$(date +%Y%m%d%H%M%S)"
}
Git Helpers
# Create feature branch from main
gfeat() {
git checkout main && git pull && git checkout -b "feature/$1"
}
# Clean up merged branches
gclean() {
git branch --merged | grep -v "\\*" | xargs -n 1 git branch -d
}
# Undo last commit (keep changes)
gundo() {
git reset --soft HEAD~1
}
# Show commit count by author
gstats() {
git shortlog -sn --no-merges
}
Complete .zshrc Template
# ==========================================
# Oh My Zsh Configuration
# ==========================================
export ZSH="$HOME/.oh-my-zsh"
# Theme
ZSH_THEME="powerlevel10k/powerlevel10k"
# Plugins
plugins=(
git
z
colored-man-pages
command-not-found
history-substring-search
safe-paste
sudo
zsh-autosuggestions
zsh-syntax-highlighting
)
source $ZSH/oh-my-zsh.sh
# ==========================================
# User Configuration
# ==========================================
# Editor
export EDITOR='vim'
# Language
export LANG=en_US.UTF-8
# Paths
export PATH="/usr/local/bin:$PATH"
export PATH="$HOME/.local/bin:$PATH"
# History
HISTSIZE=10000
SAVEHIST=10000
HISTFILE=~/.zsh_history
setopt HIST_IGNORE_DUPS
setopt HIST_IGNORE_SPACE
setopt SHARE_HISTORY
# ==========================================
# Aliases
# ==========================================
# Navigation
alias ..='cd ..'
alias ...='cd ../..'
alias ~='cd ~'
# List files
alias l='ls -lah'
alias la='ls -lAh'
alias ll='ls -lh'
# Git
alias g='git'
alias gst='git status'
alias glog='git log --oneline --decorate --graph'
# Development
alias reload='source ~/.zshrc'
alias zshconfig='vim ~/.zshrc'
# ==========================================
# Functions
# ==========================================
mkcd() {
mkdir -p "$1" && cd "$1"
}
serve() {
python3 -m http.server "${1:-8000}"
}
# ==========================================
# Tool-specific Configuration
# ==========================================
# nvm (Node Version Manager)
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# Pyenv
export PYENV_ROOT="$HOME/.pyenv"
[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
# Rust (cargo)
[ -f ~/.cargo/env ] && source ~/.cargo/env
# ==========================================
# Custom (keep at bottom)
# ==========================================
[ -f ~/.zshrc.local ] && source ~/.zshrc.local
Useful Zsh Features
Globbing
# Recursive file matching
ls **/*.js # All .js files in all subdirectories
ls **/*.ts(.) # Regular files only (not symlinks)
ls **/*.(js|ts) # Multiple extensions
# Qualifiers
ls -lh *(.Lm+100) # Files larger than 100MB
ls *(m-7) # Modified in last 7 days
ls *(@) # Symlinks only
ls *(^@) # Non-symlinks
# Modifiers
ls *.bak(:r) # Remove .bak extension
ls *.txt(:e) # Show only extension
ls *.txt(:h) # Show only directory
ls *.txt(:t) # Show only filename
Parameter Expansion
# Default values
${VAR:-default} # Use default if VAR unset
${VAR:=default} # Set VAR to default if unset
${VAR:?error} # Show error if VAR unset
# String manipulation
${VAR#pattern} # Remove shortest match from start
${VAR##pattern} # Remove longest match from start
${VAR%pattern} # Remove shortest match from end
${VAR%%pattern} # Remove longest match from end
${VAR/pattern/rep} # Replace first match
${VAR//pattern/rep} # Replace all matches
# Examples
file="document.txt"
echo ${file%.txt}.pdf # document.pdf
path="/home/user/file"
echo ${path##*/} # file
Completion System
# Case-insensitive completion
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}'
# Fuzzy matching
zstyle ':completion:*' completer _complete _match _approximate
zstyle ':completion:*:match:*' original only
# Menu selection
zstyle ':completion:*' menu select
# Group results
zstyle ':completion:*' group-name ''
# Colorize completions
zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
Troubleshooting
Slow Prompt
# If powerlevel10k is slow, enable instant prompt
# In ~/.p10k.zsh, uncomment:
# typeset -g POWERLEVEL9K_INSTANT_PROMPT=quiet
Plugins Not Loading
# Check for syntax errors
zsh -xv # Verbose debugging
# Or test config
zsh -n ~/.zshrc # Syntax check only
Right Prompt Issues
# Some terminals don't handle RPROMPT well
# In ~/.p10k.zsh:
# typeset -g POWERLEVEL9K_DISABLE_RPROMPT=true
Summary
- Zsh — Modern shell with better defaults than bash
- Oh My Zsh — Plugin framework (300+ plugins, themes)
- Powerlevel10k — Fast, informative, customizable theme
- Essential plugins — git, z, autosuggestions, syntax-highlighting
- Aliases — Save keystrokes on commands you use 100x/day
- Functions — Automate multi-step workflows
A well-configured shell is like a custom IDE — it fits your workflow and saves hours over time.
What to Read Next
- SSH & GPG Cheat Sheet — Secure shell connections
- Linux & Bash Cheat Sheet — Command line reference
- tmux Cheat Sheet — Terminal multiplexing
- VS Code Shortcuts — Editor productivity