bash while/until

bash provides two classes of two loop structure. The 1st of these is the "while" (or until) which is used when then number of iterations is unknown before hand, and the "for" loop which is used when the number of iterations is known or a list of items to process exists.

Like if, while invokes the argument following the "while" keyword as a command. As long as the return status of the command is true, the action body of the loop will execute. "until" performs the same except that the action body is executed as long as the argument command returns false.

The general structure of the while is :


while test-cmd
do
   action-cmd
   ...
done

or

while test-cmd; do action-cmd; done

where :

"while" (or until) initializes the loop.

"test-cmd" is the command to run and test for success. Like if, this may be a compound sequence of commands possibly containing pipes and redirection. The "while test-cmd" should be on its own line or followed by the semi-colon.

"do" marks the beginning of the action block.

action-cmd is the command to run if the while detects a true status for the test-cmd. The action block must contain at least one command or the : (colon) and may contain many commands including cases, ifs, other whiles, or for statements.

"done" terminates the action block.

The test-cmd is always run 1st, so it is possible that the commands in the action block do not run because of the starting condition.

bash (and Bourne) loop structures recognize the "break" and "continue" commands in the action body.

"break" causes the execution of the bash shell script to immediately continue after the "done" keyword. You may use "break" to prematurely exit the loop because of an error condition or another reason not caught by the test-cmd.

"continue", on the other hand, cause the execution of the loop to immediately return back to the "while" statement and re-run the test-command. If you use a "continue", make sure the conditions for breaking out of the loop can still occur.

guess () {
  # generate a random number between 1 and 1000
  rand=$RANDOM
  rand=`expr $rand % 1001`

  # set initial over range
  guess=1001
  count=0

  # while guess not a match
  while test "$guess" != "$rand"
  do
    # Get command to run from user.
    echo -e "Pick a number between 0 and 1000 or q to quit \c"
    read guess

    # test for q to quit.
    if test "$guess" = q
    then
       break

    # if input not a number, go back and get new guess
    elif test -z "`echo $guess | grep '^[0-9]\{1,\}$' 2> /dev/null`"
    then
       echo "No, enter a number or q"
       continue

    # else count try and test guess against answer
    else
      count=`expr $count + 1`

      # give hints
      if test $guess -lt $rand
      then
        echo more

      elif test $guess -gt  $rand
      then
        echo less

      elif test $guess -eq  $rand
      then
        echo "Yes, you took $count tries."

      fi

    fi
  done
}

Remember that bash will not run any commands in the loop body after the executed "continue" statement, so any actions required to modify the status of the the test-cmd following the "while" keyword must occur before the "continue" is acted on.

ans=y

while [ "$ans" != n ]
do
  if [ "$ans" = y ]
  then
    continue
  fi

  echo "continue y or n"
  read ans
done

In the example above, the prompt and read will never be executed because of the position of the "continue"

In general, use the loop's primary test-cmd for the conditions that justify the use of the loop and use conditional breaks and continues to handle other situations that affect the execution of the loop.

When working with embedded loops, the break and continue apply only to the current loop level. To break (or continue) out of multiple layers of embedded loops, use an integer value after the break or continue. Value represents the number of levels to move.

# The following decrements a count by 1 or 2 depending on user choice.
# Inner loop handles user input and -2 adjustment

looper () {
  count=100; again=i

  # While the value to be decremented > 0
  while [ $count -gt 0 ]
  do
    # Get user choice of adjustment and check for valid input
    while [ "$again" != q ]
    do
      echo -e "\n$count\n "
      echo  "Choice : y - yes  n - no r - repeat  q - quit"
      echo -e "Deduct two? y, n, r, or q  :"
      read again

      case "$again" in
        q ) break 2 ;;         # break out of both loops and terminate
        r ) again=i; continue 2 ;;  # go directly back to outer while
        n ) again=i; break ;;  # break out of inner loop only
        y ) again=i;;       # reset input and allow expr after case to run
        * ) echo "oops"; again=i; continue ;; # go directly to inner while
      esac
      count=`expr $count - 2`

    done

    count=`expr $count - 1`
  done
}


The until works the same as while except that the loop body is run as long as the test-cmd returns false.

again="g"

until test "$again" = q
do
  actions
  echo "To quit, enter q"
  read again
done


When the ending condition has to be determined by another command or user selected input, use "while" or "until".


When the iteration is based on known counts or a specific set of values, use the "for" command.