Posted on
Artificial Intelligence

Prompt Optimisation

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Turbocharge your Bash: Practical Prompt Optimisation

You look at your terminal hundreds of times a day. If your prompt is slow, uninformative, or cluttered, you pay a tiny tax every time you press Enter. Over a week, that’s a lot of lost flow. Prompt optimisation is about making your Bash prompt fast, informative, and reliable—so you make fewer mistakes and move faster.

This article shows why your prompt matters, what actually slows it down, and how to optimise it with concrete, copy‑pasteable snippets. We’ll cover native Bash tricks and a few battle-tested prompt frameworks, with installation instructions for apt, dnf, and zypper where relevant.

Why optimise your prompt?

  • Speed: Subshells, external commands, and naïve Git checks can add 100–500 ms to every prompt render. That’s a full second every 2–10 commands.

  • Signal > noise: A clear visual indicator for exit status, directory, and Git state reduces context switching and errors.

  • Safety: Distinguish root vs. user and local vs. remote hosts at a glance to avoid dangerous mistakes.

  • Consistency: A robust prompt behaves the same across machines and SSH sessions.

Core principles and actionable steps

1) Build a fast, readable base prompt (no heavy subshells)

Start with ANSI colors and proper non-printing markers so line wrapping and editing stay sane. Use built-ins and avoid invoking external commands in your PS1.

Add this to your ~/.bashrc:

# Colors (wrap control sequences in \[ \] so Bash knows they're non-printing)
RESET='\[\e[0m\]'
BOLD='\[\e[1m\]'
FG_RED='\[\e[31m\]'
FG_GREEN='\[\e[32m\]'
FG_BLUE='\[\e[34m\]'
FG_YELLOW='\[\e[33m\]'

# Keep the path short in deep directories
PROMPT_DIRTRIM=3

# Capture exit status, compute a color, and optional duration (see step 3)
__prompt_command() {
  local exit_code=$?
  if (( exit_code == 0 )); then
    PS1_STATUS="${FG_GREEN}✔${RESET}"
  else
    PS1_STATUS="${FG_RED}✘${RESET} ${FG_RED}${exit_code}${RESET}"
  fi

  # Show long command duration if available (set in step 3)
  if [[ -n "$PS1_DURATION" ]]; then
    PS1_TIME="${FG_YELLOW}${PS1_DURATION}s${RESET} "
  else
    PS1_TIME=""
  fi
}

# Ensure we don't clobber existing PROMPT_COMMAND
if [[ -n "$PROMPT_COMMAND" ]]; then
  PROMPT_COMMAND="__prompt_command; $PROMPT_COMMAND"
else
  PROMPT_COMMAND="__prompt_command"
fi

# The prompt itself: [status time] user@host path $
PS1='${PS1_STATUS} ${PS1_TIME}'"${BOLD}\u${RESET}@\h ${FG_BLUE}\w${RESET} \$ "

Notes:

  • Avoid $(...) inside PS1 unless absolutely necessary.

  • Use PROMPT_DIRTRIM to keep path readable without slow external calls.

2) Git-aware without lag (use __git_ps1, not raw git calls)

Calling git status in your prompt is slow. Instead, use Git’s lightweight helper __git_ps1, shipped with Git, which avoids most overhead.

First, ensure Git is installed:

  • apt (Debian/Ubuntu): sudo apt update && sudo apt install -y git

  • dnf (Fedora/RHEL/CentOS Stream): sudo dnf install -y git

  • zypper (openSUSE): sudo zypper install -y git

Then, source git-prompt.sh from one of the common locations and configure it:

# Try common locations for git-prompt
for p in \
  /usr/share/git/completion/git-prompt.sh \
  /usr/share/git-core/contrib/completion/git-prompt.sh \
  /usr/lib/git-core/git-sh-prompt \
  /usr/share/git/git-prompt.sh
do
  [[ -r "$p" ]] && source "$p" && break
done

# Optional Git prompt settings
export GIT_PS1_SHOWDIRTYSTATE=1     # * for unstaged, + for staged
export GIT_PS1_SHOWUNTRACKEDFILES=1 # % for untracked
export GIT_PS1_SHOWUPSTREAM=auto    # show upstream divergence
export GIT_PS1_COMPRESSSPARSESTATE=1

# Integrate __git_ps1 output efficiently (one subshell per prompt)
__prompt_git_segment() {
  if declare -F __git_ps1 >/dev/null; then
    __git_ps1 ' (%s)'
  fi
}

# Extend existing PROMPT_COMMAND to compute Git segment once
__prompt_command() {
  local exit_code=$?
  if (( exit_code == 0 )); then
    PS1_STATUS="${FG_GREEN}✔${RESET}"
  else
    PS1_STATUS="${FG_RED}✘${RESET} ${FG_RED}${exit_code}${RESET}"
  fi

  # long command duration assembled in step 3
  if [[ -n "$PS1_DURATION" ]]; then
    PS1_TIME="${FG_YELLOW}${PS1_DURATION}s${RESET} "
  else
    PS1_TIME=""
  fi

  PS1_GIT="$(__prompt_git_segment)"
}

# Merge with existing PROMPT_COMMAND safely
if [[ "$PROMPT_COMMAND" != *"__prompt_command"* ]]; then
  if [[ -n "$PROMPT_COMMAND" ]]; then
    PROMPT_COMMAND="__prompt_command; $PROMPT_COMMAND"
  else
    PROMPT_COMMAND="__prompt_command"
  fi
