3.8. Loops and Branches

Operations on code blocks are the key to structured, organized shell scripts. Looping and branching constructs provide the tools for accomplishing this.

3.8.1. Loops

A loop is a block of code that iterates (repeats) a list of commands as long as the loop control condition is true.

for (in)

This is the basic looping construct. It differs significantly from its C counterpart.

for arg in [list]
do
 command...
done

Note

During each pass through the loop, arg takes on the value of each variable in the list.

   1 for arg in "$var1" "$var2" "$var3" ... "$varN"  
   2 # In pass 1 of the loop, $arg = $var1	    
   3 # In pass 2 of the loop, $arg = $var2	    
   4 # In pass 3 of the loop, $arg = $var3	    
   5 # ...
   6 # In pass N of the loop, $arg = $varN
   7 
   8 # Arguments in [list] quoted to prevent possible word splitting.

The argument list may contain wild cards.

If do is on same line as for, there needs to be a semicolon after list.

for arg in [list] ; do


Example 3-43. Simple for loops

   1 #!/bin/bash
   2 
   3 for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto
   4 do
   5   echo $planet
   6 done
   7 
   8 echo
   9 
  10 # Entire 'list' enclosed in quotes creates a single variable.
  11 for planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto"
  12 do
  13   echo $planet
  14 done
  15 
  16 exit 0

Note

Each [list] element may contain multiple parameters. This is useful when processing parameters in groups. In such cases, use the set command (see Example 3-75) to force parsing of each [list] element and assignment of each component to the positional parameters.


Example 3-44. for loop with two parameters in each [list] element

   1 #!/bin/bash
   2 # Planets revisited.
   3 
   4 # Want to associate name of each planet with its distance from the sun.
   5 
   6 for planet in "Mercury 36" "Venus 67" "Earth 93"  "Mars 142" "Jupiter 483"
   7 do
   8   set -- $planet  # Parses variable "planet" and sets positional parameters.
   9   # the "--" prevents nasty surprises if $planet is null or begins with a dash.
  10 
  11   # May need to save original positional parameters, since they get overwritten.
  12   # One way of doing this is to use an array,
  13   #        original_params=("$@)
  14 
  15   echo "$1		$2,000,000 miles from the sun"
  16   #-------two  tabs---concatenate zeroes onto parameter $2
  17 done
  18 
  19 # (Thanks, S.C., for additional clarification.)
  20 
  21 exit 0

A variable may supply the [list] in a for loop.


Example 3-45. Fileinfo: operating on a file list contained in a variable

   1 #!/bin/bash
   2 # fileinfo.sh
   3 
   4 FILES="/usr/sbin/privatepw
   5 /usr/sbin/pwck
   6 /usr/sbin/go500gw
   7 /usr/bin/fakefile
   8 /sbin/mkreiserfs
   9 /sbin/ypbind"     # List of files you are curious about.
  10 # Threw in a dummy file, /usr/bin/fakefile.
  11 
  12 echo
  13 
  14 for file in $FILES
  15 do
  16 
  17   if [ ! -e "$file" ]  # Check if file exists.
  18   then
  19     echo "$file does not exist."; echo
  20     continue  # On to next.
  21    fi
  22 
  23   ls -l $file | awk '{ print $9 "         file size: " $5 }'  # Print 2 fields.
  24   whatis `basename $file`   # File info.
  25   echo
  26 done  
  27 
  28 exit 0

The [list] in a for loop may contain filename globbing, that is, using wildcards for filename expansion.


Example 3-46. Operating on files with a for loop

   1 #!/bin/bash
   2 # Generating [list] in a for-loop using "globbing".
   3 
   4 echo
   5 
   6 for file in *
   7 do
   8   ls -l "$file"  # Lists all files in $PWD (current directory).
   9   # Recall that the wild card character "*" matches everything.
  10   # Although, in "globbing", it doesn't match dot-files.
  11 
  12   # If the pattern matches no file, it is expanded to itself.
  13   # To prevent this, set the nullglob option
  14   # (shopt -s nullglob).
  15   # Thanks, S.C.
  16 done
  17 
  18 echo; echo
  19 
  20 for file in [jx]*
  21 do
  22   rm -f $file  # Removes only files beginning with "j" or "x" in $PWD.
  23   echo "Removed file \"$file\"".
  24 done
  25 
  26 echo
  27 
  28 exit 0

Omitting the in [list] part of a for loop causes the loop to operate on $@, the list of arguments given on the command line to the script.


