A loop is a block of code that iterates (repeats) a list of commands as long as the loop control condition is true.
This is the basic looping construct. It differs significantly from its C counterpart.
for   arg   in  [list]
  do 
   command(s)... 
  done 
|  | During each pass through the loop, arg takes on the value of each successive variable in the list. | 
| for arg in "$var1" "$var2" "$var3" ... "$varN" # In pass 1 of the loop, $arg = $var1 # In pass 2 of the loop, $arg = $var2 # In pass 3 of the loop, $arg = $var3 # ... # In pass N of the loop, $arg = $varN # 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 10-1. Simple for loops
| #!/bin/bash # List the planets. for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto do echo $planet done echo # Entire 'list' enclosed in quotes creates a single variable. for planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto" do echo $planet done exit 0 | 
|  | Each [list] element may contain multiple parameters. This is useful when processing parameters in groups. In such cases, use the set command (see Example 11-13) to force parsing of each [list] element and assignment of each component to the positional parameters. | 
Example 10-2. for loop with two parameters in each [list] element
| #!/bin/bash
# Planets revisited.
# Associate the name of each planet with its distance from the sun.
for planet in "Mercury 36" "Venus 67" "Earth 93"  "Mars 142" "Jupiter 483"
do
  set -- $planet  # Parses variable "planet" and sets positional parameters.
  # the "--" prevents nasty surprises if $planet is null or begins with a dash.
  # May need to save original positional parameters, since they get overwritten.
  # One way of doing this is to use an array,
  #        original_params=("$@")
  echo "$1		$2,000,000 miles from the sun"
  #-------two  tabs---concatenate zeroes onto parameter $2
done
# (Thanks, S.C., for additional clarification.)
exit 0 | 
A variable may supply the [list] in a for loop.
Example 10-3. Fileinfo: operating on a file list contained in a variable
| #!/bin/bash
# fileinfo.sh
FILES="/usr/sbin/privatepw
/usr/sbin/pwck
/usr/sbin/go500gw
/usr/bin/fakefile
/sbin/mkreiserfs
/sbin/ypbind"     # List of files you are curious about.
                  # Threw in a dummy file, /usr/bin/fakefile.
echo
for file in $FILES
do
  if [ ! -e "$file" ]       # Check if file exists.
  then
    echo "$file does not exist."; echo
    continue                # On to next.
   fi
  ls -l $file | awk '{ print $9 "         file size: " $5 }'  # Print 2 fields.
  whatis `basename $file`   # File info.
  echo
done  
exit 0 | 
The [list] in a for loop may contain filename globbing, that is, using wildcards for filename expansion.
Example 10-4. Operating on files with a for loop
| #!/bin/bash # list-glob.sh: Generating [list] in a for-loop using "globbing". echo for file in * do ls -l "$file" # Lists all files in $PWD (current directory). # Recall that the wild card character "*" matches every filename, # however, in "globbing", it doesn't match dot-files. # If the pattern matches no file, it is expanded to itself. # To prevent this, set the nullglob option # (shopt -s nullglob). # Thanks, S.C. done echo; echo for file in [jx]* do rm -f $file # Removes only files beginning with "j" or "x" in $PWD. echo "Removed file \"$file\"". done echo 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. A particularly clever illustration of this is Example A-17.
Example 10-5. Missing in [list] in a for loop
| #!/bin/bash # Invoke both with and without arguments, and see what happens. for a do echo -n "$a " done # The 'in list' missing, therefore the loop operates on '$@' #+ (command-line argument list, including whitespace). echo exit 0 | 
It is possible to use command substitution to generate the [list] in a for loop. See also Example 12-39, Example 10-10 and Example 12-33.
Example 10-6. Generating the [list] in a for loop with command substitution
| #!/bin/bash # A for-loop with [list] generated by command substitution. NUMBERS="9 7 3 8 37.53" for number in `echo $NUMBERS` # for number in 9 7 3 8 37.53 do echo -n "$number " done echo exit 0 | 
This is a somewhat more complex example of using command substitution to create the [list].
Example 10-7. A grep replacement for binary files
| #!/bin/bash # bin-grep.sh: Locates matching strings in a binary file. # A "grep" replacement for binary files. # Similar effect to "grep -a" E_BADARGS=65 E_NOFILE=66 if [ $# -ne 2 ] then echo "Usage: `basename $0` string filename" exit $E_BADARGS fi if [ ! -f "$2" ] then echo "File \"$2\" does not exist." exit $E_NOFILE fi for word in $( strings "$2" | grep "$1" ) # The "strings" command lists strings in binary files. # Output then piped to "grep", which tests for desired string. do echo $word done # As S.C. points out, the above for-loop could be replaced with the simpler # strings "$2" | grep "$1" | tr -s "$IFS" '[\n*]' # Try something like "./bin-grep.sh mem /bin/ls" to exercise this script. exit 0 | 
More of the same.
Example 10-8. Listing all users on the system
| #!/bin/bash
# userlist.sh
PASSWORD_FILE=/etc/passwd
n=1           # User number
for name in $(awk 'BEGIN{FS=":"}{print $1}' < "$PASSWORD_FILE" )
# Field separator = :    ^^^^^^
# Print first field              ^^^^^^^^
# Get input from password file               ^^^^^^^^^^^^^^^^^
do
  echo "USER #$n = $name"
  let "n += 1"
