-2

How can I display a selection menu for a user with options I have stored on individual lines of a text file?

For example, my text file (ingestion.txt) looks like this.

SOUP
FTS/CTS
JDBC
NEW

And I want the user to see this

Please select an option by typing in the corresponding number

1) SOUP
2) FTS/CTS
3) JDBC
4) NEW

And then if they didn't type a valid number they would be prompted again.

Josh
  • 718
  • 2
  • 15
  • 38
  • 2
    In bash, I'd use `mapfile` to read the file into an array, then `select` to control the display and input for the menu, with `PS3` variable to hold the prompt. You should be able to search for those terms to find lots of examples. – glenn jackman Jul 31 '18 at 15:40
  • https://stackoverflow.com/questions/30988586/creating-an-array-from-a-text-file-in-bash and https://stackoverflow.com/questions/2033309/how-can-i-write-a-bash-script-to-display-a-menu-accept-user-input-and-display-d – Benjamin W. Jul 31 '18 at 15:43
  • @glennjackman Thanks for the links. I understand how to make a basic options menu and put the text file into an array, and obviously that's easily searchable, but what I don't understand is how to handle the `case` part of the menu, as this text file will be maintained outside of this script so I'll never know exactly how many options there are. I'm wanting to just set a variable equal to the string of whatever option is chosen unless 'New' is selected, in which case I'll do something else. – Josh Jul 31 '18 at 16:41
  • Why don't you make an attempt, show the code you've developed, then ask specific questions about where you're stuck? – glenn jackman Jul 31 '18 at 16:58

1 Answers1

1
#!/bin/bash

unset option menu ERROR      # prevent inheriting values from the shell
declare -a menu              # create an array called $menu
menu[0]=""                   # set and ignore index zero so we can count from 1

# read menu file line-by-line, save as $line
while IFS= read -r line; do
  menu[${#menu[@]}]="$line"  # push $line onto $menu[]
done < ingestion.txt

# function to show the menu
menu() {
  echo "Please select an option by typing in the corresponding number"
  echo ""
  for (( i=1; i<${#menu[@]}; i++ )); do
    echo "$i) ${menu[$i]}"
  done
  echo ""
}

# initial menu
menu
read option

# loop until given a number with an associated menu item
while ! [ "$option" -gt 0 ] 2>/dev/null || [ -z "${menu[$option]}" ]; do
  echo "No such option '$option'" >&2  # output this to standard error
  menu
  read option
done

echo "You said '$option' which is '${menu[$option]}'"

This reads through ingestion.txt line by line, then pushes the contents of that line into the $menu array. ${#variable} gives you the length of $variable. When given the entirety of an array ("${array[@]}" is akin to "$@"), such as ${#array[@]}, you get the number of elements in that array. Because bash is zero-indexed, that number is the first available item you can add to, starting with zero given the newly defined (and therefore empty) array.

The menu() function iterates through the menu items and displays them on the screen.

We then loop until we get a valid input. Since bash will interpret a non-number as zero, we first determine if it is actually a (natural) number, discarding any error that would come from non-numbers, then we actually ensure the index exists. If it's not the first iteration, we complain about the invalid input given before. Display the menu, then read user input into $option.

Upon a valid option index, the loop stops and the code then gives you back the index and its corresponding value.

Adam Katz
  • 14,455
  • 5
  • 68
  • 83
  • The `while read` loop is slightly painful; modern Bash has `readarray` (but if you are stuck on Bash v3, you don't have that). – tripleee Nov 05 '22 at 12:06
  • But more importantly, Bash has a built-in `select` command for displaying a menu and letting the user select an option. – tripleee Nov 05 '22 at 12:07
  • @tripleee – Can you tell I'm a POSIX shell coder? With a few modifications, this could easily be adapted to work with /bin/sh without bashisms. In fact, I'm surprised I didn't just do it with `$@` in a function to begin with. – Adam Katz Nov 07 '22 at 15:21