Skip to content

Instantly share code, notes, and snippets.

@sellmerfud
Last active September 15, 2023 00:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sellmerfud/86f3d705d56db1b0df23c081410b139e to your computer and use it in GitHub Desktop.
Save sellmerfud/86f3d705d56db1b0df23c081410b139e to your computer and use it in GitHub Desktop.
Bash function to prompt using a menu
#! /usr/bin/env bash
# Present a menu and prompt user to select one of the given options.
#
# usage:
# prompt_menu "<choices>" "<cols>:<width>" prompt="Choose one: " delim=":"
# $1 - choices
# contains the values to be seleted separated by newlines
# Optionally, each value can be followed by a User friendly label that will
# be displayed for that value. The label if present must be appened to the value
# following a delimiter. The default delimiter is colon ':'
# $2 - columns and width
# By default the menu options are show in a single column.
# For large menus is it sometimes better to display the choices in
# more than one column.
# This argument may consist of one or two integers separated by a colon ':'
# The first (and possible only) integer is the number of columns desired.
# The second integer is the amount of screen width that the entire menu
# will use. The colums widths will be evenly divided among this screen
# width. If the <width> column is not given, then the scren width
# as reported by `tput cols` is used and if that fails then a value
# of 80 is used as a last resort.
# Normally, you will want to supply the width so that the menu does
# not look spread out on wide monitors.
# $3 - prompt
# The prompt string show to the user. If not given a default prompt
# of "Choose one: " is used.
# $4 - delim
# delimiter used to separate values from their associated labels.
# This must be a single character.
# If not given, a default delimiter of ':' is used.
#
# return
# The selected value is written to stdout
# Use command substitution to save the return value
#
# example:
# choices='
# international: YYYY-MM-DD (ISO)
# us : MM-DD-YYYY (United States)
# europe : DD-MM-YYYY (Europe)
# '
# date_format=`promptMenu "$choices"
#
# # With two column layout:
# date_format=`promptMenu "$choices" "2:80"
#
# # With a custom prompt:
# date_format=`promptMenu "$choices" "1" "Select date format:"`
promptMenu() {
local choices="$1"
local prompt="${3:-"Choose one:"}"
local delim="${4:-":"}"
local values=() labels=()
local choice=-1 max_label=0 separator=""
local response rest idx num_width num_choices
local default_width num_cols menu_width
if [[ -z "$choices" ]]; then
printf "promptMenu: first argument (choices) is required\n" >&2
return 1
fi
trim() {
local var="$1"
# remove leading whitespace characters
var="${var#"${var%%[![:space:]]*}"}"
# remove trailing whitespace characters
var="${var%"${var##*[![:space:]]}"}"
printf '%s' "$var"
}
default_width="$(tput cols)" || default_width=80
# Set up columns
if [[ -z "$2" ]]; then
num_cols="1"
menu_width="$default_width"
else
# Putting IFS=':' in the same line before set was not working!
local save_IFS="$IFS"
IFS=':'
# Do not quote $2 here!
set $2
IFS="$save_IFS"
num_cols="${1}"
menu_width="${2:-$default_width}"
fi
while IFS="$delim" read -r value label; do
value="$(trim "$value")"
label="$(trim "$label")"
[[ -z "$value" ]] && continue
label=${label:-"$value"}
values+=("$value")
labels+=("$label")
done <<< "$choices"
for idx in "${labels[@]}"; do
(( max_label="${#idx} > max_label ? ${#idx} : max_label" ))
done
num_choices="${#labels[@]}"
num_width="${#num_choices}"
# Acount for max label plus number plus ") "
for (( idx=0; idx < max_label + num_width + 2; ++idx )); do
separator="$separator-"
done
while [[ $choice == -1 ]]; do
# Print the menu entires using pr to handle
# the column layout.
# Use pr to handle the display of columns
for idx in "${!labels[@]}"; do
printf "%${num_width}d) %s\n" $((idx + 1)) "${labels[$idx]}"
done | pr -t "-$num_cols" "-w$menu_width"
printf -- "%s\n%s " "$separator" "$prompt"
read -r response rest
(( choice="response - 1" ))
if [[ $choice -lt 0 || $choice -ge ${num_choices} ]]; then
printf "'%s' is not a valid choice\n" "$response"
choice=-1
fi
done >&2
unset trim
echo "${values[$choice]}"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment