Skip to content

Instantly share code, notes, and snippets.

@dekarrin
Last active July 27, 2020 20:37
Show Gist options
  • Save dekarrin/3e213e7bf915bbbd75c0 to your computer and use it in GitHub Desktop.
Save dekarrin/3e213e7bf915bbbd75c0 to your computer and use it in GitHub Desktop.
Various prompting tasks in bash
# This file is not intended to be executed.
# Source this script from other scripts.
# replacement for realpath on posix systems that lack it
findrealpath() {
[ "$#" = 1 ] || { echo "usage: findpath path" >&2 ; return 1 ; }
[ ! "$1" = "/" ] || { echo "/" ; return 0 ; }
canonpath="$(cd "$(dirname "$1")" && pwd -P)" || return 1
separator="/"
[ ! "$canonpath" = "/" ] || separator=
base="$(basename "$1")"
if [ "$base" = "." ]
then
base=
separator=
fi
path="$canonpath$separator$base"
if [ -L "$path" ]
then
# if it IS a real path to a symlink, resolve it via recursive call
path="$(readlink "$path")" || return 1
if [ -d "$path" ]
then
$(cd "$path") || return 1
findrealpath "$path" || return 1
else
echo "$path"
fi
else
# if not a path to a real symlink, dont do anything further
echo "$path"
fi
}
_PROMPT_FUNCTS_TRIM=
_prompt_preproc ()
{
local i
i="$1"
if [ -n "$_PROMPT_FUNCTS_TRIM" ]
then
i="$(echo "$i" | sed 's/^ *\| *$//g')"
fi
echo "$i"
}
# Makes it so all prompt functions trim input before reading it.
# $1 - If 0 (or none), turns off prompt trimming. If 1 (or anything else), turns
# it on.
set_prompt_trim ()
{
if [ -z "$1" -o "$1" = 0 ]
then
_PROMPT_FUNCTS_TRIM=
else
_PROMPT_FUNCTS_TRIM=1
fi
}
# Prompts, and if no input, returns a default.
# $1 - Prompt to show user
# $2 - What to return if user enters no input
prompt_default ()
{
local inp
read -p "$1 (default '$2'): " inp
inp="$(_prompt_preproc "$inp")"
if [ -z "$inp" ]
then
inp="$2"
fi
echo "$inp"
}
# Prompts until user enters integer.
# $1 - Prompt to show user
prompt_num ()
{
local inp
while true
do
read -p "$1 (integer): " inp
inp="$(_prompt_preproc "$inp")"
case "$inp" in
''|*[!0-9]*) ;;
*) break ;;
esac
done
echo "$inp"
}
# Prompts user for integer. Returns default value if invalid value is entered.
# $1 - Prompt to show user
# $2 - What to return if user enters no input
prompt_num_default ()
{
local inp
read -p "$1 (default '$2'): " inp
inp="$(_prompt_preproc "$inp")"
case "$inp" in
''|*[!0-9]*) inp="$2";;
*) ;;
esac
echo "$inp"
}
# Prompts until user enters integer in given range.
# $1 - Prompt to show user
# $2 - Start of range (inclusive)
# $3 - End of range (inclusive)
prompt_range ()
{
local range_start
local range_end
local inp
range_start="$2"
range_end="$3"
if [ $range_start -gt $range_end ]
then
local t
t=$range_start
range_start=$range_end
range_end=$t
fi
while true
do
inp=$(prompt_num "$1 (${range_start}-${range_end})")
if [ $inp -ge $range_start -a $inp -le $range_end ]
then
break
fi
done
echo "$inp"
}
# Prompts for integer in range. Returns default if user enters an invalid value.
# $1 - Prompt to show user
# $2 - Start of range (inclusive)
# $3 - End of range (inclusive)
# $4 - Default value
prompt_range_default ()
{
local range_start
local range_end
local inp
range_start="$2"
range_end="$3"
if [ $range_start -gt $range_end ]
then
local t
t=$range_start
range_start=$range_end
range_end=$t
fi
inp=$(prompt_num_default "$1 (${range_start}-${range_end})" "$4")
if [ $inp -lt $range_start -o $inp -gt $range_end ]
then
inp="$4"
fi
echo "$inp"
}
# Prompts for confirmation. Returns 1 if yes and nothing if no.
# $1 - Prompt to show user
prompt_confirm ()
{
local inp
while true
do
read -p "$1 (y/n): " inp
inp="$(_prompt_preproc "$inp")"
case $inp in
[Yy]*)
echo 1
break
;;
[Nn]*)
break
;;
*)
;;
esac
done
}
# Prompts for an item from a menu. The menu is paginated, with 10 menu items
# per page. The user is prompted until an item is selected.
# $1 - Prompt to show user. This will be at the top of the selection menu.
# $2-... - The options that the user may select from. This will also be return
# value of this function
prompt_select()
{
local items_per_page=10
local prompt
local -a options
local -a page_options
local inp
local page=0
# load args
prompt="$1"
shift
while (( "$#" ))
do
options=("${options[@]}" "$1")
shift
done
local print_linebreak=0
while true
do
# get the options currently being displayed:
local -a prev_opt=()
local -a next_opt=()
local item_count=$items_per_page
local prior_items=$(( page * (items_per_page - 2) ))
if [[ "$prior_items" -gt 0 ]]; then (( prior_items++ )); fi
local remaining_items=$(( ${#options[@]} - prior_items ))
if [[ "$remaining_items" -gt "$items_per_page" ]]
then
next_opt=("(next page)")
(( item_count-- ))
fi
if [[ "$page" -gt 0 ]]
then
prev_opt=("(previous page)")
(( item_count-- ))
fi
page_options=("${prev_opt[@]}" "${options[@]:$prior_items:$item_count}" "${next_opt[@]}")
# print the prompt
local opt_num=0
local opt
local menu_prompt="$prompt"
if [[ $print_linebreak = 1 ]]
then
menu_prompt="
$menu_prompt"
fi
for opt in "${page_options[@]}"
do
menu_prompt="$menu_prompt
$opt_num) $opt"
(( opt_num++ ))
done
menu_prompt="$menu_prompt
"
# now get the input
local max_opt=$(( ${#page_options[@]} - 1 ))
inp=$(prompt_range "${menu_prompt}select" 0 $max_opt)
print_linebreak=1
if [[ ${#prev_opt[@]} != 0 && $inp = 0 ]]
then
(( page-- ))
elif [[ ${#next_opt[@]} != 0 && $inp = $max_opt ]]
then
(( page++ ))
else
echo "${page_options[$inp]}"
break
fi
done
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment