1

I know there are programs which make a TUI (like dialog, whiptail) which allow selections in bash. However, I want a simple, nice, completely CLI menu (may not be simple to implement though).

Note:

  • the program should NOT clear the terminal (it should keep current terminal contents intact)
  • the program should change contents of the menu instead of spitting out lines every key input

The answer should be a bash/shell script.

Given an array of strings (4 in this case):

options=(opt1 "option 2" another "option here")

I want to construct a menu with the selected option highlighted (option will be chosen if pressed enter): Example 1

If the user presses arrow up/down, the program should select the previous/next item and "cycle" through options (selection becomes last when arrow up is pressed on first option). This example shows the menu after arrow down is pressed: Example 2

If the user types "opt", the menu selection should only show options starting with "opt" and change back the current selection to the first item: Example 3

Is this even possible? If yes, please tell me how. Also, I am willing to add any description if requested.

6
  • 1
    bash has a build in command "select" which behaves quiet similar to your description, but you have to choose by number. Commented Sep 2, 2022 at 12:02
  • select name in Frank Fred Francis; do echo Hello $name; break; done Commented Sep 2, 2022 at 12:03
  • i think it differs. the select command cannot go through options using arrow keys + not interactive Commented Sep 2, 2022 at 12:06
  • 2
    @sudoer It definitely is interactive, only not allowing the user to choose with the arrow keys. Commented Sep 2, 2022 at 12:10
  • hmm ok. correct me if i am wrong, but there isnt the filter options by input feature Commented Sep 2, 2022 at 12:11

2 Answers 2

1

It turns out there is this tool called fzy, which is a fuzzy finder. The script should be:

options=(opt1 "option 2" another "option here")
echo "Please choose an option:"
printf "%s\n" "${options[@]}" | fzy

This seems to do what I want.

1
  • Hmm, using a fuzzy finder is great, but its not really what you asked for. Take a look here: askubuntu.com/a/1386907/1771279 Commented Feb 28, 2024 at 7:49
1

I know this has already been answered but heres an example without any dependencies

enter image description here

#!/bin/bash

COLOR_BLACK=0
COLOR_RED=1
COLOR_GREEN=2
COLOR_YELLOW=3
COLOR_BLUE=4
COLOR_MAGENTA=5
COLOR_CYAN=6
COLOR_WHITE=7
COLOR_OFF=9
FG_BLACK=$(echo -e "\033[3${COLOR_BLACK}m")
FG_RED=$(echo -e "\033[3${COLOR_RED}m")
FG_GREEN=$(echo -e "\033[3${COLOR_GREEN}m")
FG_YELLOW=$(echo -e "\033[3${COLOR_YELLOW}m")
FG_BLUE=$(echo -e "\033[3${COLOR_BLUE}m")
FG_MAGENTA=$(echo -e "\033[3${COLOR_MAGENTA}m")
FG_CYAN=$(echo -e "\033[3${COLOR_CYAN}m")
FG_WHITE=$(echo -e "\033[3${COLOR_WHITE}m")
FG_OFF=$(echo -e "\033[3${COLOR_OFF}m")
BG_BLACK=$(echo -e "\033[4${COLOR_BLACK}m")
BG_RED=$(echo -e "\033[4${COLOR_RED}m")
BG_GREEN=$(echo -e "\033[4${COLOR_GREEN}m")
BG_YELLOW=$(echo -e "\033[4${COLOR_YELLOW}m")
BG_BLUE=$(echo -e "\033[4${COLOR_BLUE}m")
BG_MAGENTA=$(echo -e "\033[4${COLOR_MAGENTA}m")
BG_CYAN=$(echo -e "\033[4${COLOR_CYAN}m")
BG_WHITE=$(echo -e "\033[4${COLOR_WHITE}m")
BG_OFF=$(echo -e "\033[4${COLOR_OFF}m")
FG_INFO=$(echo -e "${FG_CYAN}")
FG_DANGER=$(echo -e "${FG_RED}")
FG_WARNING=$(echo -e "${FG_YELLOW}")
FG_SUCCESS=$(echo -e "${FG_GREEN}")
BG_INFO=$(echo -e "${BG_CYAN}")
BG_DANGER=$(echo -e "${BG_RED}")
BG_WARNING=$(echo -e "${BG_YELLOW}")
BG_SUCCESS=$(echo -e "${BG_GREEN}")

# Disable word wrapping
echo -e "\033[?7l"

# Hide cursor
echo -e "\033[?25l"

# Define menu colors
MENU_FG_COLOR=${FG_WHITE}
MENU_BG_COLOR=${BG_CYAN}
MENU_HIGHLIGHT_FG_COLOR=${FG_CYAN}
MENU_HIGHLIGHT_BG_COLOR=${BG_BLACK}

# Define menu title colors
MENU_TITLE_FG_COLOR=${FG_BLACK}
MENU_TITLE_BG_COLOR=${BG_GREEN}

# Define menu width
MENU_WIDTH=50

# Define menu title padding
MENU_TITLE_PADDING=4

# Menu title
MENU_TITLE="Use arrow keys to navigate, press Enter to select."
MENU_TITLE_LENGTH=${#MENU_TITLE}
if [[ $MENU_TITLE_LENGTH -gt $MENU_WIDTH ]]; then
    MENU_WIDTH=$MENU_TITLE_LENGTH
fi
MENU_TITLE_LENGTH=$((MENU_TITLE_LENGTH + MENU_TITLE_PADDING * 2))
if [[ $MENU_TITLE_LENGTH -gt $MENU_WIDTH ]]; then
    MENU_WIDTH=$MENU_TITLE_LENGTH
fi

# Menu options
options=("Option 1" "Option 2" "Option 3" "Exit")
selected=0  # Index of the selected menu item

# Function to display the menu
display_menu() {
    clear
    local term_width=$(tput cols)
    local start_col=$(( (term_width - MENU_WIDTH) / 2 ))
    local padding=$((MENU_WIDTH - MENU_TITLE_LENGTH))
    local filler=$(printf '%*s' "$padding" '')
    local title_filler=$(printf '%*s' "$MENU_TITLE_PADDING" '')
    printf "\n\n"  # Add two empty rows above the menu title
    printf "%${start_col}s" ""
    echo -e "${MENU_TITLE_BG_COLOR}${MENU_TITLE_FG_COLOR}${title_filler}${MENU_TITLE}${title_filler}${FG_OFF}${BG_OFF}"
    for i in "${!options[@]}"; do
        local padding=$((MENU_WIDTH - ${#options[$i]} - 4))
        local filler=$(printf '%*s' "$padding" '')
        if [[ $i -eq $selected ]]; then
            printf "%${start_col}s" ""  # Align to center
            echo -e "${MENU_HIGHLIGHT_BG_COLOR}${MENU_HIGHLIGHT_FG_COLOR} > ${options[$i]} $filler ${FG_OFF}${BG_OFF}"
        else
            printf "%${start_col}s" ""  # Align to center
            echo -e "${MENU_BG_COLOR}${MENU_FG_COLOR}   ${options[$i]} $filler ${FG_OFF}${BG_OFF}"
        fi
    done
}

# Capture keypresses
while true; do
    display_menu
    read -rsn1 key  # Read a single key
    if [[ $key == $'\x1b' ]]; then
        read -rsn2 key  # Read the next two characters
        if [[ $key == "[A" ]]; then  # Up arrow
            ((selected--))
            if [[ $selected -lt 0 ]]; then
                selected=$((${#options[@]} - 1))
            fi
        elif [[ $key == "[B" ]]; then  # Down arrow
            ((selected++))
            if [[ $selected -ge ${#options[@]} ]]; then
                selected=0
            fi
        fi
    elif [[ $key == "" ]]; then  # Enter key
        case ${options[$selected]} in
            "Option 1")
                echo "You selected Option 1!"
                ;;
            "Option 2")
                echo "You selected Option 2!"
                ;;
            "Option 3")
                echo "You selected Option 3!"
                ;;
            "Exit")
                echo "Exiting..."
                echo -e "\033[?7h"  # Re-enable word wrapping
                echo -e "\033[?25h"  # Show cursor
                exit 0
                ;;
        esac
        read -p "Press any key to continue..." -n1
    fi

done

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.