Available for day contractsFrom 21st September I have availability for day and half day contracts. Please contact for more information.

Contact →
mikepreston.org

Zsh

A powerful shell with advanced completion, theming, and plugin support for enhanced command-line productivity.

Zsh

A powerful shell with advanced completion, theming, and plugin support for enhanced command-line productivity.

Overview

Zsh (Z Shell) is an extended Bourne shell with numerous improvements, including better tab completion, spelling correction, and customisation options. It's the default shell on macOS and popular on Linux systems, particularly when combined with Oh My Zsh for enhanced functionality. Zsh is ideal for interactive use and scripting, offering features like advanced globbing, programmable completion, and a rich plugin ecosystem.

YesNoYesNoYesNoYesNoShell StartLogin Shell?/etc/zshenv~/.zshenvLogin Shell?/etc/zprofile~/.zprofileInteractive?/etc/zshrc~/.zshrcLogin Shell?Ready~/.zloginYesNoYesNoYesNoYesNoShell StartLogin Shell?/etc/zshenv~/.zshenvLogin Shell?/etc/zprofile~/.zprofileInteractive?/etc/zshrc~/.zshrcLogin Shell?Ready~/.zlogin

Configuration Files

Zsh reads multiple configuration files in a specific order, each serving different purposes.

Key Concepts

File Purpose When Loaded
~/.zshenv Environment variables Always, first
~/.zprofile Login shell setup Login shells only
~/.zshrc Interactive shell config Interactive shells
~/.zlogin Post-login commands Login shells, after zshrc
~/.zlogout Logout cleanup When login shell exits

Configuration File Purposes

# ~/.zshenv - Loaded for ALL shells (scripts, interactive, login)
# Keep minimal - only essential environment variables
export EDITOR="vim"
export LANG="en_GB.UTF-8"
export PATH="$HOME/bin:$PATH"

# ~/.zprofile - Loaded for login shells before zshrc
# Use for commands that should run once at login
eval "$(/opt/homebrew/bin/brew shellenv)"

# ~/.zshrc - Main configuration for interactive shells
# Aliases, functions, completions, prompts, plugins
alias ll='ls -la'
bindkey -e

# ~/.zlogin - Loaded for login shells after zshrc
# Use for commands that should run after full setup
fortune | cowsay

# ~/.zlogout - Run when login shell exits
# Cleanup tasks
clear

Practical Configuration Structure

# ~/.zshrc - Well-organised configuration

# ============================================================================
# Environment
# ============================================================================
export HISTFILE="$HOME/.zsh_history"
export HISTSIZE=50000
export SAVEHIST=50000

# ============================================================================
# Options
# ============================================================================
setopt HIST_IGNORE_DUPS         # Don't record duplicates
setopt SHARE_HISTORY            # Share history between sessions
setopt AUTO_CD                  # cd by typing directory name
setopt CORRECT                  # Spelling correction
setopt EXTENDED_GLOB            # Advanced globbing

# ============================================================================
# Completion
# ============================================================================
autoload -Uz compinit && compinit
zstyle ':completion:*' menu select
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}'

# ============================================================================
# Key Bindings
# ============================================================================
bindkey -e                      # Emacs mode
bindkey '^R' history-incremental-search-backward

# ============================================================================
# Aliases
# ============================================================================
alias ll='ls -la'
alias ..='cd ..'

# ============================================================================
# Functions
# ============================================================================
mkcd() { mkdir -p "$1" && cd "$1" }

# ============================================================================
# Plugins and Theme
# ============================================================================
source ~/.zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh

Examples

# Check which files are being loaded
zsh -x -i -c exit 2>&1 | grep 'source'

# Reload configuration without restarting shell
source ~/.zshrc

# Test configuration in new shell
zsh -f        # Start without any config files
zsh --norcs   # Skip rc files

# Find where a setting is defined
zsh -x -i -c exit 2>&1 | grep 'setopt'

# Check current shell options
setopt        # List all enabled options
unsetopt      # List all disabled options

Oh My Zsh Setup and Themes

Oh My Zsh is a community-driven framework for managing zsh configuration with hundreds of plugins and themes.

Oh My ZshThemesPluginsCustomBuilt-in ThemesExternal ThemesBuilt-in PluginsCustom Plugins~/.oh-my-zsh/customaliases.zshfunctions.zshthemes/plugins/Oh My ZshThemesPluginsCustomBuilt-in ThemesExternal ThemesBuilt-in PluginsCustom Plugins~/.oh-my-zsh/customaliases.zshfunctions.zshthemes/plugins/

Installation

# Install Oh My Zsh
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

# Or using wget
sh -c "$(wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O -)"

# Manual installation
git clone https://github.com/ohmyzsh/ohmyzsh.git ~/.oh-my-zsh
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

Theme Configuration

# ~/.zshrc - Theme settings

# Set theme (see ~/.oh-my-zsh/themes/)
ZSH_THEME="robbyrussell"        # Default theme
ZSH_THEME="agnoster"            # Popular powerline theme
ZSH_THEME="powerlevel10k/powerlevel10k"  # Feature-rich theme

# Random theme on each shell start
ZSH_THEME="random"
ZSH_THEME_RANDOM_CANDIDATES=("robbyrussell" "agnoster" "ys")

# Disable theme
ZSH_THEME=""

Popular Themes

# Install Powerlevel10k (recommended)
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git \
    ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k

# In ~/.zshrc
ZSH_THEME="powerlevel10k/powerlevel10k"

# Configure Powerlevel10k
p10k configure

# Install Spaceship theme
git clone https://github.com/spaceship-prompt/spaceship-prompt.git \
    ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/spaceship-prompt --depth=1
ln -s "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/spaceship-prompt/spaceship.zsh-theme" \
    "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/spaceship.zsh-theme"

# In ~/.zshrc
ZSH_THEME="spaceship"

Custom Directory Structure

# Oh My Zsh custom directory
~/.oh-my-zsh/custom/
├── aliases.zsh           # Custom aliases (auto-loaded)
├── functions.zsh         # Custom functions (auto-loaded)
├── plugins/              # Custom plugins
│   └── my-plugin/
│       └── my-plugin.plugin.zsh
└── themes/               # Custom themes
    └── my-theme.zsh-theme

# Files in custom/ ending in .zsh are auto-loaded
# Create custom files for organisation
touch ~/.oh-my-zsh/custom/work-aliases.zsh
touch ~/.oh-my-zsh/custom/dev-functions.zsh

Examples

# Update Oh My Zsh
omz update

# Check current theme
echo $ZSH_THEME

# List available themes
ls ~/.oh-my-zsh/themes/

# Preview theme without changing config
THEME="agnoster" && source ~/.oh-my-zsh/themes/$THEME.zsh-theme

# Uninstall Oh My Zsh
uninstall_oh_my_zsh

Plugins

Plugins add functionality like aliases, completions, and functions for various tools.

Plugin Configuration

# ~/.zshrc - Enable plugins
plugins=(
    git
    docker
    kubectl
    aws
    terraform
    z
    fzf
    zsh-autosuggestions
    zsh-syntax-highlighting
)

# Load Oh My Zsh (after setting plugins)
source $ZSH/oh-my-zsh.sh

Essential Built-in Plugins

# git - Aliases and functions for Git
ga      # git add
gc      # git commit
gco     # git checkout
gst     # git status
gp      # git push
gl      # git pull
gd      # git diff
glog    # git log --oneline --graph

# docker - Docker aliases and completion
dps     # docker ps
dpa     # docker ps -a
di      # docker images
drm     # docker rm
dri     # docker rmi
dex     # docker exec -it

# kubectl - Kubernetes aliases
k       # kubectl
kaf     # kubectl apply -f
kgp     # kubectl get pods
kgs     # kubectl get services
kgd     # kubectl get deployments
kd      # kubectl describe
kl      # kubectl logs
kex     # kubectl exec -it

# aws - AWS CLI completion and aliases
asp     # Set AWS profile
agp     # Get current AWS profile

# terraform - Terraform aliases
tf      # terraform
tfa     # terraform apply
tfd     # terraform destroy
tfi     # terraform init
tfp     # terraform plan

Installing External Plugins

# zsh-autosuggestions - Fish-like suggestions
git clone https://github.com/zsh-users/zsh-autosuggestions \
    ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions

# zsh-syntax-highlighting - Syntax highlighting
git clone https://github.com/zsh-users/zsh-syntax-highlighting \
    ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting

# zsh-completions - Additional completions
git clone https://github.com/zsh-users/zsh-completions \
    ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-completions

# fzf - Fuzzy finder integration
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install

# Add to plugins array
plugins=(... zsh-autosuggestions zsh-syntax-highlighting)

# Note: zsh-syntax-highlighting must be last in plugins list

Plugin Configuration Options

# ~/.zshrc - Plugin-specific settings

# zsh-autosuggestions
ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="fg=#666666"
ZSH_AUTOSUGGEST_STRATEGY=(history completion)
ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=20

