Table of Contents
- Understanding Bash for CLI Tools
- Core Concepts: Variables and User Input
- Creating Interactive Menus
- Input Validation and Error Handling
- Handling Command-Line Arguments
- Progress Indicators and User Feedback
- Advanced Techniques
- Real-World Example: Interactive Backup Tool
- Testing and Debugging
- Best Practices
- References
1. Understanding Bash for CLI Tools
Bash is the default shell on most Linux distributions and macOS, making it a universal choice for scripting. Its strengths for CLI tools include:
- Ubiquity: Preinstalled on nearly all Unix-like systems (no setup required).
- Simplicity: Syntax is readable for beginners, with powerful built-in commands (
grep,sed,awk). - Interactivity: Native support for user input, menus, and dynamic feedback.
- Integration: Seamlessly interacts with system utilities (e.g.,
rsync,git,curl).
While languages like Python or Go offer more features, Bash excels for lightweight, system-focused tools where minimal dependencies and speed matter.
2. Core Concepts: Variables and User Input
At the heart of interactivity is capturing user input. Bash provides the read command for this, along with variables to store data.
Basic User Input with read
The read command reads input from the user and stores it in a variable. Use the -p flag to display a prompt directly:
#!/bin/bash
# interactive_greeting.sh
echo "=== Interactive Greeting ==="
read -p "Enter your name: " username # Prompt user and store input in $username
echo "Hello, $username! Welcome to the tool."
Output:
=== Interactive Greeting ===
Enter your name: Alice
Hello, Alice! Welcome to the tool.
Silent Input (e.g., Passwords)
Use read -s to hide input (useful for passwords):
read -sp "Enter your password: " password # -s suppresses output
echo # Add a newline after input
echo "Password stored (don't worry, we won't show it here!)"
3. Creating Interactive Menus
Menus let users select options without typing commands. Bash’s select loop simplifies menu creation:
Basic Menu with select
#!/bin/bash
# simple_menu.sh
echo "=== File Manager Tool ==="
echo "Please select an option:"
# Define menu options
select choice in "List Files" "Create Directory" "Exit"; do
case $choice in
"List Files")
ls -l
break # Exit loop after action
;;
"Create Directory")
read -p "Enter directory name: " dirname
mkdir "$dirname" && echo "Directory '$dirname' created!" || echo "Error: Failed to create directory."
break
;;
"Exit")
echo "Goodbye!"
exit 0
;;
*)
echo "Invalid option. Please enter a number (1-3)."
;;
esac
done
How it works:
select choice in ...displays numbered options (e.g.,1) List Files 2) Create Directory 3) Exit).- Users input a number, and
$choicestores the selected option. - The
casestatement handles logic for each choice.
4. Input Validation and Error Handling
Interactive tools must validate user input to avoid crashes or unintended behavior. Use conditional checks (if, [[ ]]) and regex for validation.
Example: Validate Numeric Input
#!/bin/bash
# age_validator.sh
read -p "Enter your age: " age
# Check if input is a number (using regex)
if [[ $age =~ ^[0-9]+$ ]]; then
if [ $age -ge 18 ]; then
echo "You are an adult."
else
echo "You are a minor."
fi
else
echo "Error: Please enter a valid number."
exit 1 # Exit with error code 1
fi
Validate File Existence
read -p "Enter a filename to check: " filename
if [ -f "$filename" ]; then
echo "File '$filename' exists."
else
echo "Error: File '$filename' not found."
exit 1
fi
5. Handling Command-Line Arguments
For non-interactive or hybrid tools, parse arguments (e.g., --name, -v) using positional parameters or getopts.
Positional Parameters
Scripts can access arguments via $1, $2, etc. ($0 is the script name):
#!/bin/bash
# greet_arg.sh
# Check if argument is provided
if [ $# -eq 0 ]; then
echo "Usage: $0 <name>"
exit 1
fi
echo "Hello, $1!" # $1 = first argument
Run with: ./greet_arg.sh Bob → Hello, Bob!
Parsing Flags with getopts
For tools with options (e.g., --verbose, --output), use getopts to parse flags:
#!/bin/bash
# advanced_tool.sh
verbose=0
output_file="output.txt"
# Parse options: -v (verbose), -o <file> (output file)
while getopts "vo:" flag; do
case $flag in
v) verbose=1 ;; # Set verbose mode
o) output_file="$OPTARG" ;; # $OPTARG = value after -o
\?) echo "Invalid option: -$OPTARG" >&2; exit 1 ;;
:) echo "Option -$OPTARG requires an argument." >&2; exit 1 ;;
esac
done
if [ $verbose -eq 1 ]; then
echo "Verbose mode enabled. Output will be saved to $output_file."
fi
Run with: ./advanced_tool.sh -v -o results.txt
6. Progress Indicators and User Feedback
Long-running tasks need feedback to keep users informed. Use spinners, progress bars, or status messages.
Spinner for Long Tasks
#!/bin/bash
# spinner.sh
spin() {
local pid=$1
local delay=0.1
local spin_chars="|/-\\"
while [ -d /proc/$pid ]; do # Check if process is running
for (( i=0; i<${#spin_chars}; i++ )); do
echo -ne "\r[${spin_chars:$i:1}] Processing..."
sleep $delay
done
done
echo -ne "\r[✓] Done! \n" # Replace spinner with success message
}
# Simulate a long task (e.g., backup, download)
echo "Starting backup..."
sleep 5 & # Run sleep in background (replace with your task)
spin $! # Pass background PID to spinner
How it works:
- The
spinfunction takes a process ID ($pid) and runs a loop with spinning characters (|/-\). echo -ne "\r"overwrites the current line to animate the spinner.
7. Advanced Techniques
Signal Handling (e.g., Ctrl+C)
Use trap to handle user interrupts (e.g., Ctrl+C) and clean up resources:
#!/bin/bash
# cleanup_on_exit.sh
cleanup() {
echo -e "\nCleaning up temporary files..."
rm -f /tmp/tempfile.txt # Example cleanup
exit 0
}
# Trap Ctrl+C (SIGINT) and call cleanup
trap cleanup SIGINT
echo "Running... Press Ctrl+C to exit."
sleep 30 # Simulate long task
GUI-Like Menus with dialog
For terminal-based “GUIs,” use dialog (install with sudo apt install dialog on Debian/Ubuntu):
#!/bin/bash
# dialog_menu.sh
# Display a yes/no prompt
if dialog --yesno "Do you want to proceed?" 10 40; then # 10=height, 40=width
dialog --msgbox "Great! Let's continue." 10 40
else
dialog --msgbox "Aborted." 10 40
exit 0
fi
8. Real-World Example: Interactive Backup Tool
Let’s combine everything into a backup script with:
- A menu to select directories.
- Input validation.
- A progress spinner.
- Confirmation prompts.
#!/bin/bash
# interactive_backup.sh
set -euo pipefail # Exit on error, undefined variable, or pipe failure
# Configuration
BACKUP_DIR="/backups"
SPIN_CHARS="|/-\\"
# Spinner function
spin() {
local pid=$1
while [ -d /proc/$pid ]; do
for ((i=0; i<${#SPIN_CHARS}; i++)); do
echo -ne "\r[${SPIN_CHARS:$i:1}] Backing up..."
sleep 0.1
done
done
echo -ne "\r[✓] Backup completed! \n"
}
# Main menu
echo "=== Interactive Backup Tool ==="
select dir in "Documents" "Pictures" "Music" "Custom Path" "Exit"; do
case $dir in
"Documents") src="$HOME/Documents" ;;
"Pictures") src="$HOME/Pictures" ;;
"Music") src="$HOME/Music" ;;
"Custom Path")
read -p "Enter custom directory: " src
if [ ! -d "$src" ]; then
echo "Error: Directory '$src' does not exist."
continue # Restart menu
fi
;;
"Exit") echo "Goodbye!"; exit 0 ;;
*) echo "Invalid option."; continue ;;
esac
# Confirm backup
read -p "Backup '$src' to '$BACKUP_DIR'? (y/N) " confirm
if [[ $confirm =~ ^[Yy]$ ]]; then
echo "Starting backup..."
rsync -av "$src" "$BACKUP_DIR/" & # Run rsync in background
spin $! # Show spinner while rsync runs
echo "Backup saved to $BACKUP_DIR/$(basename "$src")"
else
echo "Backup canceled."
fi
break # Exit menu after backup
done
9. Testing and Debugging
- Debugging: Use
set -xto print commands as they run:#!/bin/bash -x # Enable debugging echo "Hello, $USER" - Unit Testing: Use
shunit2(a Bash unit testing framework) for automated tests. - Common Pitfalls:
- Forgetting to quote variables (e.g.,
rm "$file"instead ofrm $fileto handle spaces). - Ignoring exit codes (check
$?after commands to ensure success).
- Forgetting to quote variables (e.g.,
10. Best Practices
- Shebang: Start scripts with
#!/bin/bash(not#!/bin/shfor Bash-specific features). - Comments: Explain complex logic (e.g.,
# Validate user input with regex). - Error Handling: Use
set -eto exit on errors, andtrapfor cleanup. - Portability: Avoid Bash-specific features (e.g.,
read -p) if targetingshshells. - User Feedback: Always inform users of progress or errors.
11. References
With these tools and techniques, you can build powerful, user-friendly CLI tools in Bash. Start small, iterate, and don’t forget to test—your users (and future self) will thank you! 🚀