Example 3-47. Missing in [list] in a for loop

   1 #!/bin/bash
   2 
   3 # Invoke both with and without arguments,
   4 # and see what happens.
   5 
   6 for a
   7 do
   8  echo $a
   9 done
  10 
  11 # 'in list' missing, therefore operates on '$@'
  12 # (command-line argument list, including white space)
  13 
  14 exit 0

It is possible to use command substitution to generate the [list] in a for loop. See also Example 3-109, Example 3-51 and Example 3-108.


Example 3-48. Generating the [list] in a for loop with command substitution

   1 #!/bin/bash
   2 # A for-loop with [list] generated by command substitution.
   3 
   4 NUMBERS="9 7 3 8 37.53"
   5 
   6 for number in `echo $NUMBERS`  # for number in $NUMBERS
   7 do
   8   echo -n "$number "
   9 done
  10 
  11 echo 
  12 exit 0

This is a somewhat more complex example of using command substitution to create the [list].


Example 3-49. A grep replacement for binary files

   1 #!/bin/bash
   2 # Locates matching strings in a binary file.
   3 
   4 # A "grep" replacement for binary files.
   5 # Similar effect to "grep -a"
   6 
   7 E_BADARGS=65
   8 NOFILE=66
   9 
  10 if [ $# -ne 2 ]
  11 then
  12   echo "Usage: `basename $0` string filename"
  13   exit $E_BADARGS
  14 fi
  15 
  16 if [ ! -f "$2" ]
  17 then
  18   echo "File \"$2\" does not exist."
  19   exit $NOFILE
  20 fi  
  21 
  22 
  23 for word in $( strings "$2" | grep "$1" )
  24 # The "strings" command lists strings in binary files,
  25 # then piped to "grep", which tests for desired string.
  26 do
  27   echo $word
  28 done
  29 
  30 # As S.C. points out, the above for-loop could be replaced with the simpler
  31 #    strings "$2" | grep "$1" | tr -s "$IFS" '[\n*]'
  32 
  33 
  34 # Try something like  "./bin-grep mem /bin/ls"  to exercise this script.
  35 
  36 exit 0

Here is yet another example of the [list] resulting from command substitution.


Example 3-50. Checking all the binaries in a directory for authorship

   1 #!/bin/bash
   2 # findstring.sh: find a particular string in binaries in a specified directory.
   3 
   4 directory=/usr/bin/
   5 fstring="Free Software Foundation"  # See which files come from the FSF.
   6 
   7 for file in $( find $directory -type f -name '*' | sort )
   8 do
   9   strings -f $file | grep "$fstring" | sed -e "s%$directory%%"
  10   # In the "sed" expression, it is necessary to substitute for the normal "/" delimiter
  11   # because "/" happens to be one of the characters filtered out.
  12   # Failure to do so gives an error message (try it).
  13 done  
  14 
  15 exit 0
  16 
  17 # Exercise for the reader (easy):
  18 # Convert this script to taking command-line parameters for $directory and $fstring.

The output of a for loop may be piped to a command or commands.


Example 3-51. Listing the symbolic links in a directory

   1 #!/bin/bash
   2 # Lists symbolic links in a directory.
   3 
   4 ARG=1  # Expect one command-line argument.
   5 
   6 if [ $# -ne "$ARG" ]  # If not 1 arg...
   7 then
   8   directory=`pwd`      # current working directory.
   9 else
  10   directory=$1
  11 fi
  12 
  13 echo "symbolic links in directory \"$directory\""
  14 
  15 for file in $( find $directory -type l )   # "-type l" = symbolic links
  16 do
  17   echo $file
  18 done | sort   # Otherwise file list is unsorted.
  19 
  20 exit 0

There is an alternative syntax to a for loop that will look very familiar to C programmers. This requires double parentheses.


Example 3-52. A C-like for loop

   1 #!/bin/bash
   2 # Two ways to count up to 10.
   3 
   4 echo
   5 
   6 # Standard syntax.
   7 for a in 1 2 3 4 5 6 7 8 9 10
   8 do
   9   echo -n "$a "
  10 done  
  11 
  12 echo; echo
  13 
  14 # +==========================================+
  15 
  16 # Now, let's do the same, using C-like syntax.
  17 
  18 LIMIT=10
  19 
  20 for ((a=1; a <= LIMIT ; a++))  # Double parentheses, and "LIMIT" with no "$".
  21 do
  22   echo -n "$a "
  23 done  
  24 # A construct borrowed from 'ksh93'.
  25 
  26 echo; echo
  27 
  28 exit 0

See also Example 3-154 and Example 3-155.

---

Now, for an example from "real life".


Example 3-53. Using efax in batch mode

   1 #!/bin/bash
   2 
   3 ARGS=2
   4 E_BADARGS=65
   5 
   6 if [ $# -ne $ARGS ]
   7 # Check for proper no. of command line args.
   8 then
   9    echo "Usage: `basename $0` phone# text-file"
  10    exit $E_BADARGS
  11 fi
  12 
  13 
  14 if [ ! -f "$2" ]
  15 then
  16   echo "File $2 is not a text file"
  17   exit $E_BADARGS
  18 fi
  19   
  20 
  21 # Create fax formatted files from text files.
  22 fax make $2
  23 
  24 for file in $(ls $2.0*)
  25 # Concatenate the converted files.
  26 # Uses wild card in variable list.
  27 do
  28   fil="$fil $file"
  29 done  
  30 
  31 # Do the work.
  32 efax -d /dev/ttyS3 -o1 -t "T$1" $fil
  33 
  34 
  35 # As S.C. points out, the for-loop can be eliminated with
  36 #    efax -d /dev/ttyS3 -o1 -t "T$1" $2.0*
  37 # but it's not as instructive [grin].
  38 
  39 exit 0

while

This construct tests for a condition at the top of a loop, and keeps looping as long as that condition is true (returns a 0 exit status).

while [condition]
do
 command...
done

As is the case with for/in loops, placing the do on the same line as the condition test requires a semicolon.

while [condition] ; do

Note that certain specialized while loops, as, for example, a getopts construct, deviate somewhat from the standard template given here.


Example 3-54. Simple while loop

   1 #!/bin/bash
   2 
   3 var0=0
   4 LIMIT=10
   5 
   6 while [ "$var0" -lt "$LIMIT" ]
   7 do
   8   echo -n "$var0 "
   9   # -n suppresses newline.
  10   var0=`expr $var0 + 1`
  11   # var0=$(($var0+1)) also works.
  12 done
  13 
  14 echo
  15 
  16 exit 0


Example 3-55. Another while loop

   1 #!/bin/bash
   2 
   3 echo
   4 
   5 while [ "$var1" != "end" ]
   6 do
   7   echo "Input variable #1 (end to exit) "
   8   read var1
   9   # It's not 'read $var1' because value of var1 is being set.
  10   echo "variable #1 = $var1"
  11   # Need quotes because of #
  12   echo
  13 done  
  14 
  15 # Note: Echoes 'end' because termination condition tested for at top of loop.
  16 
  17 exit 0

Note

A while loop may have multiple conditions. Only the final condition determines when the loop terminates. This necessitates a slightly different loop syntax, however.


Example 3-56. while loop with multiple conditions

   1 #!/bin/bash
   2 
   3 var1=unset
   4 previous=$var1
   5 
   6 while echo "previous-variable = $previous"
   7       echo
   8       previous=$var1
   9       [ "$var1" != end ] # Keeps track of what "var1" was previously.
  10       # Four conditions on "while", but only last one controls loop.
  11       # The *last* exit status is the one that counts.
  12 do
  13 echo "Input variable #1 (end to exit) "
  14   read var1
  15   echo "variable #1 = $var1"
  16 done  
  17 
  18 # Try to figure out how this all works.
  19 # It's a wee bit tricky.
  20 
  21 exit 0

As with a for loop, a while loop may employ C-like syntax by using the double parentheses construct (see also Example 3-42).


Example 3-57. C-like syntax in a while loop

   1 #!/bin/bash
   2 # Count to 10 in a "while" loop.
   3 
   4 LIMIT=10
   5 a=1
   6 
   7 while [ "$a" -le $LIMIT ]
   8 do
   9   echo -n "$a "
  10   let "a+=1"
  11 done  # No surprises, so far.
  12 
  13 echo; echo
  14 
  15 # +=================================================================+
  16 
  17 # Now, repeat with C-like syntax.
  18 
  19 ((a = 1)) # a=1
  20 # Double parentheses permit space when setting a variable, as in C.
  21 
  22 while (( a <= LIMIT ))   # Double parentheses, and no "$" preceding variables.
  23 do
  24   echo -n "$a "
  25   ((a += 1))   # let "a+=1"
  26   # Yes, indeed.
  27   # Double parentheses permit incrementing a variable with C-like syntax.
  28 done
  29 
  30 echo
  31 
  32 # Now, C programmers can feel right at home in Bash.
  33 
  34 exit 0

Note

A while loop may have its stdin redirected to a file by a < at its end.

until

This construct tests for a condition at the top of a loop, and keeps looping as long as that condition is false (opposite of while loop).

until [condition-is-true]
do
 command...
done

Note that an until loop tests for the terminating condition at the top of the loop, differing from a similar construct in some programming languages.

As is the case with for/in loops, placing the do on the same line as the condition test requires a semicolon.

until [condition-is-true] ; do


Example 3-58. until loop

   1 #!/bin/bash
   2 
   3 until [ "$var1" = end ]
   4 # Tests condition at top of loop.
   5 do
   6   echo "Input variable #1 "
   7   echo "(end to exit)"
   8   read var1
   9   echo "variable #1 = $var1"
  10 done  
  11 
  12 exit 0

3.8.2. Nested Loops

A nested loop is a loop within a loop, an inner loop within the body of an outer one. What happens is that the first pass of the outer loop triggers the inner loop, which executes to completion. Then the second pass of the outer loop triggers the inner loop again. This repeats until the outer loop finishes. Of course, a break within either the inner or outer loop may interrupt this process.


Example 3-59. Nested Loop

   1 #!/bin/bash
   2 # Nested "for" loops.
   3 
   4 
   5 outer=1  # Set outer loop counter.
   6 
   7 for a in 1 2 3 4 5  # Beginning of outer loop.
   8 do
   9   echo "Pass $outer in outer loop."
  10   echo "---------------------"
  11   inner=1  # Reset inner loop counter.
  12 
  13   # Beginning of inner loop.
  14   for b in 1 2 3 4 5
  15   do
  16     echo "Pass $inner in inner loop."
  17     let "inner+=1"  # Increment inner loop counter.
  18   done
  19   # End of inner loop.
  20 
  21   let "outer+=1"  # Increment outer loop counter. 
  22   echo  # Space between output in pass of outer loop.
  23 done   # End of outer loop.
  24 
  25 exit 0

See Example 3-152 for an illustration of nested "while" loops, and Example 3-153 to see a "while" loop nested inside an "until" loop.

3.8.3. Loop Control Commands

break, continue

The break and continue loop control commands [1] correspond exactly to their counterparts in other programming languages. The break command terminates the loop (breaks out of it), while continue causes a jump to the next iteration of the loop, skipping all the remaining commands in that particular loop cycle.


Example 3-60. Effects of break and continue in a loop

   1 #!/bin/bash
   2 
   3 LIMIT=19  # Upper limit
   4 
   5 echo
   6 echo "Printing Numbers 1 through 20 (but not 3 and 11)."
   7 
   8 a=0
   9 
  10 while [ $a -le "$LIMIT" ]
  11 
  12 do
  13  a=$(($a+1))
  14 
  15  if [ "$a" -eq 3 ] || [ "$a" -eq 11 ]
  16  # Excludes 3 and 11
  17  then
  18    continue
  19    # Skip rest of this particular loop iteration.
  20  fi
  21 
  22  echo -n "$a "
  23 done 
  24 
  25 # Exercise for reader:
  26 # Why does loop print up to 20?
  27 
  28 echo
  29 echo
  30 
  31 echo Printing Numbers 1 through 20, but something happens after 2.
  32 
  33 ##################################################################
  34 
  35 # Same loop, but substituting 'break' for 'continue'.
  36 
  37 a=0
  38 
  39 while [ "$a" -le "$LIMIT" ]
  40 do
  41  a=$(($a+1))
  42 
  43  if [ "$a" -gt 2 ]
  44  then
  45    break
  46    # Skip entire rest of loop.
  47  fi
  48 
  49  echo -n "$a "
  50 done
  51 
  52 echo
  53 echo
  54 
  55 echo
  56 
  57 exit 0

The break command may optionally take a parameter. A plain break terminates only the innermost loop in which it is embedded, but a break N breaks out of N levels of loop.


Example 3-61. Breaking out of multiple loop levels

   1 #!/bin/bash
   2 # Breaking out of loops.
   3 
   4 # "break N" breaks out of N level loops.
   5 
   6 for outerloop in 1 2 3 4 5
   7 do
   8   echo -n "Group $outerloop:   "
   9 
  10   for innerloop in 1 2 3 4 5
  11   do
  12     echo -n "$innerloop "
  13 
  14     if [ "$innerloop" -eq 3 ]
  15     then
  16       break
  17       # Replace the line above with     break 2
  18       # to see what happens ("breaks" out of both inner and outer loops.)
  19     fi
  20 
  21   done
  22 
  23   echo
  24 done  
  25 
  26 echo
  27 
  28 exit 0

The continue command, similar to break, optionally takes a parameter. A plain continue cuts short the current iteration within its loop and begins the next. A continue N terminates all remaining iterations at its loop level and continues with the next iteration at the loop N levels above.


Example 3-62. Continuing at a higher loop level

   1 #!/bin/bash
   2 # The "continue N" command, continuing at the Nth level loop.
   3 
   4 for outer in I II III IV V           # outer loop
   5 do
   6   echo; echo -n "Group $outer: "
   7 
   8   for inner in 1 2 3 4 5 6 7 8 9 10  # inner loop
   9   do
  10 
  11     if [ "$inner" -eq 7 ]
  12     then
  13       continue 2  # Continue at loop on 2nd level, that is "outer loop".
  14       # Replace above line with a simple "continue" to see normal loop behavior.
  15     fi  
  16 
  17     echo -n "$inner "  # 8 9 10 will never echo.
  18   done  
  19 
  20 done
  21 
  22 echo; echo
  23 
  24 # Exercise for the reader:
  25 # Come up with a meaningful use for "continue N" in a script.
  26 
  27 exit 0

Caution

The continue N construct is difficult to understand and tricky to use in any meaningful context. It is probably best avoided.

3.8.4. Testing and Branching

The case and select constructs are technically not loops, since they do not iterate a code block. Like loops, however, they direct program flow according to conditions at the top or bottom of the block.

case (in) / esac

The case construct is the shell equivalent of switch in C/C++. It permits branching to one of a number of code blocks, depending on condition tests. It serves as a kind of shorthand for multiple if/then/else statements and is an appropriate tool for creating menus.

case "$variable" in

 "$condition1" )
 command...
 ;;

 "$condition2" )
 command...
 ;;

esac

Note

  • Quoting the variables is not mandatory, since word splitting does not take place.

  • Each test line ends with a right paren ).

  • Each condition block ends with a double semicolon ;;.

  • The entire case block terminates with an esac (case spelled backwards).


