Gum: Shell Scripting with Style and Elegance
When you think of shell scripting, the image that often comes to mind is a plain, text‑only interface that gets the job done but lacks any flair. Enter Gum—a tiny yet powerful Go‑based library that lets you sprinkle modern UI components into your Bash scripts without sacrificing the simplicity you love. In this article we’ll explore why Gum is a game‑changer, walk through practical examples, and share pro tips to keep your scripts both functional and elegant.
Why Choose Gum?
Gum bridges the gap between raw terminal output and polished user experiences. It offers ready‑made widgets like spinners, progress bars, tables, and interactive prompts, all rendered with ANSI colors and Unicode symbols. Because it’s a compiled binary, you don’t need a heavyweight runtime; just drop the executable alongside your script.
Beyond aesthetics, Gum improves usability. Clear prompts reduce input errors, and visual feedback (like spinners) reassures users that long‑running tasks are still active. In collaborative environments, a well‑styled script also conveys professionalism and attention to detail.
Key Features at a Glance
- Zero‑dependency binary (single file)
- Cross‑platform support: Linux, macOS, Windows (via WSL)
- Rich set of widgets:
spinner,progress,confirm,select,table, and more - Customizable colors, icons, and layout via flags or environment variables
- Seamless integration with any shell (bash, zsh, fish)
Getting Started
Installation is straightforward. The official GitHub releases page provides pre‑built binaries. For most Linux distributions you can use a one‑liner:
curl -sSL https://github.com/charmbracelet/gum/releases/latest/download/gum_$(uname -s)_$(uname -m).tar.gz | tar -xz -C /usr/local/bin
After placing gum in your $PATH, verify the installation:
gum version
Now you’re ready to replace the classic read and echo patterns with interactive, styled components.
Example 1: Interactive Confirmation Prompt
Imagine a deployment script that asks the operator to confirm before proceeding. A plain read -p can be ambiguous; Gum’s confirm widget makes the intent crystal clear.
#!/usr/bin/env bash
# Deploy to production?
if gum confirm "🚀 Deploy to production now?" --affirmative="Yes, go!" --negative="No, abort"; then
echo "🔧 Starting deployment..."
# Simulate work
sleep 2
echo "✅ Deployment complete."
else
echo "🛑 Deployment aborted by user."
exit 1
fi
The gum confirm command returns an exit status of 0 for affirmative answers and 1 otherwise, allowing you to branch naturally in Bash. Notice the use of emojis and custom button labels—these tiny touches instantly make the script feel more personable.
Pro tip: SetGUM_STYLEin your environment to enforce a consistent color palette across all Gum widgets. For example,export GUM_STYLE="foreground:#ffffff background:#282c34"gives a dark‑mode look that works well on most terminals.
Example 2: Progress Bar for Batch Operations
Long‑running loops often leave users staring at a static cursor. Gum’s progress widget provides a real‑time visual cue that updates as each iteration completes. Below is a script that processes a list of image files, showing progress as it goes.
#!/usr/bin/env bash
set -euo pipefail
# Directory containing images
IMG_DIR="images"
# Collect all PNG files
files=($(ls "$IMG_DIR"/*.png 2>/dev/null || true))
total=${#files[@]}
if (( total == 0 )); then
echo "❌ No PNG files found in $IMG_DIR."
exit 1
fi
# Start the progress bar in the background
gum progress --title="🖼️ Optimizing images…" --total=$total &
pid=$!
# Process each file
for img in "${files[@]}"; do
# Simulate an optimization step (e.g., using optipng)
# optipng -o7 "$img" >/dev/null
sleep 0.3 # placeholder for real work
# Increment the progress bar
kill -SIGUSR1 $pid
done
# Close the progress bar
wait $pid
echo "✅ All $total images optimized."
The trick here is sending SIGUSR1 to the progress process each time a file finishes. Gum listens for this signal and automatically increments the bar. This pattern works for any loop where you can calculate the total work upfront.
Pro tip: If your task’s total count isn’t known ahead of time, use gum spinner instead. It gives a continuous animation without a numeric progress indicator, ideal for streaming data or unknown‑duration operations.
Example 3: Dynamic Table Output
When scripts need to present structured data—think service status, configuration summaries, or test results—a plain echo table can quickly become unreadable. Gum’s table widget formats rows and columns with proper alignment and optional colors.
#!/usr/bin/env bash
# Sample data: service name, status, uptime
services=(
"nginx|Running|12d 4h"
"postgres|Running|30d 2h"
"redis|Stopped|—"
"cron|Running|45d 6h"
)
# Convert array to newline‑separated string for Gum
table_input=$(printf "%s\n" "${services[@]}")
gum table \
--header="Service|Status|Uptime" \
--border="rounded" \
--width=30 \
--align="left,center,right" \
--foreground="cyan" \
--border-foreground="magenta" \
<<< "$table_input"
The gum table command reads tab‑separated values from stdin, so we pipe the prepared string using a here‑document. The --align flag lets you control column alignment individually, while --border adds a sleek rounded frame.
Real‑World Use Cases
CI/CD pipelines: Embed Gum prompts in pre‑deployment checks to require manual approval when running in interactive mode, while allowing a --yes flag to bypass prompts for automated runs.
System administration: Use spinners while waiting for services to restart, and progress bars for bulk package installations. The visual feedback reduces the perceived wait time for admins monitoring dashboards via SSH.
Developer tooling: Build CLI utilities that list available templates, let users pick one via a gum select menu, and then scaffold a project with colored status messages.
Advanced Styling Techniques
Gum respects the GUM_STYLE environment variable, which accepts a semicolon‑separated list of CSS‑like properties. This makes it possible to enforce brand colors across an entire suite of scripts.
export GUM_STYLE="foreground:#e5e9f0;background:#1e1e2e;border:#6272a4"
For per‑widget overrides, most commands provide --foreground, --background, and --border-foreground flags. Combine these with the --border option (choices: none, rounded, double) to match the visual language of your organization.
Creating Reusable UI Functions
Instead of sprinkling gum calls throughout a monolithic script, wrap common interactions in Bash functions. This improves readability and makes future updates (like changing colors) a single‑line edit.
#!/usr/bin/env bash
# Centralized UI helpers
ui_confirm() {
local prompt=$1
gum confirm "$prompt" --affirmative="Yes" --negative="No"
}
ui_spinner() {
local message=$1
gum spin --title="$message" --spinner="dot" "$@"
}
# Example usage
if ui_confirm "🧹 Clean temporary files?"; then
ui_spinner "Cleaning…" rm -rf /tmp/*
echo "✅ Cleaned."
fi
By abstracting the UI layer, you can swap Gum for another library later without rewriting the core logic.
Performance Considerations
Because Gum is a compiled binary, its overhead is minimal—typically a few milliseconds per invocation. However, launching a separate process inside tight loops can add up. To mitigate this, batch multiple UI updates into a single call when possible, or keep the process alive (as shown with the progress bar example).
Another tip: redirect Gum’s standard error to /dev/null in non‑interactive contexts. This prevents stray ANSI codes from contaminating log files.
Pro tip: When running scripts in CI environments that lack a TTY, guard Gum calls with [ -t 1 ] && gum …. This ensures the script degrades gracefully without breaking the pipeline.
Testing Gum‑Enhanced Scripts
Automated testing of interactive scripts can be tricky. Use tools like expect or script to simulate user input. Since Gum reads from stdin, you can pipe predefined answers directly.
#!/usr/bin/env bash
# Test harness
printf "y\n" | bash deploy.sh
# Verify exit code, log output, etc.
For more robust CI, consider a “headless” mode where you set environment variables (e.g., GUM_NO_STYLE=1) to suppress UI and fallback to plain text prompts.
Best Practices Checklist
- Keep UI logic separate from business logic.
- Use emojis sparingly—one per widget is enough to convey intent.
- Define a global
GUM_STYLEto maintain consistency. - Prefer
gum confirmandgum selectover rawreadfor better validation. - Test both interactive and non‑interactive paths.
- Document required Gum version in your README to avoid version drift.
Conclusion
Gum transforms the humble shell script into a user‑friendly, visually appealing tool without imposing heavy dependencies. By leveraging its widgets—spinners, progress bars, tables, and interactive prompts—you can deliver scripts that feel modern, reduce errors, and keep users engaged. Remember to centralize UI helpers, respect environment‑based styling, and test for both TTY and non‑TTY scenarios. With these practices in place, your Bash scripts will not only work flawlessly but also look polished, earning you the reputation of a developer who writes code with both substance and style.