{
    "title": "My Z shell setup",
    "slug": "my-z-shell-setup",
    "excerpt": "A quick walkthrough of my Z shell setup for Debian servers and macOS, including Pure prompt, autosuggestions, history search, npm completion, SSH host completion, aliases, and a few cross-platform helper commands.",
    "body": "I've been a [Fish shell](https://en.wikipedia.org/wiki/Fish_(Unix_shell)) user for years. It was my shell of choice on my Linux systems until I purchased my [MacBook Pro](https://blog.philipnewborough.co.uk/posts/my-macbook-pro-m5), when I switched to [Z shell](https://en.wikipedia.org/wiki/Z_shell).\r\n\r\nAs a fairly new Z shell user, I thought it might be good for me to document how I install and configure it, so that I can replicate my setup on new servers etc.\r\n\r\n## Install\r\n\r\nI run Debian on my servers, so adjust this command if you use a different Linux distro. I've included `git` and `curl` as Git is required to clone the Z shell scripts and `curl` is referenced in my `.zshrc` file.\r\n\r\n```\r\nsudo apt update\r\nsudo apt install zsh git curl\r\n```\r\n\r\n## Clone zsh scripts from GitHub\r\n\r\nRun the following commands to create a directory to hold the zsh scripts before cloning them from GitHub:\r\n\r\n```\r\nmkdir -p ~/.zsh\r\ngit clone https://github.com/sindresorhus/pure.git ~/.zsh/pure\r\ngit clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions\r\ngit clone https://github.com/zsh-users/zsh-history-substring-search ~/.zsh/zsh-history-substring-search\r\ngit clone https://github.com/lukechilds/zsh-better-npm-completion ~/.zsh/zsh-better-npm-completion\r\n```\r\n\r\n## Create the .zshrc file\r\n\r\nThe `.zshrc` file is used to configure the shell session and make use of the above scripts. Run the command:\r\n\r\n```\r\nnano ~/.zshrc\r\n```\r\n\r\nAnd paste in the following:\r\n\r\n```\r\n# --- STARTUP SYSTEM INFO (Linux interactive shells only) ---\r\nif [[ -o interactive && \"$(uname -s)\" == \"Linux\" ]]; then\r\n  echo\r\n\r\n  # Header\r\n  print -P \"%F{cyan}=== System Info ===%f\"\r\n\r\n  # Core info\r\n  print -P \"Host:    %F{green}%m%f\"\r\n  print -P \"User:    %F{green}%n%f\"\r\n  print -P \"Uptime:  %F{green}$(uptime -p)%f\"\r\n  print -P \"Kernel:  %F{green}$(uname -r)%f\"\r\n\r\n  # OS info (safe fallback if file missing)\r\n  if [[ -r /etc/os-release ]]; then\r\n    os=$(awk -F= '/^PRETTY_NAME=/ {gsub(/\"/,\"\",$2); print $2}' /etc/os-release)\r\n    print -P \"OS:      %F{green}$os%f\"\r\n  fi\r\n\r\n  # RAM usage\r\n  ram=$(free | awk '/^Mem:/ {used=$3; total=$2; pct=int(used/total*100); printf \"%.1f/%.1fG (%d%%%%)\", used/1024/1024, total/1024/1024, pct}')\r\n  print -P \"RAM:     %F{green}$ram%f\"\r\n  # Root disk usage (cleaner parsing)\r\n  disk=$(df -h / | awk 'NR==2 {print $3 \"/\" $2 \" (\" $5 \"%)\"}')\r\n  print -P \"Disk:    %F{green}$disk%f\"\r\n  # Inode usage\r\n  inode=$(df -i / | awk 'NR==2 {gsub(/%/,\"\",$5); print $3 \"/\" $2 \" (\" $5 \"%%)\"}')\r\n  print -P \"Inodes:  %F{green}$inode%f\"\r\n  echo\r\nfi\r\n\r\n# --- PATH CUSTOMISATION ---\r\n# Ensure ~/bin is at the front of PATH (and avoid duplicates)\r\ntypeset -U path\r\npath=(~/bin $path)\r\n\r\n# --- HISTORY SETTINGS ---\r\n# File location and size limits for command history\r\nHISTFILE=~/.zsh_history\r\nHISTSIZE=5000      # commands kept in memory\r\nSAVEHIST=5000      # commands saved to file\r\n\r\n# Share history across all terminal sessions in real time\r\nsetopt SHARE_HISTORY\r\n\r\n# Skip saving duplicate consecutive commands\r\nsetopt HIST_IGNORE_DUPS\r\n\r\n# --- PURE PROMPT SETUP ---\r\n# Add Pure prompt functions to fpath if installed, then enable Pure theme\r\nif [[ -d \"$HOME/.zsh/pure\" ]]; then\r\n  fpath+=(\"$HOME/.zsh/pure\")\r\n  autoload -U promptinit; promptinit\r\n  prompt pure\r\nfi\r\n\r\n# --- COMPLETION SETTINGS ---\r\n# Enable Zsh's programmable completion system\r\nautoload -Uz compinit && compinit\r\n\r\n# Make completion case-insensitive (e.g. \"doc\" → \"Documents\")\r\nzstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'\r\n\r\n# --- SSH HOST COMPLETION ---\r\n# Populate SSH/SCP completion from ~/.ssh/config (ignore wildcard entries)\r\nif [ -f ~/.ssh/config ]; then\r\n  h=($(grep -i \"^Host \" ~/.ssh/config | grep -v \"*\" | awk '{print $2}'))\r\n  zstyle ':completion:*:*:ssh:*:hosts' hosts $h\r\n  zstyle ':completion:*:*:scp:*:hosts' hosts $h\r\nfi\r\n\r\n# --- COMPLETION UI TWEAKS ---\r\n# Enable interactive selection menu and improve descriptions\r\nzstyle ':completion:*:*:ssh:*' menu select\r\nzstyle ':completion:*' verbose yes\r\nzstyle ':completion:*:descriptions' format '%B--- %d ---%b'\r\n\r\n# --- NPM COMPLETION ---\r\n# Load enhanced npm completion if available\r\n[[ -f ~/.zsh/zsh-better-npm-completion/zsh-better-npm-completion.plugin.zsh ]] && \\\r\n  source ~/.zsh/zsh-better-npm-completion/zsh-better-npm-completion.plugin.zsh\r\n\r\n# --- HISTORY SUBSTRING SEARCH ---\r\n# Allows up/down arrows to search history by current input\r\nif [[ -f ~/.zsh/zsh-history-substring-search/zsh-history-substring-search.zsh ]]; then\r\n  source ~/.zsh/zsh-history-substring-search/zsh-history-substring-search.zsh\r\n\r\n  # Bind arrow keys (terminfo first, fallback sequences if needed)\r\n  bindkey \"$terminfo[kcuu1]\" history-substring-search-up\r\n  bindkey \"$terminfo[kcud1]\" history-substring-search-down\r\n  bindkey '^[[A' history-substring-search-up\r\n  bindkey '^[[B' history-substring-search-down\r\nfi\r\n\r\n# --- AUTOSUGGESTIONS ---\r\n# Fish-style inline suggestions based on history\r\nif [[ -f ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh ]]; then\r\n  source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh\r\n\r\n  # Subtle grey suggestion text\r\n  ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'\r\n\r\n  # Accept suggestion with right arrow\r\n  bindkey '^[[C' autosuggest-accept\r\nfi\r\n\r\n# ---------------\r\n# --- ALIASES ---\r\n# ---------------\r\n\r\n# --- Git helpers ---\r\nalias gs='git status'\r\nalias gd='git diff'\r\nalias ga='git add'\r\nalias gc='git commit'\r\nalias gcm='git commit -m'\r\nalias gl='git log --oneline --graph --decorate'\r\nalias gp='git pull'\r\nalias gps='git push'\r\nalias gb='git branch'\r\nalias gco='git checkout'\r\n\r\n# File list helpers\r\nalias ls='ls --color=auto'\r\nalias ll='ls -lh --color=auto'\r\nalias la='ls -lah --color=auto'\r\n\r\n# --- OS-SPECIFIC HELPERS ---\r\ncase \"$(uname -s)\" in\r\n  Darwin)\r\n    # macOS\r\n    alias ports='lsof -iTCP -sTCP:LISTEN -n -P'\r\n    alias localip=\"ipconfig getifaddr en0\"\r\n    ;;\r\n  Linux)\r\n    # Linux\r\n    alias ports='ss -tulpen'\r\n    alias localip='hostname -I'\r\n    ;;\r\nesac\r\n\r\n# Public IPv4 address\r\nalias myip='curl -4 ifconfig.me && echo'\r\n```\r\n\r\n## Set Z shell as the default shell\r\n\r\nEnter the following command to set Z shell as your default shell:\r\n\r\n```\r\nchsh -s $(which zsh)\r\n```\r\n\r\n**Note:** you'll be prompted for your user password to confirm the change.",
    "tags": [],
    "published_at": "2026-04-27 17:45:00",
    "url": "https://blog.philipnewborough.co.uk/posts/my-z-shell-setup",
    "featured_image": "https://blog.philipnewborough.co.uk/media/og-b4ef2860-577f-4426-bd3e-124a2ccd356c.png"
}