Example 3-63. Using case

   1 #!/bin/bash
   2 
   3 echo
   4 echo "Hit a key, then hit return."
   5 read Keypress
   6 
   7 case "$Keypress" in
   8   [a-z]   ) echo "Lowercase letter";;
   9   [A-Z]   ) echo "Uppercase letter";;
  10   [0-9]   ) echo "Digit";;
  11   *       ) echo "Punctuation, whitespace, or other";;
  12 esac
  13 # Allows ranges of characters in [square brackets].
  14 
  15 exit 0


Example 3-64. Creating menus using case

   1 #!/bin/bash
   2 
   3 # Crude rolodex-type database
   4 
   5 clear
   6 # Clear the screen.
   7 
   8 echo "          Contact List"
   9 echo "          ------- ----"
  10 echo "Choose one of the following persons:" 
  11 echo
  12 echo "[E]vans, Roland"
  13 echo "[J]ones, Mildred"
  14 echo "[S]mith, Julie"
  15 echo "[Z]ane, Morris"
  16 echo
  17 
  18 read person
  19 
  20 case "$person" in
  21 # Note variable is quoted.
  22 
  23   "E" | "e" )
  24   # Accept upper or lowercase input.
  25   echo
  26   echo "Roland Evans"
  27   echo "4321 Floppy Dr."
  28   echo "Hardscrabble, CO 80753"
  29   echo "(303) 734-9874"
  30   echo "(303) 734-9892 fax"
  31   echo "revans@zzy.net"
  32   echo "Business partner & old friend"
  33   ;;
  34 # Note double semicolon to terminate
  35 # each option.
  36 
  37   "J" | "j" )
  38   echo
  39   echo "Mildred Jones"
  40   echo "249 E. 7th St., Apt. 19"
  41   echo "New York, NY 10009"
  42   echo "(212) 533-2814"
  43   echo "(212) 533-9972 fax"
  44   echo "milliej@loisaida.com"
  45   echo "Girlfriend"
  46   echo "Birthday: Feb. 11"
  47   ;;
  48 
  49 # Add info for Smith & Zane later.
  50 
  51           * )
  52    # Default option.	  
  53    echo
  54    echo "Not yet in database."
  55   ;;   
  56 
  57 
  58 esac
  59 
  60 echo
  61 
  62 exit 0

