A loop is a block of code that
iterates (repeats) a list of commands
as long as the loop control condition
is true.
for loops
for arg in [list]
This is the basic looping construct. It differs significantly
from its C counterpart.
forarg 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.
forarg in [list] ; do
Example 10-1. Simple for loops
#!/bin/bash
# Listing the planets.
for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto
do
echo $planet # Each planet on a separate line.
done
echo
for planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto"
# All planets on same line.
# Entire 'list' enclosed in quotes creates a single variable.
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-15) 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/accept
/usr/sbin/pwck
/usr/sbin/chroot
/usr/bin/fakefile
/sbin/badblocks
/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.
# Note that the whatis database needs to have been set up for this to work.
# To do this, as root run /usr/bin/makewhatis.
echo
done
exit 0
If the [list] in a
for loop contains wildcards
(* and ?) used in filename
expansion, then globbing
takes place.
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 *
# ^ Bash performs filename expansion
#+ on expressions that globbing recognizes.
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-16.
Example 10-5. Missing in [list] in a
for loop
#!/bin/bash
# Invoke this script 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
Example 10-6. Generating the [list] in a for
loop with command substitution
#!/bin/bash
# for-loopcmd.sh: 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
Here is a somewhat more complex example of using command
substitution to create the [list].
#!/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` search_string filename"
exit $E_BADARGS
fi
if [ ! -f "$2" ]
then
echo "File \"$2\" does not exist."
exit $E_NOFILE
fi
IFS="\n" # Per suggestion of Paulo Marcel Coelho Aragao.
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, lines 23 - 29 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
# Exercise:
# --------
# How is it that an ordinary user (or a script run by same)
#+ can read /etc/passwd?
# Isn't this a security hole? Why or why not?
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.
# Strictly speaking, a loop isn't really necessary here,
#+ since the output of the "find" command is expanded into a single word.
# However, it's easy to understand and illustrative this way.
# 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
# Jean Helou proposes the following alternative:
echo "symbolic links in directory \"$directory\""
# Backup of the current IFS. One can never be too cautious.
OLDIFS=$IFS
IFS=:
for file in $(find $directory -type l -printf "%p$IFS")
do # ^^^^^^^^^^^^^^^^
echo "$file"
done|sort
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
#!/bin/bash
# Faxing (must have 'fax' installed).
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
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 0exit 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 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.
# ^ Space, to separate printed out numbers.
var0=`expr $var0 + 1` # var0=$(($var0+1)) also works.
# var0=$((var0 + 1)) also works.
# let "var0 += 1" also works.
done # Various other methods also work.
echo
exit 0
Example 10-15. Another while loop
#!/bin/bash
echo
# Equivalent to:
while [ "$var1" != "end" ] # while test "$var1" != "end"
do
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-30).
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