done  
# USER #1 = root
# USER #2 = bin
# USER #3 = daemon
# ...
# USER #30 = bozo
exit 0 | 
A final example of the [list] resulting from command substitution.
Example 10-9. Checking all the binaries in a directory for authorship
| #!/bin/bash # findstring.sh: # Find a particular string in binaries in a specified directory. directory=/usr/bin/ fstring="Free Software Foundation" # See which files come from the FSF. for file in $( find $directory -type f -name '*' | sort ) do strings -f $file | grep "$fstring" | sed -e "s%$directory%%" # In the "sed" expression, #+ it is necessary to substitute for the normal "/" delimiter #+ because "/" happens to be one of the characters filtered out. # Failure to do so gives an error message (try it). done exit 0 # Exercise (easy): # --------------- # 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 10-10. Listing the symbolic links in a directory
| #!/bin/bash
# symlinks.sh: Lists symbolic links in a directory.
directory=${1-`pwd`}
#  Defaults to current working directory,
#+ if not otherwise specified.
#  Equivalent to code block below.
# ----------------------------------------------------------
# ARGS=1                 # Expect one command-line argument.
#
# if [ $# -ne "$ARGS" ]  # If not 1 arg...
# then
#   directory=`pwd`      # current working directory
# else
#   directory=$1
# fi
# ----------------------------------------------------------
echo "symbolic links in directory \"$directory\""
for file in "$( find $directory -type l )"   # -type l = symbolic links
do
  echo "$file"
done | sort                                  # Otherwise file list is unsorted.
#  As Dominik 'Aeneas' Schnitzer points out,
#+ failing to quote  $( find $directory -type l )
#+ will choke on filenames with embedded whitespace.
#  Even this will only pick up the first field of each argument.
exit 0 | 
The stdout of a loop may be redirected to a file, as this slight modification to the previous example shows.
Example 10-11. Symbolic links in a directory, saved to a file
| #!/bin/bash
# symlinks.sh: Lists symbolic links in a directory.
OUTFILE=symlinks.list                         # save file
directory=${1-`pwd`}
#  Defaults to current working directory,
#+ if not otherwise specified.
echo "symbolic links in directory \"$directory\"" > "$OUTFILE"
echo "---------------------------" >> "$OUTFILE"
for file in "$( find $directory -type l )"    # -type l = symbolic links
do
  echo "$file"