An exceptionally clever use of case involves testing for command-line parameters.

   1 #! /bin/bash
   2 
   3 case "$1" in
   4 "") echo "Usage: ${0##*/} <filename>"; exit 65;;  # No command-line parameters,
   5                                                   # or first parameter empty.
   6 # Note that ${0##*/} is ${var##pattern} param substitution. Net result is $0.
   7 
   8 -*) FILENAME=./$1;;   # If filename passed as argument ($1) starts with a dash,
   9                       # replace it with ./$1
  10                       # so further commands don't interpret it as an option.
  11 
  12 * ) FILENAME=$1;;     # Otherwise, $1.
  13 esac


Example 3-65. Using command substitution to generate the case variable

   1 #!/bin/bash
   2 # Using command substitution to generate a "case" variable.
   3 
   4 
   5 case $( arch ) in   # "arch" returns machine architecture.
   6 i386 ) echo "80386-based machine";;
   7 i486 ) echo "80486-based machine";;
   8 i586 ) echo "Pentium-based machine";;
   9 i686 ) echo "Pentium2-based machine";;
  10 *    ) echo "Other type of machine";;
  11 esac
  12 
  13 exit 0

A case construct can filter strings for globbing patterns.


Example 3-66. Checking for alphabetic input

   1 #!/bin/bash
   2 # Using "case" structure to filter a string.
   3 
   4 SUCCESS=0
   5 FAILURE=-1
   6 
   7 isalpha ()  # Tests whether *first character* of input string is alphabetic.
   8 {
   9 if [ -z "$1" ]  # No argument passed?
  10 then
  11   return $FAILURE
  12 fi
  13 
  14 case "$1" in
  15 [a-zA-Z]*) return $SUCCESS;;  # Begins with a letter?
  16 *        ) return $FAILURE;;
  17 esac
  18 }  # Compare this with "isalpha ()" function in C.
  19 
  20 
  21 isalpha2 ()  # Tests whether *entire string* is alphabetic.
  22 {
  23   [ $# -eq 1 ] || return $FAILURE
  24 
  25   case $1 in
  26   *[!a-zA-Z]*|"") return $FAILURE;;
  27                *) return $SUCCESS;;
  28   esac
  29 }
  30 
  31 
  32 
  33 check_var ()  # Front-end to isalpha().
  34 {
  35 if isalpha "$@"
  36 then
  37   echo "$* = alpha"
  38 else
  39   echo "$* = non-alpha"  # Also "non-alpha" if no argument passed.
  40 fi
  41 }
  42 
  43 a=23skidoo
  44 b=H3llo
  45 c=-What?
  46 d=`echo $b`  # Command substitution.
  47 
  48 check_var $a
  49 check_var $b
  50 check_var $c
  51 check_var $d
  52 check_var # No argument passed, so what happens?
  53 
  54 
  55 # Script improved by S.C.
  56 
  57 exit 0

