4

So, I'm trying to create a menu system in bash as a learning experience. I'm sure there are countless ways, and even "better" ways to do this, but what I've got is something like this...

echo "
2nd Menu
********
1) command
2) command
M) Main menu
X) Exit program
"
read -p "Choose option >" opt
if [ "$opt" -eq 1 ]; then
    commands
elif [ "$opt" -eq 2 ]; then
    commands
elif [[ "$opt" = [m,M] ]]; then
    main #calls the function main()
elif [[ "$opt" = [x,X] ]]; then
    exit
else
    echo "Invalid option"
    main
fi

The script works for every option except the "X) Exit program". When I run "X" or "x" I get this error...

./acct-mgr.sh: line 10: [: x: integer expression expected
./acct-mgr.sh: line 12: [: x: integer expression expected
Invalid option

This is baffling me! I'm pretty sure I'm using the correct comparison operator for each data type (-eq for integers and = for strings), coupled with the fact that EVERY option works EXCEPT that "x".

Any help is greatly appreciated. Thanks.

P.S. - Alternative ways of accomplishing the desired result is greatly appreciated, but even then I would like to know why this isn't working for my edification. Thanks again.

10

When you enter M/m or X/x, you're comparing a non-number to a number using -eq, and that is causing the failure. If you want $opt to be a string sometimes, treat it as a string all of the time:

...
if [ "$opt" = "1" ]; then
    commands
elif [ "$opt" = "2" ]; then
    commands
...
  • That totally makes sense! Thanks, Andy! I still don't understand why M/m worked and X/x failed with the error though? Is it just a quirk of the system? – dlowrie290 Oct 7 '17 at 3:14
  • I don't see how M/m would have worked, unless at some point you checked those cases before 1/2. – Andy Dalton Oct 7 '17 at 3:21
  • That must be what's happening. Regardless, your solution worked perfectly! Cheers :) – dlowrie290 Oct 7 '17 at 3:22
6

As an addition to what @Andy already said, note that

[[ "$opt" = [m,M] ]]

matches on m, , and M. If you wanted to match on m and M only, that would be

[[ "$opt" = [mM] ]]

Here, I'd use a standard case construct though:

case $opt in
  (1)   commands;;
  (2)   commands;;
  (m|M) main;;
  (x|X) exit;;
  (*)   echo >&2 "Invalid option"; main
esac

Also note that the behaviour of read depends on the current value of $IFS. If you wanted to read one line from the user, you'd use:

IFS= read -r opt

To read one character:

IFS= read -rn1 opt

To read one word (read one line, but ignore the leading space and tabs and anything but the first word):

IFS=$' \t' read -r opt ignored

Or to allow the use to include space or tabs in the word by prefixing with backslash:

IFS=$' \t' read opt ignored
  • Great information, Stephane! Thanks for the insight and contribution to this thread! – dlowrie290 Oct 7 '17 at 13:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.