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.
- 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
Related Articles
Deepen your understanding with these curated continuations.
SSH Config & Aliases: The Developer's Connection Kit
Master SSH configuration — host aliases, jump hosts, key management, port forwarding, and secure connections. Save time with ~/.ssh/config automation.
Show or Hide Absolute Line Numbers in Vim Editor
Master line number visibility in Vim. Learn the commands for absolute, relative, and hybrid line numbers to improve your navigation speed in the terminal.
tmux Cheat Sheet: Sessions, Panes, Windows & Config
Complete tmux reference — prefix key, sessions, windows, panes, copy mode, key bindings, plugins, and a starter .tmux.conf for 2026.