fi

# Add Git segment to PS1, no extra subshells here
PS1='${PS1_STATUS} ${PS1_TIME}'"${BOLD}\u${RESET}@\h ${FG_BLUE}\w${RESET}\${PS1_GIT} \$ "

Real-world impact: On large monorepos, replacing ad-hoc $(git rev-parse ...) with __git_ps1 can cut prompt render from ~200–500 ms to under 20 ms.

3) Show long command duration, without slowing anything else

A tiny bit of timing is incredibly useful—only when it matters.

# Start a timer before each command
trap 'CMD_START=$SECONDS' DEBUG

# In __prompt_command (already defined in steps 1–2), compute duration:
# Add this snippet inside __prompt_command:
if [[ -n "$CMD_START" ]]; then
  local dur=$(( SECONDS - CMD_START ))
  if (( dur >= 2 )); then   # show only if >= 2 seconds
    PS1_DURATION="$dur"
  else
    PS1_DURATION=""
  fi
fi

This uses built-in shell timing and keeps your fast-path fast.

4) Safety and ergonomics: root/SSH awareness, clean lines

  • Color root prompts red automatically:
if [[ $EUID -eq 0 ]]; then
  FG_IDENTITY='\[\e[31m\]'  # red for root
else
  FG_IDENTITY='\[\e[32m\]'  # green for normal user
fi
PS1='${PS1_STATUS} ${PS1_TIME}'"${FG_IDENTITY}\u${RESET}@\h ${FG_BLUE}\w${RESET}\${PS1_GIT} \$ "
  • Make remote sessions obvious:
if [[ -n "$SSH_CONNECTION" ]]; then
  HOST_COLOR='\[\e[35m\]'   # magenta when over SSH
else
  HOST_COLOR='\[\e[36m\]'   # cyan locally
fi
PS1='${PS1_STATUS} ${PS1_TIME}'"${FG_IDENTITY}\u${RESET}@${HOST_COLOR}\h${RESET} ${FG_BLUE}\w${RESET}\${PS1_GIT} \$ "
  • Keep lines wrapping correctly: Always wrap color codes in \[ \]. Don’t use external commands like basename or pwd in PS1—use \W and \w.

5) Prefer a ready-made prompt if you want features fast

If you’d rather skip DIY and get a polished prompt quickly, these frameworks are excellent and fast.

  • Starship (cross-shell, minimal, very fast)

    • apt: sudo apt update && sudo apt install -y starship
    • dnf: sudo dnf install -y starship
    • zypper: sudo zypper install -y starship
    • Setup for Bash (add to ~/.bashrc):
    eval "$(starship init bash)"
    
    • Optional: If your repo doesn’t include starship, install via the official script:
    curl -fsSL https://starship.rs/install.sh | sh
    
  • Liquidprompt (adaptive Bash/Zsh prompt with context-aware info)

    • apt: sudo apt update && sudo apt install -y liquidprompt
    • dnf: sudo dnf install -y liquidprompt
    • zypper: sudo zypper install -y liquidprompt
    • Enable in ~/.bashrc:
    [[ -r "/usr/share/liquidprompt/liquidprompt" ]] && source /usr/share/liquidprompt/liquidprompt
    
  • Powerline (classic, segment-based; also used in Vim/Tmux)

    • apt: sudo apt update && sudo apt install -y powerline fonts-powerline
    • dnf: sudo dnf install -y powerline powerline-fonts
    • zypper: sudo zypper install -y powerline powerline-fonts
    • Bash integration (add to ~/.bashrc):
    if [ -f /usr/share/powerline/bindings/bash/powerline.sh ]; then
      source /usr/share/powerline/bindings/bash/powerline.sh
    fi
    

Font note:

  • For symbols/glyphs, install the font packages above and select a Powerline-compatible or Nerd Font in your terminal profile. On Debian/Ubuntu, fonts-powerline is usually enough; on Fedora/openSUSE, install powerline-fonts.

Troubleshooting and performance tips

  • Profile your prompt: Temporarily add set -x at the top of your ~/.bashrc and open a new shell; look for repeated external calls on every prompt. Remove set -x after.

  • Consolidate PROMPT_COMMAND: Some tools (e.g., direnv, kubectl prompt helpers) append to PROMPT_COMMAND. Chain them carefully so you don’t overwrite anything.

  • Avoid fork bombs in PS1: Each $(...) runs a subshell. Compute once in PROMPT_COMMAND and store results in variables.

  • Keep Git cheap: If even __git_ps1 feels heavy in massive repos, set GIT_OPTIONAL_LOCKS=0 in your environment and/or disable some GIT_PS1_* flags.

Conclusion / Call to Action

Your prompt fires thousands of times a week—make it work for you. Start with the minimal fast prompt (step 1), add Git via __git_ps1 (step 2), and sprinkle in duration and safety cues (steps 3–4). If you want full-featured polish right now, install Starship or Liquidprompt (step 5) with the package manager commands above.

Next step: 1) Back up your current ~/.bashrc. 2) Paste in the snippets you want. 3) Open a new terminal and feel the difference.

Got a favorite prompt trick or a before/after timing? Share it and inspire someone else to optimise their shell.