# Right arrow or End accepts a suggestion by default;
# optionally add Ctrl+Space as an extra accept binding
bindkey '^ ' autosuggest-accept

# fzf settings
export FZF_DEFAULT_OPTS="--height 40% --layout=reverse --border"
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"

# z (directory jumping) settings
export _Z_DATA="$HOME/.z"
export _Z_MAX_SCORE=9000

Creating Custom Plugins

# Create plugin directory
mkdir -p ~/.oh-my-zsh/custom/plugins/my-plugin

# Create plugin file
cat > ~/.oh-my-zsh/custom/plugins/my-plugin/my-plugin.plugin.zsh << 'EOF'
# My custom plugin

# Aliases
alias myalias='echo "Hello from my plugin"'

# Functions
my-function() {
    echo "Arguments: $@"
}

# Completion
_my_command() {
    local -a commands
    commands=(
        'start:Start the service'
        'stop:Stop the service'
        'status:Show status'
    )
    _describe 'command' commands
}
compdef _my_command my-command
EOF

# Enable plugin in ~/.zshrc
plugins=(... my-plugin)

Custom Aliases and Functions

Aliases

# Basic aliases
alias ll='ls -la'
alias la='ls -A'
alias l='ls -CF'
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'

# Safety aliases
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

# Modern replacements
alias cat='bat'
alias ls='exa'
alias find='fd'
alias grep='rg'

# Global aliases (work anywhere in command)
alias -g G='| grep'
alias -g L='| less'
alias -g H='| head'
alias -g T='| tail'
alias -g J='| jq .'
alias -g NE='2>/dev/null'
alias -g NUL='>/dev/null 2>&1'

# Usage: dmesg G error
# Expands to: dmesg | grep error

# Suffix aliases (open files by extension)
alias -s md=code
alias -s json=code
alias -s yaml=code
alias -s txt=cat

# Usage: typing 'readme.md' runs 'code readme.md'

# Git aliases
alias gs='git status'
alias ga='git add'
alias gc='git commit -m'
alias gp='git push'
alias gl='git pull'
alias gd='git diff'
alias gco='git checkout'
alias gb='git branch'
alias glog='git log --oneline --graph --decorate'

# Docker aliases
alias d='docker'
alias dc='docker compose'
alias dps='docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
alias dex='docker exec -it'
alias dlogs='docker logs -f'

# Kubernetes aliases
alias k='kubectl'
alias kx='kubectx'
alias kn='kubens'
alias kgp='kubectl get pods'
alias kgs='kubectl get services'
alias kga='kubectl get all'
alias kd='kubectl describe'
alias kl='kubectl logs -f'
alias kex='kubectl exec -it'

Functions

# Create directory and cd into it
mkcd() {
    mkdir -p "$1" && cd "$1"
}

# 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" ;;
        esac
    else
        echo "'$1' is not a valid file"
    fi
}

