0

I want to implement a selection menu in bash (version 3.2 on macOS) which would output something like this:

Select a fruit:
  0  Nothing
  1  A banana
  2  An orange

When the user will choose an item, I want to be able to run an associated bash script. For this, I would need something like a 2-dimension array but if I am not wrong it does not exist in bash. I thought about using several arrays in a main array. Then I would use a loop to display the menu to the user:

#!/usr/bin/env bash

item_nothing=("0" "Nothing" "") item_banana=("1" "A banana" "select_banana.sh") item_orange=("2" "An orange" "select_orange.sh") items=( "item_nothing" "item_banana" "item_orange" )

printf "Select a fruit:\n" for item in "${items[@]}"; do printf "$($$item[0])" printf "$($$item[1])\n" # $($$item[2]) done

When I run my script I get the following output:

Select a fruit:
./fruit_selection.sh: line 14: $item_nothing[0]: command not found
./fruit_selection.sh: line 15: $item_nothing[1]: command not found

./fruit_selection.sh: line 14: $item_banana[0]: command not found ./fruit_selection.sh: line 15: $item_banana[1]: command not found

./fruit_selection.sh: line 14: $item_orange[0]: command not found ./fruit_selection.sh: line 15: $item_orange[1]: command not found

I have no idea what I am doing wrong. Would you have a solution to achieve what I describded? Also, if you have a better way than what I started to do, do not hesitate to suggest it.


EDIT: solution below in the for loop

#!/usr/bin/env bash

item_nothing=("0" "Nothing" "") item_banana=("1" "A banana" "select_banana.sh") item_orange=("2" "An orange" "select_orange.sh") items=( "item_nothing" "item_banana" "item_orange" )

printf "Select a fruit:\n" for item in "${items[@]}"; do var_0=$item[0] var_1=$item[1] printf " ${!var_0} " printf "${!var_1}\n"

# var_3=$item[3]
# do something with this later "${!var_3}\n"

done

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
Mat.R
  • 103
  • Even bash 3.0 should have the select statement I think? this looks like a canonical use-case for that – steeldriver Jul 05 '20 at 14:49
  • @Inian, it looks like by using your link, I modified my code to: var=$item[1]; printf "${!var}" I get exactly what I want: the second element of my sub-array. However, may I ask you how to use a single statement instead of using a variable before the printf? I tried printf "${!$($item[1])}" but I got this error: ./fruit_selection.sh: line 20: ${!$($item[1])}: bad substitution. – Mat.R Jul 05 '20 at 18:47
  • Given items=(Nothing 'A banana' 'An orange') then select fruit in "${items[@]}"; do case $fruit in "Nothing") break;; *) echo "You selected: $fruit";; esac; done - fill in the case...esac as you wish – steeldriver Jul 05 '20 at 19:08
  • @steeldriver My problem is more about getting a script/command associated to a choice. This is the reason I think I need a 2-d array because I need to store 2 (or more) different elements (e.g. name, command) in one row. I do not have problems with 1-d array. – Mat.R Jul 05 '20 at 20:27
  • 1
    You can associate the choices with the commands using the case statement - sorry if that wasn't clear – steeldriver Jul 05 '20 at 22:03

1 Answers1

0

I would make an array for each piece of information you have, instead of each item. Columns, not rows, if you will. Also, if you're happy with numbering them from zero up, there's no need to explicitly store the indexes.

This would present the use with some fruit names from an array, and work based on shorter codes or command names from other arrays.

# names and values, the order must be the same in every array 
item_names=(Nothing "A banana" "An orange")             # displayed to user
item_codes=(NIL BAN ORA)                       # for internal use
item_funcs=('' do_banana '')                   # names of functions called

special function for bananas

do_banana() { echo "Banana function called" }

present choices and read entry

for (( i=0 ; i < ${#item_names[@]} ; i++ )) { printf "%2d %s\n" "$i" "${item_names[i]}" } read num

XXX: verify that 'num' contains a number and is in the range...

printf "You chose the fruit with code '%s'\n" "${item_codes[num]}"

if there's a function connected to this fruit, call it

if [ "${item_funcs[num]}" ]; then "${item_funcs[num]}"
fi

or do something based on the chosen value

case "${item_codes[num]}" in NIL) echo "do something for nothing" ;; BAN) echo "do something for banana" ;; ORA) echo "do something for orange" ;; esac

If you enter 1, it'll print that you chose BAN and run the appropriate branch of the case statement, and call the function for bananas. You'll want to make sure num only contains a number and to check that it's within the range of existing items.

Note that it's not simple to store full commands with arguments like this, since to store them properly, you'd need an array for each command. If you need that, it may be best to only use the case statement. See: How can we run a command stored in a variable?

ilkkachu
  • 138,973
  • And what should I do to use an associated command to an item code? This is the goal of what I want to achieve. I want to display the user a choice and according to its choice I want to execute a script/command linked to this choice. – Mat.R Jul 05 '20 at 18:23
  • @Mat.R, either use a case statement (or if chain) on the chosen value, or just store command names in another array. See edit. – ilkkachu Jul 06 '20 at 07:54