5

In a set of if/elif/else/fi statements, I have made 'else' present the user with an error message, but I also want it to take the user back to the question which was asked before the if/else statements so that they can try to answer it again.

How do I take the user back to a previous line of code? Or, if this is not possible, is there another way to do this?

Wildcard
  • 36,499
Kaiylar
  • 627

2 Answers2

8

I think the easiest way would be to wrap the prompting code into a function, and then drop it into an until loop.

Since all you need really is to call the function until it succeeds, you can put the noop command ":" in the until loop.

Something like this:

#!/bin/bash

getgender() {
  read -p "What is the gender of the user? (male/female): " gender
  case "$gender" in
    m|M)
      grouptoaddto="boys"
      return 0
      ;;
    f|F)
      grouptoaddto="girls"
      return 0
      ;;
    *)
      printf %s\\n "Please enter 'M' or 'F'"
      return 1
      ;;
  esac
}

until getgender; do : ; done
sudo usermod -a -G "$grouptoaddto" "$username"

The point here is the function called with until, so it is repeatedly called until it succeeds. The case switch within the function is just an example.


Simpler example, without using a function:

while [ -z "$groupname" ]; do
  read -p "What gender is the user?" answer
  case "$answer" in
    [MmBb]|[Mm]ale|[Bb]oy) groupname="boys" ;;
    [FfGg]|[Ff]emale|[Gg]irl) groupname="girls" ;;
    *) echo "Please choose male/female (or boy/girl)" ;;
  esac
done
sudo usermod -a -G "$groupname" "$username"

In this last example, I'm using the -z switch to the [ (test) command, to continue the loop as long as the "groupname" variable has zero length.

The keynote is the use of while or until.

To translate this last example into human readable pseudocode:

While groupname is empty,

  ask user for gender.

  If he answers with one letter "m" or "B",
    or the word "Male" or "boy",
    set the groupname as "boys".

  If she answers with one letter "F" or "g",
    or the word "female" or "Girl",
    set the groupname as "girls".

  If he/she answers anything else, complain.

(And then repeat, since groupname is still empty.)

Once you have groupname populated,
  add the user to that group.

Yet another example, without the groupname variable:

while true; do
  read -p "What gender is the user?" answer
  case "$answer" in
    [MmBb]|[Mm]ale|[Bb]oy)
      sudo usermod -a -G boys "$username"
      break
      ;;
    [FfGg]|[Ff]emale|[Gg]irl)
      sudo usermod -a -G girls "$username"
      break
      ;;
    *) echo "Please choose male/female (or boy/girl)" ;;
  esac
done
Wildcard
  • 36,499
  • Thanks, but is this something completely different from a if/else statement? It looks complicated. – Kaiylar Mar 09 '16 at 22:13
  • @Kaiylar, not too complicated. It's a case switch which is often clearer than if/elif/elif/elif/else, especially for pattern matching. I'll update with a simpler example. – Wildcard Mar 09 '16 at 22:15
  • Thanks, I've read it. I somewhat understand, but I'm not sure what to do in my situation. I am making a user creation program, and after the user is created, the user is asked whether they are a boy or a girl, for example, and depending on their answer, the new user is placed into the corresponding group using the command 'sudo usermod -a -G <boys/girls> $username'. If they don't answer with either 'B' or 'b' or 'G' or 'g', they'll be presented with an error and taken back to the question. So, which of these methods should I use in this situation? – Kaiylar Mar 09 '16 at 22:41
  • @Kaiylar, I've updated my code examples to exactly match your use case, now that I understand it. The answer to the general question you asked is my first example, but for your exact use case you don't really need a function, so go with the second code example. – Wildcard Mar 11 '16 at 04:57
  • Okay, thank you. I'll use the second one, but I have a couple of questions. Firstly, would it be better to immediately write 'sudo usermod -a -G <boys/girls> "$username"' instead of storing the group name in a variable and putting it into a command at the end (which is what you have done)? Secondly, can you please explain what each part of ' [MmBb]|[Mm]ale|[Bb]oy)' means, including why there are square brackets and what these are, as well as why the letters (like 'Mm') can be next to each other with nothing like '|' in-between them? – Kaiylar Mar 13 '16 at 18:38
  • 1
    You could put the sudo command in right away, but I don't know what you would use for the while loop test in that case. The way I have it written, it runs until the $groupname variable is populated, which is an easy test. I see no particular advantage to putting the sudo command in earlier. – Wildcard Mar 13 '16 at 20:12
  • 1
    [MmBb] etc. are using standard shell globs; the square brackets are for "any one of the enclosed characters." You might want to read some more examples of globs. – Wildcard Mar 13 '16 at 20:12
  • @Kaiylar, if this answer helped, you can accept it by clicking the check mark to the left of the answer. :) – Wildcard Mar 14 '16 at 00:06
2
flag=1
while [ ${flag} -eq 1 ]
do
  read -p "Please answer B or G " bg
  if [ "${bg}" = B ] || [ "${bg}" = b ] 
  then
    flag=0
    groupname=boys
  else 
    if [ "${bg}" = G ] || [ "${bg}" = g ]
    then
      flag=0
      groupname=girls
    fi
  fi
done
sudo usermod -a -G ${groupname} $username

this is the simplest way I can think of, while clearly showing what it happening.

MelBurslan
  • 6,966
  • Thanks for the answer. I was thinking of something like that where you define a variable and change it depending on which statement is true. I'll try this too. – Kaiylar Mar 09 '16 at 22:51
  • You could simplify this with elif. Or just use a case switch; that's what they're for as the code gets awfully redundant without them. And you should quote your variables in the last line. – Wildcard Mar 09 '16 at 23:58
  • Yes I totally agree with you but looking at the comments user provided to the previous answer, I tried to make it as simple and as clear as possible. Hence the very, very basic commands – MelBurslan Mar 10 '16 at 00:24
  • 2
    Granted for simplicity, but quoting variables isn't more complicated, and it's arguably even more important to quote them for beginners, who may be utterly mystified by the errors they will get otherwise. – Wildcard Mar 10 '16 at 01:12
  • Also I feel it's more important to provide good examples of code, and then explain them well, then to provide not-really-good-practice examples of code on the theory that they're easier to read. – Wildcard Mar 10 '16 at 01:13
  • Hi, I'm only a GCSE student, so which do either of you think I should use? I'm considering either MelBurslan's suggestion (which seems more familiar to me) or Wildcard's. I do appreciate the time and effort that both of you have put in, so thanks for helping me. :) – Kaiylar Mar 10 '16 at 22:24
  • It is a personal preference. I usually go with the code that I can understand and modify in small amounts when it is absolutely necessary. But if you are going to run this multiple times every minute, then efficiency and speed should play a part. But if this is something you are going to use once in a blue moon, then simple trumps the elegant. But again this is my opinion. Everyone has an opinion. – MelBurslan Mar 10 '16 at 23:02
  • 1
    it is not merely opinion that failing to quote variables is bad practice, it is fact. You can have your own opinions, but not your own facts. As for Kaiylar's question "which to use?", IMO @Wildcard's examples (both of them) are better and simpler, and easier to understand and to modify in future. – cas Mar 11 '16 at 02:01