#!/usr/bin/env bash # Renders a text based list of options that can be selected by the # user using up, down and enter keys and returns the chosen option. # # Arguments : list of options, maximum of 256 # "opt1" "opt2" ... # Return value: selected index (0 for opt1, 1 for opt2 ...) function select_option { local nformat local optcount=$# printf -v nformat "%%%dd. " ${#optcount} local max_opt=0 for opt; do if [[ "${#opt}" -gt $max_opt ]]; then max_opt=${#opt} fi done # little helpers for terminal print control and key input ESC=$( printf "\033") cursor_blink_on() { printf "$ESC[?25h"; } cursor_blink_off() { printf "$ESC[?25l"; } cursor_to() { printf "$ESC[$1;${2:-1}H"; } if [[ $SELECT_MONOCHROME -eq 1 ]]; then print_option() { printf " $1 "; } print_selected() { printf " [$ESC[1m$1$ESC[0m] "; } else print_option() { printf " $ESC[33m $1 $ESC[0m "; } print_selected() { printf " [$ESC[37;1m$1$ESC[0m] "; } fi get_cursor_row() { IFS=';' read -sdR -p $'\E[6n' ROW COL; echo ${ROW#*[}; } key_input() { read -s -n1 key1 2>/dev/null >&2 read -s -n2 -t 0.1 key2 2>/dev/null >&2 if [[ "$key1" =~ ^[0-9]+$ ]]; then echo $key1; return; fi if [[ $key1$key2 = $ESC[A ]]; then echo up; return; fi if [[ $key1$key2 = $ESC[B ]]; then echo down; return; fi if [[ $key1 = $'\e' ]]; then echo esc; return; fi if [[ $key1 = "" ]]; then echo enter; return; fi } reset_display() { cursor_blink_on; stty echo; printf '\n'; } # initially print empty new lines (scroll down if at bottom of screen) for opt; do printf "\n"; done # determine current screen position for overwriting the options local lastrow=`get_cursor_row` local startrow=$(($lastrow - $#)) # ensure cursor and input echoing back on upon a ctrl+c during read -s trap "cursor_blink_on; stty echo; printf '\n'; exit 1" 2 cursor_blink_off local selected=0 while true; do # print options by overwriting the last lines local idx=0 local nidx for opt; do if [[ $SELECT_UNDERSCORES -eq 1 ]]; then opt=${opt//_/ } fi if [[ $SELECT_NUMBERS -eq 1 ]]; then printf -v nidx "$nformat" $(( idx + 1 )) fi printf -v padopt "%-${max_opt}s" "$opt" cursor_to $(($startrow + $idx)) if [ $idx -eq $selected ]; then print_selected "$nidx$padopt" else print_option "$nidx$padopt" fi ((idx++)) done # user key control local user_input=`key_input` case "$user_input" in enter) break;; esc) reset_display; exit 1;; up) ((selected--)); if [ $selected -lt 0 ]; then selected=$(($# - 1)); fi;; down) ((selected++)); if [ $selected -ge $# ]; then selected=0; fi;; [0-9]) selected=$(( $user_input - 1 )) ;; esac done # cursor position back to normal cursor_to $lastrow reset_display if [[ -n "$SELECT_TOFILE" ]]; then local idx=0 for opt; do if [ $idx -eq $selected ]; then echo "$opt" > "$SELECT_TOFILE" fi ((idx++)) done fi return $(( 10 + $selected )) } [[ $_ == $0 ]] && { _help() { SELF=$( basename "$0" ) echo "Select Option Usage: $SELF [-n] [--nc] [-o File] [-_] list of options -n Number list --nc No colors -o File Save the choice in a file -_ Print spaces instead of underscores Returns (10 + index) of selection (first option = 10) 1 if ctrl-c or ESC pressed Example: $SELF -n one two three EC=\$? [[ \$EC -lt 10 ]] && { echo Exited..; } || { echo You selected \$(( \$EC - 9 )); } " exit } SELECT_MONOCHROME=0 SELECT_NUMBERS=0 SELECT_UNDERSCORES=0 SELECT_TOFILE="" for (( i=1; i<=$#; i++ )); do value=${!i} j=$(( i + 1 )) [[ "$value" = "-h" ]] && _help [[ "$value" = "--help" ]] && _help [[ "$value" = "--nc" ]] && { SELECT_MONOCHROME=1; continue; } [[ "$value" = "-n" ]] && { SELECT_NUMBERS=1; continue; } [[ "$value" = "-_" ]] && { SELECT_UNDERSCORES=1; continue; } [[ "$value" = "-o" ]] && { SELECT_TOFILE="${!j}"; ((i++)); continue; } opts+=( "$value" ) done select_option "${opts[@]}" exit $? }