select

The select construct, adopted from the Korn Shell, is yet another tool for building menus.

select variable [in list]
do
 command...
 break
done

This prompts the user to enter one of the choices presented in the variable list. Note that select uses the PS3 prompt (#? ) by default, but that this may be changed.


Example 3-67. Creating menus using select

   1 #!/bin/bash
   2 
   3 PS3='Choose your favorite vegetable: '
   4 # Sets the prompt string.
   5 
   6 echo
   7 
   8 select vegetable in "beans" "carrots" "potatoes" "onions" "rutabagas"
   9 do
  10   echo
  11   echo "Your favorite veggie is $vegetable."
  12   echo "Yuck!"
  13   echo
  14   break
  15   # if no 'break' here, keeps looping forever.
  16 done
  17 
  18 exit 0

If in list is omitted, then select uses the list of command line arguments ($@) passed to the script or to the function in which the select construct is embedded.

Compare this to the behavior of a

for variable [in list]

construct with the in list omitted.


Example 3-68. Creating menus using select in a function

   1 #!/bin/bash
   2 
   3 PS3='Choose your favorite vegetable: '
   4 
   5 echo
   6 
   7 choice_of()
   8 {
   9 select vegetable
  10 # [in list] omitted, so 'select' uses arguments passed to function.
  11 do
  12   echo
  13   echo "Your favorite veggie is $vegetable."
  14   echo "Yuck!"
  15   echo
  16   break
  17 done
  18 }
  19 
  20 choice_of beans rice carrots radishes tomatoes spinach
  21 #         $1    $2   $3      $4       $5       $6
  22 #         passed to choice_of() function
  23 
  24 exit 0

Notes

[1]

These are shell builtins, whereas other loop commands, such as while and case, are keywords.