4

I'm new in Bash and I'm stuck with writing a prompt function which asks the user a given questions and accepts the answers yes, no and cancel. I know that there are already a lot of answers to similar questions here on SO but I wasn't able to find an answer that fulfills all my requirements.

Requirements:

The ask function must

  • accept a question as parameter: ask "are you happy?"
  • accept a answer in [y/n/c]: y, Y, yes, yEs, etc.
  • return a value based on the answer: true for yes, false for no
    • the return value must be assignable to a variable to save it for later: answer=$(ask "...?")
    • the return value must be directly useable by if statements: if ask "...?" then;
  • be able to halt the complete execution of the calling script for abort: exit
  • ask again if the answer was not yes, no or cancel
  • allow a default answer for empty input
  • work in scripts that use set -e as well as set +e

I came up with the following solution which doesn't work properly:

ask() {
  while true; do
    read -p "$1 [Y/n/a] " answer
    case $(echo "$answer" | tr "[A-Z]" "[a-z]") in
      y|yes|"" ) echo "true" ; return 0;;
      n|no     ) echo "false"; return 1;;
      a|abort  ) echo "abort"; exit 1;;
    esac
  done
}

# usage1
if ask "Are you happy?" >/dev/null; then
  # ...
fi

# usage2
answer=$(ask "Are you happy?")

For example, the abort does only work when I use -e but then logically the no also causes the script to halt.

10
  • 1
    You could kill yourself.... I mean kill -0. Or just kill your parent... omg. Anyway, $(echo "$answer" | tr "[A-Z]" "[a-z]") it's simpler as ${answer,,}
    – KamilCuk
    Commented Dec 9, 2020 at 8:59
  • @KamilCuk but a script with set -e would still halt when no is answered, right? What exactly does ${answer,,} do?
    – winklerrr
    Commented Dec 9, 2020 at 9:01
  • Only a hint: instead of case $(echo "$answer" | tr "[A-Z]" "[a-z]") in you can use case "${answer,,}" in.
    – Wiimm
    Commented Dec 9, 2020 at 9:03
  • 1
    Or online at bash manual shell parameter expansion :D
    – KamilCuk
    Commented Dec 9, 2020 at 9:04
  • 2
    how do I handle exit status? answer=$(seomthing) || ret=$?; if ((ret == 0)); then :; elif ((ret == 1)); then ..... Can I handle the exit status in the ask function? Well, there is no function exit status inside the function, it's after the function returns. Because I don't want the handling to happen in the calling code Them if you want exit 1 to terminate it all, you can kill your whole process group with kill -0 or you can't run in a subshell. $(..) starts a subshell.
    – KamilCuk
    Commented Dec 9, 2020 at 9:09

1 Answer 1

5

I believe it would be just overall simpler to work the same way as read works. Remember to pick a unique name for the namereference.

ask() {
  declare -n _ask_var=$2
  local _ask_answer
  while true; do
    read -p "$1 [Y/n/a] " _ask_answer
    case "${_ask_answer,,}" in
      y|yes|"" ) _ask_var="true" ; break; ;;
      n|no     ) _ask_var="false"; break; ;;
      a|abort  ) exit 1; ;;
    esac
  done
}

ask "Are you happy?" answer
if "$answer"; then echo "Yay! Me too!"; fi
14
  • 1
    Why do you use declare for _ask_var but local for _ask_answer? Couldn't you use local for both?
    – winklerrr
    Commented Dec 9, 2020 at 9:17
  • 1
    Does local take -n option? Och it can, didn't knew. I believe just a convention - am used to writing declare -n when doing a backreference, and using local specifically only for function local variables.
    – KamilCuk
    Commented Dec 9, 2020 at 9:18
  • 1
    No, but declare also doesn't?! ss64.com/bash/declare.html
    – winklerrr
    Commented Dec 9, 2020 at 9:19
  • 1
    Please help me to understand one more thing about your code: so I don't need to use return 0 or return 1 because Bash automatically can interpolate the string "true" and "false" or why does if "$answer"; work?
    – winklerrr
    Commented Dec 9, 2020 at 9:23
  • 1
    true and false are commands. true and similar false. true command exits with zero exit status, false with nonzero. if "$answer" first expands answer to true or false, then executes the command, then if chooses path depending on exit status. In normal shells, true and false are implemented as builtins for speed. You can run /bin/true.
    – KamilCuk
    Commented Dec 9, 2020 at 9:23

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