# Find and replace in files
replace() {
    if [[ $# -lt 3 ]]; then
        echo "Usage: replace <old> <new> <files>"
        return 1
    fi
    local old="$1" new="$2"
    shift 2
    for file in "$@"; do
        sed -i "s/$old/$new/g" "$file"
    done
}

# Quick HTTP server
serve() {
    local port="${1:-8000}"
    python3 -m http.server "$port"
}

# Get public IP
myip() {
    curl -s https://ipinfo.io/ip
    echo
}

# Docker shell
dsh() {
    docker exec -it "$1" /bin/sh -c '[ -x /bin/bash ] && exec /bin/bash || exec /bin/sh'
}

# Kubernetes pod shell
ksh() {
    kubectl exec -it "$1" -- /bin/sh -c '[ -x /bin/bash ] && exec /bin/bash || exec /bin/sh'
}

# Git commit with message
gcm() {
    git add -A && git commit -m "$*"
}

# Search command history
hgrep() {
    history | grep -i "$1"
}

# Weather
weather() {
    curl "wttr.in/${1:-London}"
}

# JSON pretty print
json() {
    if [[ -p /dev/stdin ]]; then
        jq '.' -C
    else
        jq '.' -C "$1"
    fi
}

# Backup file with timestamp
backup() {
    cp "$1" "${1}.$(date +%Y%m%d_%H%M%S).bak"
}

Advanced Function Patterns

# Function with options
myfunction() {
    local verbose=0
    local output=""

    while [[ $# -gt 0 ]]; do
        case "$1" in
            -v|--verbose) verbose=1; shift ;;
            -o|--output) output="$2"; shift 2 ;;
            *) break ;;
        esac
    done

    [[ $verbose -eq 1 ]] && echo "Processing..."
    # Function logic here
}

# Function with completion
_myfunction() {
    local -a options
    options=(
        '-v:Verbose output'
        '--verbose:Verbose output'
        '-o:Output file'
        '--output:Output file'
    )
    _describe 'option' options
}
compdef _myfunction myfunction

Command History and Search

History Configuration

# ~/.zshrc - History settings

# History file location and size
HISTFILE=~/.zsh_history
HISTSIZE=50000
SAVEHIST=50000

# History options
setopt EXTENDED_HISTORY         # Save timestamp
setopt HIST_EXPIRE_DUPS_FIRST   # Expire duplicates first
setopt HIST_IGNORE_DUPS         # Ignore consecutive duplicates
setopt HIST_IGNORE_ALL_DUPS     # Remove older duplicates
setopt HIST_IGNORE_SPACE        # Ignore commands starting with space
setopt HIST_FIND_NO_DUPS        # Don't display duplicates in search
setopt HIST_REDUCE_BLANKS       # Remove extra blanks
setopt HIST_VERIFY              # Show command before executing from history
setopt SHARE_HISTORY            # Share history between sessions
setopt INC_APPEND_HISTORY       # Add commands immediately
setopt HIST_NO_STORE            # Don't store history command itself

History Search and Navigation

# Key bindings for history
bindkey '^R' history-incremental-search-backward  # Ctrl+R
bindkey '^S' history-incremental-search-forward   # Ctrl+S
bindkey '^P' up-line-or-history                   # Ctrl+P
bindkey '^N' down-line-or-history                 # Ctrl+N

# Arrow keys for prefix search
bindkey '^[[A' history-beginning-search-backward  # Up arrow
bindkey '^[[B' history-beginning-search-forward   # Down arrow

# Alternative: use up-line-or-search
autoload -Uz up-line-or-beginning-search down-line-or-beginning-search
zle -N up-line-or-beginning-search
zle -N down-line-or-beginning-search
bindkey "^[[A" up-line-or-beginning-search
bindkey "^[[B" down-line-or-beginning-search

History Commands

# View history
history              # Show recent history
history -10          # Last 10 commands
history 1            # All history from beginning
history -E           # Show timestamps
history -D           # Show elapsed time

# Search history
history | grep docker
fc -l -m 'docker*'   # Pattern search

# Execute from history
!!                   # Repeat last command
!-2                  # Run command from 2 ago
!docker              # Run last command starting with 'docker'
!?container          # Run last command containing 'container'
!42                  # Run command number 42

# Modify and run
^old^new             # Replace 'old' with 'new' in last command
!docker:s/ps/images  # Substitute in specific command

# History expansion modifiers
!!:$                 # Last argument of last command
!$                   # Same as above
!!:0                 # First word of last command
!!:*                 # All arguments of last command
!^                   # First argument of last command

fzf History Search

# Enhanced history search with fzf
# Add to ~/.zshrc after fzf plugin

# Ctrl+R for fuzzy history search
fzf-history-widget() {
    local selected
    selected=$(fc -l 1 | fzf --tac --no-sort --query="$LBUFFER" \
        --preview 'echo {}' --preview-window up:3:hidden:wrap \
        --bind '?:toggle-preview')
    if [[ -n "$selected" ]]; then
        local num=$(echo "$selected" | awk '{print $1}')
        BUFFER=$(fc -l -n $num $num)
        CURSOR=${#BUFFER}
    fi
    zle redisplay
}
zle -N fzf-history-widget
bindkey '^R' fzf-history-widget

Tab Completion Customisation

Basic Completion Setup

# Enable completion system
autoload -Uz compinit && compinit

# Rebuild completion cache (do occasionally)
rm -f ~/.zcompdump && compinit

Completion Styles

# Menu selection (navigate with arrows)
zstyle ':completion:*' menu select

# Case-insensitive completion
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}'

# Partial word completion
zstyle ':completion:*' matcher-list '' \
    'm:{a-zA-Z}={A-Za-z}' \
    'r:|[._-]=* r:|=*' \
    'l:|=* r:|=*'

# Group completions by type
zstyle ':completion:*' group-name ''
zstyle ':completion:*:descriptions' format '%B%d%b'

# Colours in completion
zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}

# Process completion
zstyle ':completion:*:*:kill:*:processes' list-colors \
    '=(#b) #([0-9]#)*=0=01;31'
zstyle ':completion:*:kill:*' command 'ps -u $USER -o pid,%cpu,tty,cputime,cmd'

# SSH/SCP completion from known_hosts
zstyle ':completion:*:(ssh|scp|sftp):*' hosts \
    ${${${(@M)${(f)"$(cat ~/.ssh/known_hosts 2>/dev/null)"}:#[^|]*}%%\ *}%%,*}

# Complete user@host for ssh
zstyle ':completion:*:ssh:*' users root $USER
zstyle ':completion:*:ssh:*' hosts ${${${${(f)"$(<~/.ssh/known_hosts)"}:#[\|]*}%%\ *}%%,*}

# Ignore patterns
zstyle ':completion:*:(all-|)files' ignored-patterns \
    '(|*/)__pycache__' '(|*/).git'

# Cache completions
zstyle ':completion:*' use-cache on
zstyle ':completion:*' cache-path ~/.zsh/cache

Command-Specific Completions

# Docker completion customisation
zstyle ':completion:*:*:docker:*' option-stacking yes
zstyle ':completion:*:*:docker-*:*' option-stacking yes

# Git completion settings
zstyle ':completion:*:git-checkout:*' sort false
zstyle ':completion:*:descriptions' format '[%d]'

# Kubernetes completion
source <(kubectl completion zsh)
# Custom namespace list
zstyle ':completion:*:*:kubectl:*' list-grouped false

Custom Completion Functions

# Simple completion function
_my_command() {
    local -a commands
    commands=(
        'start:Start the service'
        'stop:Stop the service'
        'restart:Restart the service'
        'status:Show status'
    )
    _describe 'command' commands
}
compdef _my_command my-command

# Completion with arguments
_my_tool() {
    local state

    _arguments \
        '-h[Show help]' \
        '-v[Verbose output]' \
        '-o[Output file]:file:_files' \
        '-c[Config file]:config:_files -g "*.yaml"' \
        '1:command:(start stop status)' \
        '*:files:_files'
}
compdef _my_tool my-tool

# Dynamic completion
_dynamic_comp() {
    local -a options
    # Generate options dynamically
    options=($(my-command --list-options 2>/dev/null))
    _describe 'option' options
}
compdef _dynamic_comp dynamic-command

Prompts and Themes

Prompt Variables

# Prompt escape sequences
%n          # Username
%m          # Hostname (short)
%M          # Hostname (full)
%~          # Current directory (~ for home)
%d or %/   # Full current directory
%c or %.   # Current directory basename
%#          # # for root, % for normal user
%?          # Exit code of last command
%D          # Date (yy-mm-dd)
%T          # Time (24-hour)
%*          # Time with seconds
%w          # Day and date
%j          # Number of background jobs
%l          # Current tty

# Colour codes
%F{colour}  # Foreground colour
%K{colour}  # Background colour
%f          # Reset foreground
%k          # Reset background
%B / %b     # Bold on/off
%U / %u     # Underline on/off
%S / %s     # Standout on/off

# Conditional expressions
%(x.true.false)  # If x is true, use true, else false
%(?..%F{red})    # Red if last command failed

Custom Prompts

# Simple prompt
PROMPT='%F{green}%n@%m%f:%F{blue}%~%f$ '

# Two-line prompt
PROMPT='%F{cyan}%n%f@%F{yellow}%m%f %F{blue}%~%f
%# '

# Prompt with exit code indicator
PROMPT='%(?.%F{green}✓.%F{red}✗)%f %F{blue}%~%f %# '

# Prompt with git branch (requires vcs_info)
autoload -Uz vcs_info
precmd() { vcs_info }
zstyle ':vcs_info:git:*' formats ' (%b)'
setopt PROMPT_SUBST
PROMPT='%F{blue}%~%f%F{yellow}${vcs_info_msg_0_}%f %# '

# Right-side prompt
RPROMPT='%F{8}%*%f'  # 8 = bright black (grey); zsh has no named grey

# Prompt with job count
PROMPT='%(1j.[%j] .)%F{blue}%~%f %# '

Advanced Prompt with Git Status

# ~/.zshrc - Full git prompt

autoload -Uz vcs_info
autoload -Uz add-zsh-hook

# Enable git info
zstyle ':vcs_info:*' enable git
zstyle ':vcs_info:*' check-for-changes true
zstyle ':vcs_info:git:*' stagedstr '%F{green}●%f'
zstyle ':vcs_info:git:*' unstagedstr '%F{red}●%f'
zstyle ':vcs_info:git:*' formats ' %F{yellow}(%b%f%c%u%F{yellow})%f'
zstyle ':vcs_info:git:*' actionformats ' %F{yellow}(%b|%a%f%c%u%F{yellow})%f'

# Update prompt before each command
add-zsh-hook precmd vcs_info

# Enable prompt substitution
setopt PROMPT_SUBST

# Build prompt
PROMPT='%F{cyan}%n%f@%F{magenta}%m%f:%F{blue}%~%f${vcs_info_msg_0_}
%(?.%F{green}❯.%F{red}❯)%f '

RPROMPT='%F{8}%*%f'  # 8 = bright black (grey); zsh has no named grey

Powerlevel10k Configuration

# Install Powerlevel10k
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git \
    ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k

# Set theme
ZSH_THEME="powerlevel10k/powerlevel10k"

# Run configuration wizard
p10k configure

# Manual configuration in ~/.p10k.zsh
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(
    dir
    vcs
    prompt_char
)

typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(
    status
    command_execution_time
    background_jobs
    time
)

# Customise segments
typeset -g POWERLEVEL9K_DIR_FOREGROUND=31
typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=76
typeset -g POWERLEVEL9K_VCS_MODIFIED_FOREGROUND=178

Globbing Patterns

Zsh provides powerful extended globbing patterns for file matching.

Basic Glob Patterns

# Enable extended globbing
setopt EXTENDED_GLOB

# Basic patterns
*           # Any string
?           # Any single character
[abc]       # Any character in set
[^abc]      # Any character not in set
[a-z]       # Character range
<1-100>     # Numeric range

# Examples
ls *.txt              # All .txt files
ls file?.txt          # file1.txt, fileA.txt, etc.
ls *[0-9]*            # Files containing digits
ls file<1-10>.txt     # file1.txt through file10.txt

Extended Glob Patterns

# Extended patterns (require EXTENDED_GLOB)
^pattern    # Anything except pattern
x~y         # x but not y
x#          # Zero or more of x
x##         # One or more of x

# Examples
ls ^*.bak             # All files except .bak
ls *.txt~secret*      # .txt files except those starting with 'secret'
ls (ab)#              # '', 'ab', 'abab', 'ababab', etc.
ls (ab)##.txt         # 'ab.txt', 'abab.txt', etc.

# Match at start/end
ls (#i)readme*        # Case-insensitive: README, readme, Readme
ls *(.)               # Regular files only
ls *(/)               # Directories only
ls *(@)               # Symlinks only

Glob Qualifiers

# Qualifiers go in parentheses at end
# Type qualifiers
ls *(.)               # Regular files
ls *(/)               # Directories
ls *(@)               # Symbolic links
ls *(=)               # Sockets
ls *(p)               # Named pipes (FIFOs)
ls *(%)               # Device files (block/character)

# Permission qualifiers
ls *(r)               # Readable by owner
ls *(w)               # Writable by owner
ls *(x)               # Executable by owner
ls *(R)               # Readable by world
ls *(W)               # Writable by world
ls *(X)               # Executable by world
ls *(f755)            # Exact permissions 755

# Ownership qualifiers
ls *(U)               # Owned by current user
ls *(G)               # Owned by current group
ls *(u:root:)         # Owned by root
ls *(g:staff:)        # Owned by group staff

# Size qualifiers
ls *(L0)              # Empty files
ls *(Lk+100)          # Files larger than 100KB
ls *(Lm-50)           # Files smaller than 50MB
ls *(LM+1)            # Files larger than 1MB

# Time qualifiers
ls *(mh-1)            # Modified in last hour
ls *(md+7)            # Modified more than 7 days ago
ls *(mw-2)            # Modified in last 2 weeks
ls *(aM+3)            # Accessed more than 3 months ago

# Combination examples
ls *(.*m-7)           # Regular files modified in last week
ls *(.Lk+100m-1)      # Files >100KB modified in last day
ls *(/:u:www-data:)   # Directories owned by www-data

Recursive Globbing

# Recursive matching
ls **/*.txt           # All .txt files recursively
ls ***/*.txt          # Same, following symlinks

# Limiting depth
ls */*.txt            # .txt files one level deep
ls */*/*.txt          # .txt files two levels deep

# Combined with qualifiers
ls **/*.py(.)         # Python files (not directories)
ls **/*.log(.m-7)     # Log files modified in last week

# Directory matching
ls **/node_modules(/N) # All node_modules directories (/N for null if none)

Practical Globbing Examples

# Find all Python files modified today
ls **/*.py(.mh-24)

# List executable scripts in bin directories
ls **/bin/*(.x)

# Find files larger than 1MB, modified in last week
ls **/*(.Lm+1mw-1)

# Clean up old backup files
rm **/*.bak(.md+30)

# Find configuration files
ls **/*.(conf|cfg|ini|yaml|yml)(.)

# List directories without .git
ls -d **/(^.git)(/N)

# Copy all images recursively
cp **/*.(jpg|png|gif)(.) /backup/images/

# Find and process files
for f in **/*.txt(.m-7); do
    echo "Processing: $f"
done

# Null glob - no error if no matches
setopt NULL_GLOB
ls *.nonexistent      # No error, just empty result

Useful Key Bindings

Viewing and Setting Bindings

# List all bindings
bindkey

# List specific binding
bindkey '^R'

# Show binding in different keymaps
bindkey -M emacs
bindkey -M vicmd
bindkey -M viins

# Set emacs mode (default)
bindkey -e

# Set vi mode
bindkey -v

Essential Emacs-Mode Bindings

# Navigation
Ctrl+A      # Beginning of line
Ctrl+E      # End of line
Ctrl+B      # Back one character
Ctrl+F      # Forward one character
Alt+B       # Back one word
Alt+F       # Forward one word

# Editing
Ctrl+D      # Delete character under cursor
Ctrl+H      # Delete character before cursor (Backspace)
Ctrl+K      # Kill to end of line
Ctrl+U      # Kill entire line
Ctrl+W      # Kill previous word
Alt+D       # Kill next word
Ctrl+Y      # Yank (paste) killed text
Alt+Y       # Cycle through kill ring

# History
Ctrl+R      # Reverse history search
Ctrl+S      # Forward history search
Ctrl+P      # Previous command
Ctrl+N      # Next command
Alt+.       # Insert last argument of previous command

# Miscellaneous
Ctrl+L      # Clear screen
Ctrl+C      # Cancel current command
Ctrl+Z      # Suspend current process
Ctrl+_      # Undo
Tab         # Completion
Ctrl+X Ctrl+E  # Edit command in $EDITOR

Custom Key Bindings

# Bind custom functions
# Edit command in editor
autoload -z edit-command-line
zle -N edit-command-line
bindkey '^X^E' edit-command-line

# Insert sudo at beginning
insert-sudo() {
    BUFFER="sudo $BUFFER"
    CURSOR+=5
}
zle -N insert-sudo
bindkey '\e\e' insert-sudo  # Esc Esc (Ctrl+S clashes with terminal flow control unless stty -ixon is set)

# Clear screen keeping current line
clear-screen-and-redraw() {
    clear
    zle redisplay
}
zle -N clear-screen-and-redraw
bindkey '^L' clear-screen-and-redraw

# Quick cd to parent
cd-up() {
    cd ..
    zle accept-line
}
zle -N cd-up
bindkey '^U^U' cd-up

# Accept autosuggestion
bindkey '^ ' autosuggest-accept
bindkey '^[[C' forward-char  # Right arrow accepts char
bindkey '^[[F' autosuggest-accept  # End key accepts all

Vi-Mode Bindings

# Enable vi mode
bindkey -v

# Reduce key timeout
export KEYTIMEOUT=1

# Add useful bindings in vi mode
bindkey -M viins '^R' history-incremental-search-backward
bindkey -M viins '^A' beginning-of-line
bindkey -M viins '^E' end-of-line
bindkey -M viins '^K' kill-line
bindkey -M viins '^W' backward-kill-word
bindkey -M viins '^Y' yank

# Edit in vim
autoload -Uz edit-command-line
zle -N edit-command-line
bindkey -M vicmd 'v' edit-command-line

# Better searching in vi mode
bindkey -M vicmd '/' history-incremental-search-backward
bindkey -M vicmd '?' history-incremental-search-forward

# Show mode indicator in prompt
function zle-line-init zle-keymap-select {
    case $KEYMAP in
        vicmd) echo -ne '\e[1 q';;      # Block cursor
        viins|main) echo -ne '\e[5 q';; # Beam cursor
    esac
}
zle -N zle-line-init
zle -N zle-keymap-select

Function Key Bindings

# Find key codes by pressing Ctrl+V then the key
# Example: Ctrl+V then F1 shows ^[OP

# Bind function keys
bindkey '^[OP' run-help              # F1 - Help
bindkey '^[OQ' push-line             # F2 - Push line
bindkey '^[OR' backward-kill-line    # F3 - Kill to start

# Bind Home/End/Delete
bindkey '^[[H' beginning-of-line     # Home
bindkey '^[[F' end-of-line           # End
bindkey '^[[3~' delete-char          # Delete

# Page Up/Down for history
bindkey '^[[5~' up-line-or-history   # Page Up
bindkey '^[[6~' down-line-or-history # Page Down

Quick Reference

Essential Options

Option Description
AUTO_CD Change directory by typing name only
CORRECT Spelling correction for commands
EXTENDED_GLOB Advanced pattern matching
HIST_IGNORE_DUPS Don't record duplicate commands
SHARE_HISTORY Share history between sessions
NO_BEEP Disable terminal beep
INTERACTIVE_COMMENTS Allow comments in interactive mode
PROMPT_SUBST Enable variable expansion in prompts

Common Shortcuts

Shortcut Action
Ctrl+R Search history backwards
Ctrl+A / Ctrl+E Beginning / End of line
Ctrl+K Kill to end of line
Ctrl+U Kill entire line
Ctrl+W Kill previous word
Alt+. Insert last argument
Tab Completion
Ctrl+L Clear screen

Glob Qualifiers Quick Reference

Qualifier Description
(.) Regular files
(/) Directories
(@) Symbolic links
(x) Executable
(Lk+100) Size > 100KB
(mh-1) Modified in last hour
(md+7) Modified > 7 days ago
(U) Owned by current user
(N) Null glob (no error if empty)

History Expansion

Expansion Description
!! Last command
!$ Last argument
!^ First argument
!* All arguments
!n Command number n
!string Last command starting with string
^old^new Replace old with new in last command

Common Issues and Solutions

Issue: Slow Shell Startup

Problem: Zsh takes several seconds to start.

# Profile startup time
time zsh -i -c exit

# Detailed profiling
zmodload zsh/zprof
# ... your .zshrc content ...
zprof

# Common solutions:
# 1. Lazy load nvm
export NVM_LAZY_LOAD=true

# 2. Cache completions
zstyle ':completion:*' use-cache on

# 3. Compile completion dump
autoload -Uz compinit
if [[ -n ${ZDOTDIR}/.zcompdump(#qN.mh+24) ]]; then
    compinit
else
    compinit -C
fi

Issue: Completion Not Working

Problem: Tab completion doesn't show expected results.

# Rebuild completion cache
rm -f ~/.zcompdump* && compinit

# Check if completion is loaded
whence -w _git  # Should show "function"

# Ensure completions are initialised
autoload -Uz compinit && compinit

# Check completion path
echo $fpath

# Add custom completion directory
fpath=(~/.zsh/completions $fpath)

Issue: Special Characters in History Search

Problem: Search fails with special characters like |, *, ?.

# Use Ctrl+V to escape next character
# Ctrl+V then | to search for literal pipe

# Or use patterns
# Ctrl+R then: *pattern*

# Alternative: use fzf for history
plugins=(... fzf)

Issue: Key Bindings Not Working

Problem: Custom key bindings don't work as expected.

# Check if terminal sends correct sequence
# Press Ctrl+V then the key to see code

# Common fixes for Home/End
bindkey "${terminfo[khome]}" beginning-of-line
bindkey "${terminfo[kend]}" end-of-line

# For arrow keys
bindkey "${terminfo[kcuu1]}" up-line-or-history
bindkey "${terminfo[kcud1]}" down-line-or-history

# Reset terminal
tset

Issue: Slow Paste

Problem: Pasting text is very slow.

# Disable bracketed paste mode highlighting
zstyle ':bracketed-paste-magic' active-widgets '.self-*'

# Or disable feature entirely
unset zle_bracketed_paste

Issue: Oh My Zsh Errors After Update

Problem: Plugins or themes fail after Oh My Zsh update.

# Update all custom plugins
cd ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins
for plugin in */; do
    if [[ -d "$plugin/.git" ]]; then
        git -C "$plugin" pull
    fi
done

# Reset to default configuration
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

# Reinstall Oh My Zsh
uninstall_oh_my_zsh
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

Issue: Global Aliases Expanding Unexpectedly

Problem: Global aliases expand when not wanted.

# Escape with backslash
\G    # Literal G, not grep

# Quote to prevent expansion
'|grep'

# Disable specific alias temporarily
unalias G

# Use in specific contexts only
alias -g G='| grep'   # Only use when you mean it

Related Topics

To enhance your shell productivity and command-line expertise, explore these related topics:

  • Bash - Traditional Unix shell scripting, essential for cross-platform compatibility
  • Linux CLI - Core commands, file operations, and system administration utilities
  • tmux - Terminal multiplexer for session management and split panes
  • vim - Modal text editor with powerful editing capabilities and shell integration
  • fzf - Fuzzy finder for enhanced command-line searching and selection
  • Git - Version control with excellent zsh plugin support and completions