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,,} 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? 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. Commented Dec 9, 2020 at 9:03
  • 1
    Or online at bash manual shell parameter expansion :D 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. Commented Dec 9, 2020 at 9:09

1 Answer 1

6

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 
Sign up to request clarification or add additional context in comments.

14 Comments

Why do you use declare for _ask_var but local for _ask_answer? Couldn't you use local for both?
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.
No, but declare also doesn't?! ss64.com/bash/declare.html
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?
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.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.