Instantly share code, notes, and snippets.
Last active
September 15, 2023 00:06
-
Save sellmerfud/86f3d705d56db1b0df23c081410b139e to your computer and use it in GitHub Desktop.
Bash function to prompt using a menu
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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