Zsh and Fish: Modern Interactive Shells
Bash is ubiquitous, but it is not your only option for an interactive shell. Zsh and Fish offer superior autocompletion, syntax highlighting, theming, and plugin ecosystems that make daily command-line work faster and more enjoyable. This page covers Zsh with Oh My Zsh and Powerlevel10k, Fish shell with its unique approach, and practical tips for migrating from Bash. Many professional developers have switched to one of these shells for interactive use while still writing scripts in Bash for portability.
Zsh Overview
Zsh is highly compatible with Bash while adding features like better globbing, spelling correction, programmable completion, and a rich plugin ecosystem. Since macOS Catalina, Zsh is the default shell on macOS. It is also available on every major Linux distribution.
Installing Oh My Zsh
Oh My Zsh is a community-driven framework that manages Zsh configuration, plugins, and themes. It has hundreds of plugins and themes contributed by thousands of developers.
# Install Oh My Zsh
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
# This creates ~/.zshrc with sensible defaults and sets your shell to Zsh
After installation, your ~/.zshrc will have a line like ZSH_THEME="robbyrussell" and a plugins=(git) array that you can customize.
Powerlevel10k Theme
Powerlevel10k is the most popular Zsh theme. It is fast, highly configurable, and shows context-aware information (git status, language versions, cloud context) in your prompt.
# Install Powerlevel10k
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git \
${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k
# Set the theme in ~/.zshrc
ZSH_THEME="powerlevel10k/powerlevel10k"
# Reload and run the configuration wizard
source ~/.zshrc
# The wizard (p10k configure) runs automatically on first load
The configuration wizard walks you through style choices interactively. Your preferences are saved to ~/.p10k.zsh, which you can edit later. For the best experience, install a Nerd Font (e.g., MesloLGS NF) in your terminal emulator.
Zsh Completions
Zsh has a powerful completion system that far surpasses Bash's default tab completion. Initialize it with compinit.
# In ~/.zshrc (Oh My Zsh handles this automatically)
autoload -Uz compinit && compinit
# Enable case-insensitive completion
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'
# Enable menu selection (navigate with arrow keys)
zstyle ':completion:*' menu select
# Cache completions for speed
zstyle ':completion:*' use-cache on
zstyle ':completion:*' cache-path ~/.zsh/cache
# Group completions by type
zstyle ':completion:*' group-name ''
zstyle ':completion:*:descriptions' format '%B%d%b'
Zsh can complete command options, file paths, git branches, hostnames, process IDs, and much more -- often out of the box. It also completes arguments for common commands like ssh, scp, kill, and git with intelligent context.
Essential Plugins
Plugins are listed in the plugins array in ~/.zshrc. Oh My Zsh ships with over 300 built-in plugins.
plugins=(
git # Git aliases and completion
docker # Docker completion
kubectl # Kubernetes completion
z # Jump to frequently used directories
zsh-autosuggestions
zsh-syntax-highlighting
fzf # fzf integration
colored-man-pages
)
zsh-autosuggestions
Suggests commands as you type, based on your history. Accept the suggestion with the right arrow key or press Ctrl-E to accept and execute.
# Install
git clone https://github.com/zsh-users/zsh-autosuggestions \
${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
# Add to plugins array in ~/.zshrc
plugins=(... zsh-autosuggestions)
zsh-syntax-highlighting
Colors your command as you type -- green for valid commands, red for errors, underlines for file paths that exist, and cyan for built-in commands.
# Install
git clone https://github.com/zsh-users/zsh-syntax-highlighting \
${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
# Add to plugins array (must be LAST in the list)
plugins=(... zsh-syntax-highlighting)
Sample .zshrc
# ~/.zshrc
export ZSH="$HOME/.oh-my-zsh"
ZSH_THEME="powerlevel10k/powerlevel10k"
plugins=(
git
docker
z
zsh-autosuggestions
zsh-syntax-highlighting
)
source $ZSH/oh-my-zsh.sh
# Custom aliases
alias ll='ls -la'
alias gs='git status'
alias k='kubectl'
# Custom functions
mkcd() { mkdir -p "$1" && cd "$1"; }
# History configuration
HISTSIZE=50000
SAVEHIST=50000
setopt SHARE_HISTORY # Share history across all sessions
setopt HIST_IGNORE_ALL_DUPS # Remove older duplicate entries
# Load local overrides if they exist
[[ -f ~/.zshrc.local ]] && source ~/.zshrc.local
# Powerlevel10k config
[[ -f ~/.p10k.zsh ]] && source ~/.p10k.zsh
Fish Shell
Fish (Friendly Interactive Shell) takes a different approach: sensible defaults with zero configuration. Autosuggestions, syntax highlighting, and tab completions work out of the box with no plugins or configuration needed.
Installing Fish
# macOS
brew install fish
# Debian/Ubuntu
sudo apt install fish
# Set as default shell
chsh -s $(which fish)
# Or just try it without changing your default
fish
Fish Abbreviations
Fish uses abbreviations instead of aliases. When you type an abbreviation and press Space or Enter, Fish expands it inline so you can see the full command before it runs. This is both more transparent and more powerful than aliases.
# In Fish
abbr --add gs git status
abbr --add gco git checkout
abbr --add dc docker compose
abbr --add ll ls -la
# When you type "gs" and press space, it expands to "git status"
# Universal abbreviations (persist across sessions)
abbr --add --global k kubectl
Fish Autosuggestions
Autosuggestions are built in. As you type, Fish shows a dimmed suggestion based on your history. Press the right arrow key to accept it, or press Alt-Right to accept one word at a time. No plugin installation required.
Fish Completions
Fish parses man pages to generate completions automatically. This means completions for most commands work without any configuration. For custom completions, create a .fish file in ~/.config/fish/completions/.
# ~/.config/fish/completions/myapp.fish
complete -c myapp -l verbose -d "Enable verbose output"
complete -c myapp -l config -r -d "Path to config file"
complete -c myapp -a "start stop restart" -d "Action to perform"
complete -c myapp -l port -x -d "Port number" -a "8080 3000 5000"
Fisher Plugin Manager
Fisher is the most popular plugin manager for Fish. It is lightweight and fast.
# Install Fisher
curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher
# Install plugins
fisher install PatrickF1/fzf.fish # fzf integration
fisher install jethrokuan/z # directory jumping
fisher install IlanCosman/tide # Tide prompt (like p10k for Fish)
# List installed plugins
fisher list
# Update all plugins
fisher update
# Remove a plugin
fisher remove jethrokuan/z
Fish Configuration
Fish uses ~/.config/fish/config.fish instead of .bashrc or .zshrc.
# ~/.config/fish/config.fish
# Environment variables
set -gx EDITOR nvim
set -gx PATH $HOME/.local/bin $PATH
# Abbreviations
abbr --add gs git status
abbr --add ll ls -la
# Greeting
set fish_greeting "" # Disable the default greeting
# Custom function (or put in ~/.config/fish/functions/)
function mkcd
mkdir -p $argv[1]; and cd $argv[1]
end
Migration Tips: Bash to Zsh
Switching from Bash to Zsh is relatively painless because Zsh is highly Bash-compatible. Most scripts and configurations work without modification.
# 1. Copy your aliases and functions from .bashrc to .zshrc
# Most will work without changes.
# 2. Arrays are 1-indexed in Zsh (0-indexed in Bash)
arr=(a b c)
echo $arr[1] # Zsh: "a" (Bash would need ${arr[0]})
# 3. Word splitting differs. Zsh does NOT split unquoted variables
# by default. This is actually safer, but may surprise you.
# 4. Glob patterns are more powerful in Zsh
ls **/*.py # recursive glob (works in Zsh natively)
ls *.txt(.) # only regular files, not directories
ls *(mh-1) # files modified in the last hour
# 5. Enable Bash compatibility if needed
emulate -L bash # in a function, behave like Bash
Migration Tips: Bash to Fish
Fish has a deliberately different syntax, so scripts do not port directly. The recommended approach is to use Fish for interactive work and keep Bash scripts as-is (Fish does not execute .sh files).
# Fish variable assignment (no export keyword)
set -gx MY_VAR "value"
# Fish conditionals
if test -f /etc/hosts
echo "File exists"
end
# Fish loops
for f in *.txt
echo "Processing $f"
end
# Fish functions
function greet
echo "Hello, $argv[1]"
end
Key differences: no $() command substitution (use (command) instead), no &&/|| (use and/or or ; and/; or), and if/for/while blocks end with end instead of fi/done. Fish also does not support process substitution <() -- use psub instead.
Next Steps
For terminal productivity tools that complement any shell, see CLI Productivity. To ensure your core Bash scripting skills remain sharp alongside your new shell, revisit Bash Fundamentals. Return to the Shell Scripting hub for all available topics.