Project 06 — Menu-Driven CLI App¶
Build an interactive script with a main menu, multiple subcommands, a persistent config file, and a --help flag — the kind of tool you actually run every day.
What You Will Build¶
An interactive task manager (or pick your own domain) with:
- A numbered main menu built with select or a while loop
- Subcommands: add, list, done, delete, config
- Persistent data storage in a plain text file
- A config file for user preferences
- Non-interactive mode: app.sh add "Buy milk" (works in scripts too)
- --help with command-specific help: app.sh help add
- Color output using ANSI escape codes
Menu Patterns¶
Pattern 1: select Statement¶
#!/usr/bin/env bash
PS3="Choose an option: "
options=("Add task" "List tasks" "Mark done" "Quit")
select choice in "${options[@]}"; do
case "$REPLY" in
1) cmd_add ;;
2) cmd_list ;;
3) cmd_done ;;
4) break ;;
*) echo "Invalid choice" ;;
esac
done
Pattern 2: Manual while Loop (more control)¶
show_menu() {
echo ""
echo " TASK MANAGER"
echo " ─────────────"
echo " 1. Add task"
echo " 2. List tasks"
echo " 3. Mark done"
echo " 4. Config"
echo " 5. Quit"
echo ""
}
while true; do
show_menu
read -rp " > " choice
case "$choice" in
1) cmd_add ;;
2) cmd_list ;;
3) cmd_done ;;
4) cmd_config ;;
5|q|quit|exit) echo "Bye."; break ;;
*) echo "Unknown option: $choice" ;;
esac
done
Persistent Storage¶
Store tasks in a plain text file — one task per line:
DATA_FILE="${XDG_DATA_HOME:-$HOME/.local/share}/taskman/tasks.tsv"
mkdir -p "$(dirname "$DATA_FILE")"
cmd_add() {
read -rp "Task description: " desc
[[ -n "$desc" ]] || { echo "Empty task ignored"; return; }
local id=$(( $(wc -l < "$DATA_FILE" 2>/dev/null || echo 0) + 1 ))
echo "$id|pending|$(date +%Y-%m-%d)|$desc" >> "$DATA_FILE"
echo "Added: $desc"
}
cmd_list() {
[[ -f "$DATA_FILE" ]] || { echo "No tasks yet."; return; }
echo ""
printf "%-4s %-10s %-12s %s\n" "ID" "STATUS" "DATE" "DESCRIPTION"
printf "%-4s %-10s %-12s %s\n" "──" "──────" "────" "───────────"
while IFS='|' read -r id status date desc; do
printf "%-4s %-10s %-12s %s\n" "$id" "$status" "$date" "$desc"
done < "$DATA_FILE"
echo ""
}
Color Output¶
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
RESET='\033[0m'
echo -e "${GREEN}Task added successfully${RESET}"
echo -e "${RED}Error: task not found${RESET}"
Check terminal support
Always check [[ -t 1 ]] (stdout is a terminal) before using color codes. Disable colors when output is piped or redirected: [[ -t 1 ]] && GREEN='\033[0;32m' || GREEN=''
Config File¶
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/taskman/config"
# Read config
[[ -f "$CONFIG_FILE" ]] && source "$CONFIG_FILE"
# Defaults
COLOR_OUTPUT="${COLOR_OUTPUT:-true}"
DATE_FORMAT="${DATE_FORMAT:-%Y-%m-%d}"
cmd_config() {
echo "Current config: $CONFIG_FILE"
cat "$CONFIG_FILE" 2>/dev/null || echo "(no config file — using defaults)"
}
Stretch Goals¶
- Add priority levels and sort tasks by priority
- Add due dates and highlight overdue tasks in red
- Add export to CSV or JSON
- Add
--format jsonflag for machine-readable output - Write bats tests for
cmd_addandcmd_list
[[05-git-hooks]] | index