Full Step‑By‑Step Manual Adding Custom Animated Spinners to Bash
Everything you need → concepts → scripts → installer → integration → extensions
1. Introduction
A spinner is a tiny animation that runs while a command is doing work.
In Bash the animation is nothing more than a loop that repeatedly prints a
different character (or emoji) on the same line, sleeps a short time, and then
updates the line again.
Why is this useful?
- Visual feedback – you know something is happening, even for long
makeorgitjobs. - Fun – a little emoji‑style animation makes the terminal feel alive.
- Modular – you can swap styles, colours, and speeds in a single place.
Bash does not have native “pre‑command”/“post‑command” hooks like Zsh, so we need a small helper library called bash‑preexec. It creates two functions you can implement:
preexec()– runs right before each command starts.precmd()– runs right after the prompt is displayed (i.e. after the command finishes).
Our spinner will be started from preexec() and killed from precmd().
2. How Bash Hooking Works
2.1 What Bash normally offers
| Feature | Bash | Zsh |
|---|---|---|
preexec hook |
❌ (no built‑in) | ✅ |
precmd hook |
❌ (no built‑in) | ✅ |
PROMPT_COMMAND |
✅ (runs before each prompt) | ✅ |
Because Bash lacks preexec, the bash‑preexec script works around this limitation by:
- Setting a DEBUG trap that runs just before a command is executed.
- Overriding
PROMPT_COMMANDto run a post‑prompt function.
The library is tiny (≈ 250 lines) and safe for most environments.
2.2 Installing the library
The installer we will write automatically downloads the latest version from GitHub
if it isn’t already present:
curl -sSL https://raw.githubusercontent.com/rcaloras/bash-preexec/master/bash-preexec.sh \
-o "$INSTALL_DIR/bash-preexec.sh"
After that we just source it and the two hook functions become available.
3. How a Spinner Works Internally
Below is a minimal “bare‑bones” spinner (no colours, no fancy emojis).
Understanding it will make the later, more feature‑rich version easier to follow.
# 1️⃣ Define the animation frames (any characters you like)
frames=( '-' '\' '|' '/' ) # classic rotating bar
# 2️⃣ Function that draws the spinner while $1 (a PID) is alive
spinner() {
local pid=$1 # PID of the command we are watching
local i=0 # index into frames array
while kill -0 "$pid" 2>/dev/null; do # “kill -0” checks if PID exists
printf "\r[%s] Working..." "${frames[i]}"
i=$(( (i + 1) % ${#frames[@]} )) # wrap around
sleep 0.1 # <-- speed (seconds per frame)
done
printf "\r✔️ Done!%*s\n" "$(tput cols)" "" # clear line, show check‑mark
}
kill -0 $pid– does not send a signal; it merely asks “does this PID exist?” – perfect for a poll loop.\r– carriage return; brings the cursor back to the start of the line without a newline, allowing us to overwrite the same line.sleep 0.1– controls the animation speed. Smaller numbers → faster spin, larger numbers → slower spin.
The spinner must run in the background so that the main command can continue:
spinner "$CMD_PID" & # start spinner in background
SPINNER_PID=$! # $! is the PID of the last background job
When the command finishes we kill the spinner:
kill "$SPINNER_PID" 2>/dev/null || true # ignore “no such process” errors
unset SPINNER_PID
4. Directory Structure for a Clean Installation
All files live under a single hidden directory in the user’s home folder:
~/.hhgttg/
├── hhgttg.sh # main spinner + hook functions
├── towel.txt # optional “keep your towel” lore (fun)
├── bash-preexec.sh # downloaded automatically if missing
└── config.sh # optional user‑config (style, speed, colours)
Why this layout?
- Isolation – nothing clutters
$HOMEor/etc. - Version control – you can
git initinside~/.hhgttgand track changes. - Easy uninstall – just delete the folder and the lines we add to
~/.bashrc.
5. Main Spinner Script (hhgttg.sh)
Copy the following into ~/.hhgttg/hhgttg.sh.
It contains:
- Randomized spinner sets (emoji, Braille, sci‑fi, etc.)
- Colour handling (ANSI escape codes)
- Adjustable speed via an environment variable (
HHGTTG_SPINNER_SPEED) - Optional HHGTTG quote + towel output after each command
- Integration with
bash‑preexec(preexec/precmdfunctions)
#!/usr/bin/env bash
#====================================================================
# HHGTTG (The Hitchhiker's Guide to the Galaxy) – Bash Spinner
#====================================================================
# This file is meant to be sourced from ~/.bashrc.
# It defines:
# * preexec – start a spinner in the background
# * precmd – kill the spinner and print a random quote/towel
# * spinner() – the animation engine
#====================================================================
# ------------------------------------------------------------------
# 1️⃣ Helper: random quote -------------------------------------------------
_hhg_quote() {
local quotes=(
"Don't Panic."
"Time is an illusion. Lunchtime doubly so."
"The answer is 42."
"The ships hung in the air in much the same way bricks don't."
"We apologize for the inconvenience."
"Mostly harmless."
"Life... is like a grapefruit."
"So long, and thanks for all the fish."
)
# Pick one at random
echo "${quotes[$RANDOM % ${#quotes[@]}]}"
}
# ------------------------------------------------------------------
# 2️⃣ Helper: towel (optional lore) ------------------------------------
_hhg_towel() {
# If the file does not exist, just skip output
[[ -f "$HOME/.hhgttg/towel.txt" ]] || return
# Prefix each line with a small bullet for visual separation
sed -e 's/^/🔹 /' "$HOME/.hhgttg/towel.txt"
}
# ------------------------------------------------------------------
# 3️⃣ Helper: pick a random spinner set --------------------------------
_hhg_spinners() {
# You can add more sets here; each set is a space‑separated string.
case $((RANDOM % 7)) in
0) echo "🌑 🌒 🌓 🌔 🌕 🌖 🌗 🌘" ;; # Moon phases
1) echo "◐ ◓ ◑ ◒" ;; # Classic circle quadrants
2) echo "⠁ ⠂ ⠄ ⡀ ⢀ ⠠ ⠐ ⠈" ;; # Braille pattern
3) echo "🛸 👽 ⭐ 💫" ;; # Sci‑fi emojis
4) echo "🔄 🔃 🔁" ;; # Arrow loop
5) echo "⏳ ⏱️ ⏲️" ;; # Hourglass set
6) echo "🐍 🐉 🐲" ;; # Mythical dragons (just for fun)
esac
}
# ------------------------------------------------------------------
# 4️⃣ Core spinner function ---------------------------------------------
spinner() {
local pid=$1 # PID of the command we watch
local speed="${HHGTTG_SPINNER_SPEED:-0.12}" # seconds per frame, can be overridden
local frames=($( _hhg_spinners )) # turn the string into an array
local i=0
local colour="\e[33m" # yellow (you can change or make it configurable)
printf "%b" "$colour"
while kill -0 "$pid" 2>/dev/null; do
printf "\r[%s] Working..." "${frames[i]}"
i=$(( (i + 1) % ${#frames[@]} ))
sleep "$speed"
done
# When the loop exits the command is finished
printf "\r\e[32m✔️ Done!%*s\e[0m\n" "$(tput cols)" ""
}
# ------------------------------------------------------------------
# 5️⃣ Hook: preexec → start spinner ---------------------------------------
preexec() {
# $BASH_COMMAND contains the command line that is about to be executed.
# $$ is the PID of the current shell – the spinner will watch **that**
# PID because the command runs as a *child* of the shell.
(spinner "$$") &
SPINNER_PID=$!
}
# ------------------------------------------------------------------
# 6️⃣ Hook: precmd → stop spinner, show quote/towel ----------------------
precmd() {
# 1️⃣ Stop the background spinner (if any)
if [[ -n "$SPINNER_PID" ]]; then
kill "$SPINNER_PID" 2>/dev/null || true
unset SPINNER_PID
fi
# 2️⃣ Print a colourful quote on a new line
echo -e "\n\e[36m$(_hhg_quote)\e[0m"
# 3️⃣ Optionally print the towel text (grey colour)
if [[ -f "$HOME/.hhgttg/towel.txt" ]]; then
echo -e "\e[90m$(_hhg_towel)\e[0m"
fi
}
# ------------------------------------------------------------------
# 7️⃣ Export the hook functions for bash‑preexec to see ------------------
export -f preexec precmd spinner
# --------------------------------------------------------------------
What the script does, line‑by‑line
| Line / Section | Why it matters |
|---|---|
#!/usr/bin/env bash |
Makes the file runnable if you ever execute it directly (not required for source). |
export -f … |
bash-preexec runs the functions in a sub‑shell; exporting makes them visible there. |
HHGTTG_SPINNER_SPEED env var |
Allows you to change the animation speed on the fly, e.g. export HHGTTG_SPINNER_SPEED=0.05. |
printf "\e[33m" |
Sets yellow colour for the spinner; reset later with \e[0m. |
kill -0 "$pid" |
Non‑destructive way to check if the watched PID is still alive. |
preexec/precmd |
Hook functions that are automatically called by the bash‑preexec library. |
tput cols |
Returns terminal width; we pad the “Done!” line so the old spinner chars disappear completely. |
8. One‑File Installer Script (install_hhgttg.sh)
Save the following as install_hhgttg.sh (anywhere you like, e.g. your Downloads folder).
Running it will:
- Create
~/.hhgttg(the install directory). - Drop
towel.txt(the fun lore file). - Drop
hhgttg.sh(the spinner script). - Download
bash-preexec.shif not already present. - Append the required
sourcelines to~/.bashrc(only once). - Print a friendly “installation complete” message.
#!/usr/bin/env bash
#====================================================================
# Installer for the HHGTTG Bash spinner + bash‑preexec integration
#====================================================================
set -euo pipefail # fail fast, treat unset vars as errors
# --------------------------------------------------------------------
# 1️⃣ Define where everything will live
INSTALL_DIR="$HOME/.hhgttg"
BASHRC="$HOME/.bashrc"
PREEXEC_URL="https://raw.githubusercontent.com/rcaloras/bash-preexec/master/bash-preexec.sh"
# --------------------------------------------------------------------
# 2️⃣ Create directory
mkdir -p "$INSTALL_DIR"
# --------------------------------------------------------------------
# 3️⃣ Install the optional towel text
cat > "$INSTALL_DIR/towel.txt" <<'EOF'
Always know where your towel is.
EOF
# --------------------------------------------------------------------
# 4️⃣ Install the main spinner script (hhgttg.sh)
cat > "$INSTALL_DIR/hhgttg.sh" <<'EOF'
# --------------------------------------------------------------------
# HHGTTG Spinner – copied verbatim from the manual (section 5)
# --------------------------------------------------------------------
#!/usr/bin/env bash
_hhg_quote() {
local quotes=(
"Don't Panic."
"Time is an illusion. Lunchtime doubly so."
"The answer is 42."
"The ships hung in the air in much the same way bricks don't."
"We apologize for the inconvenience."
"Mostly harmless."
"Life... is like a grapefruit."
"So long, and thanks for all the fish."
)
echo "${quotes[$RANDOM % ${#quotes[@]}]}"
}
_hhg_towel() {
[[ -f "$HOME/.hhgttg/towel.txt" ]] || return
sed -e 's/^/🔹 /' "$HOME/.hhgttg/towel.txt"
}
_hhg_spinners() {
case $((RANDOM % 7)) in
0) echo "🌑 🌒 🌓 🌔 🌕 🌖 🌗 🌘" ;;
1) echo "◐ ◓ ◑ ◒" ;;
2) echo "⠁ ⠂ ⠄ ⡀ ⢀ ⠠ ⠐ ⠈" ;;
3) echo "🛸 👽 ⭐ 💫" ;;
4) echo "🔄 🔃 🔁" ;;
5) echo "⏳ ⏱️ ⏲️" ;;
6) echo "🐍 🐉 🐲" ;;
esac
}
spinner() {
local pid=$1
local speed="${HHGTTG_SPINNER_SPEED:-0.12}"
local frames=($( _hhg_spinners ))
local i=0
local colour="\e[33m"
printf "%b" "$colour"
while kill -0 "$pid" 2>/dev/null; do
printf "\r[%s] Working..." "${frames[i]}"
i=$(( (i + 1) % ${#frames[@]} ))
sleep "$speed"
done
printf "\r\e[32m✔️ Done!%*s\e[0m\n" "$(tput cols)" ""
}
preexec() {
(spinner "$$") &
SPINNER_PID=$!
}
precmd() {
if [[ -n "$SPINNER_PID" ]]; then
kill "$SPINNER_PID" 2>/dev/null || true
unset SPINNER_PID
fi
echo -e "\n\e[36m$(_hhg_quote)\e[0m"
if [[ -f "$HOME/.hhgttg/towel.txt" ]]; then
echo -e "\e[90m$(_hhg_towel)\e[0m"
fi
}
export -f preexec precmd spinner
EOF
# --------------------------------------------------------------------
# 5️⃣ Download bash‑preexec (if we don't already have it system‑wide)
if [[ -f /etc/bash-preexec.sh ]]; then
PREEXEC_SOURCE="/etc/bash-preexec.sh"
else
PREEXEC_SOURCE="$INSTALL_DIR/bash-preexec.sh"
if [[ ! -f "$PREEXEC_SOURCE" ]]; then
echo "Downloading bash-preexec..."
curl -fsSL "$PREEXEC_URL" -o "$PREEXEC_SOURCE"
echo "bash-preexec downloaded to $PREEXEC_SOURCE"
fi
fi
# --------------------------------------------------------------------
# 6️⃣ Append source lines to ~/.bashrc (only once)
if ! grep -Fxq "# HHGTTG PROMPT + SPINNER" "$BASHRC"; then
cat >> "$BASHRC" <<EOF
# ------------------------------------------------------------
# HHGTTG PROMPT + SPINNER
# ------------------------------------------------------------
# Load bash‑preexec (provides preexec()/precmd() hooks)
source "$PREEXEC_SOURCE"
# Load the HHGTTG spinner functions
source "$INSTALL_DIR/hhgttg.sh"
EOF
echo "Added source lines to $BASHRC"
else
echo "Source lines already present in $BASHRC – skipping."
fi
# --------------------------------------------------------------------
# 7️⃣ Final instructions
echo "✨ HHGTTG spinner installed successfully!"
echo "To start using it, either:"
echo " • Open a new terminal, or"
echo " • Run: source \"$BASHRC\""
echo ""
echo "You can customise the speed by adding, for example:"
echo " export HHGTTG_SPINNER_SPEED=0.05 # faster"
echo "to your ~/.bashrc before the HHGTTG block."
How to run the installer
chmod +x install_hhgttg.sh # make it executable (once)
./install_hhgttg.sh # run it
The script is idempotent: running it again will not duplicate entries
or re‑download files unnecessarily.
9. Verify Everything Works
- Try a longer command (e.g.,
find . -type f | wc -l) and notice the spinner stays visible the whole time. - Force a specific style (optional). Edit
~/.hhgttg/hhgttg.shand replace thecasein_hhg_spinners()with a single line, e.g.:
echo "⏳ ⏱️ ⏲️"
Save, reload ~/.bashrc, and enjoy the new animation.
Change the speed on the fly:
export HHGTTG_SPINNER_SPEED=0.05 # faster
sleep 2
The animation should now be noticeably quicker.
Run a command that takes a couple of seconds, e.g.:
sleep 3
You should see something like:
[🌑] Working... (spins through the chosen set)
✔️ Done!
Don't Panic.
🔹 Always know where your towel is.
Reload your shell (or open a new terminal):
source ~/.bashrc
10. Adding Your Own Custom Spinner
- Locate the function
_hhg_spinners().
Save and reload:
source ~/.bashrc
Increase the modulo range in the case line:
case $((RANDOM % 8)) in # now 0‑7 inclusive
Add a new case branch, for example:
7) echo "🦀 🐙 🐠 🐡" ;; # marine life set
Open the spinner file:
nano "$HOME/.hhgttg/hhgttg.sh"
Your new style will now appear randomly among the others.
Advanced customisation
| Variable | Purpose | Example |
|---|---|---|
HHGTTG_SPINNER_SPEED |
Seconds per frame (float). | export |
HHGTTG_SPINNER_COLOUR |
ANSI colour code (without \e[ and m). |
export (magenta) |
HHGTTG_QUOTES_FILE |
Path to a custom quote file (one line per quote). | export |
To honour these variables, replace the hard‑coded colour line in spinner() with:
local colour="\e[${HHGTTG_SPINNER_COLOUR:-33}m"
and change _hhg_quote() to read from $HHGTTG_QUOTES_FILE if it exists.
11. Uninstall
If you ever want to remove the spinner completely:
# 1️⃣ Remove the installation directory
rm -rf "$HOME/.hhgttg"
# 2️⃣ Delete the lines we added to ~/.bashrc
sed -i '/# HHGTTG PROMPT + SPINNER/,+5d' "$HOME/.bashrc"
# (the `+5d` removes the block of six lines that follows the comment)
# 3️⃣ Reload the shell (or start a new terminal)
source "$HOME/.bashrc"
All traces of the spinner, the preexec library (if it was only in the~/.hhgttg folder), and the optional lore file are gone.
12. Summary
| ✅ What you now have |
|---|
| A fully‑functional spinner that starts automatically before any command and stops after it finishes. |
| Randomised animation sets (emoji, Braille, sci‑fi, moons, dragons, …). |
| Configurable speed & colour via environment variables. |
| HHGTTG‑themed quote + towel printed after each command (optional). |
A tiny installer (install_hhgttg.sh) that sets everything up in ~/.hhgttg. |
| Clean, modular directory layout – easy to back up, version‑control, or share. |
Simple uninstall – one rm -rf and a sed command. |
| Extensible – add new spinner styles, quotes, or even hook into other Bash events. |
Enjoy the new visual feedback, and remember: Don’t Panic – your spinner has got your back (and your towel). 🎉