done | sort >> "$OUTFILE"                     # stdout of loop
#           ^^^^^^^^^^^^^                       redirected to save file.
exit 0 | 
There is an alternative syntax to a for loop that will look very familiar to C programmers. This requires double parentheses.
Example 10-12. A C-like for loop
| #!/bin/bash # Two ways to count up to 10. echo # Standard syntax. for a in 1 2 3 4 5 6 7 8 9 10 do echo -n "$a " done echo; echo # +==========================================+ # Now, let's do the same, using C-like syntax. LIMIT=10 for ((a=1; a <= LIMIT ; a++)) # Double parentheses, and "LIMIT" with no "$". do echo -n "$a " done # A construct borrowed from 'ksh93'. echo; echo # +=========================================================================+ # Let's use the C "comma operator" to increment two variables simultaneously. for ((a=1, b=1; a <= LIMIT ; a++, b++)) # The comma chains together operations. do echo -n "$a-$b " done echo; echo exit 0 | 
See also Example 26-8, Example 26-9, and Example A-7.
---
Now, a for-loop used in a "real-life" context.
Example 10-13. Using efax in batch mode
| #!/bin/bash
EXPECTED_ARGS=2
E_BADARGS=65
if [ $# -ne $EXPECTED_ARGS ]
# Check for proper no. of command line args.
then
   echo "Usage: `basename $0` phone# text-file"
   exit $E_BADARGS
fi
if [ ! -f "$2" ]
then
  echo "File $2 is not a text file"
  exit $E_BADARGS
fi
  
fax make $2              # Create fax formatted files from text files.
for file in $(ls $2.0*)  # Concatenate the converted files.
                         # Uses wild card in variable list.
do
  fil="$fil $file"
done  
efax -d /dev/ttyS3 -o1 -t "T$1" $fil   # Do the work.
# As S.C. points out, the for-loop can be eliminated with
#    efax -d /dev/ttyS3 -o1 -t "T$1" $2.0*
# but it's not quite as instructive [grin].
exit 0 | 
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). In contrast to a for loop, a while loop finds use in situations where the number of loop repetitions is not known beforehand.
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 10-14. Simple while loop
| #!/bin/bash var0=0 LIMIT=10 while [ "$var0" -lt "$LIMIT" ] do echo -n "$var0 " # -n suppresses newline. var0=`expr $var0 + 1` # var0=$(($var0+1)) also works. done echo exit 0 | 
Example 10-15. Another while loop
| #!/bin/bash echo while [ "$var1" != "end" ] # while test "$var1" != "end" do # also works. echo "Input variable #1 (end to exit) " read var1 # Not 'read $var1' (why?). echo "variable #1 = $var1" # Need quotes because of "#". # If input is 'end', echoes it here. # Does not test for termination condition until top of loop. echo done exit 0 | 
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 10-16. while loop with multiple conditions
| #!/bin/bash
var1=unset
previous=$var1
while echo "previous-variable = $previous"
      echo
      previous=$var1
      [ "$var1" != end ] # Keeps track of what $var1 was previously.
      # Four conditions on "while", but only last one controls loop.
      # The *last* exit status is the one that counts.
do
echo "Input variable #1 (end to exit) "
  read var1
  echo "variable #1 = $var1"
done  
# Try to figure out how this all works.
# It's a wee bit tricky.
exit 0 | 
As with a for loop, a while loop may employ C-like syntax by using the double parentheses construct (see also Example 9-28).
Example 10-17. C-like syntax in a while loop
| #!/bin/bash # wh-loopc.sh: Count to 10 in a "while" loop. LIMIT=10 a=1 while [ "$a" -le $LIMIT ] do echo -n "$a " let "a+=1" done # No surprises, so far. echo; echo # +=================================================================+ # Now, repeat with C-like syntax. ((a = 1)) # a=1 # Double parentheses permit space when setting a variable, as in C. while (( a <= LIMIT )) # Double parentheses, and no "$" preceding variables. do echo -n "$a " ((a += 1)) # let "a+=1" # Yes, indeed. # Double parentheses permit incrementing a variable with C-like syntax. done echo # Now, C programmers can feel right at home in Bash. exit 0 | 
|  | A while loop may have its stdin redirected to a file by a < at its end. | 
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