- •Table of Contents
- •Chapter 1. Why Shell Programming?
- •2.1. Invoking the script
- •2.2. Preliminary Exercises
- •Part 2. Basics
- •Chapter 3. Exit and Exit Status
- •Chapter 4. Special Characters
- •Chapter 5. Introduction to Variables and Parameters
- •5.1. Variable Substitution
- •5.2. Variable Assignment
- •5.3. Bash Variables Are Untyped
- •5.4. Special Variable Types
- •Chapter 6. Quoting
- •Chapter 7. Tests
- •7.1. Test Constructs
- •7.2. File test operators
- •7.3. Comparison operators (binary)
- •7.4. Nested if/then Condition Tests
- •7.5. Testing Your Knowledge of Tests
- •Chapter 8. Operations and Related Topics
- •8.1. Operators
- •8.2. Numerical Constants
- •Part 3. Beyond the Basics
- •Chapter 9. Variables Revisited
- •9.1. Internal Variables
- •9.2. Manipulating Strings
- •9.2.1. Manipulating strings using awk
- •9.2.2. Further Discussion
- •9.3. Parameter Substitution
- •9.4. Typing variables: declare or typeset
- •9.5. Indirect References to Variables
- •9.6. $RANDOM: generate random integer
- •9.7. The Double Parentheses Construct
- •Chapter 10. Loops and Branches
- •10.1. Loops
- •10.2. Nested Loops
- •10.3. Loop Control
- •10.4. Testing and Branching
- •Chapter 11. Internal Commands and Builtins
- •11.1. Job Control Commands
- •Chapter 12. External Filters, Programs and Commands
- •12.1. Basic Commands
- •12.2. Complex Commands
- •12.3. Time / Date Commands
- •12.4. Text Processing Commands
- •12.5. File and Archiving Commands
- •12.6. Communications Commands
- •12.7. Terminal Control Commands
- •12.8. Math Commands
- •12.9. Miscellaneous Commands
- •Chapter 13. System and Administrative Commands
- •Chapter 14. Command Substitution
- •Chapter 15. Arithmetic Expansion
- •Chapter 16. I/O Redirection
- •16.1. Using exec
- •16.2. Redirecting Code Blocks
- •16.3. Applications
- •Chapter 17. Here Documents
- •Chapter 18. Recess Time
- •Part 4. Advanced Topics
- •Chapter 19. Regular Expressions
- •19.1. A Brief Introduction to Regular Expressions
- •19.2. Globbing
- •Chapter 20. Subshells
- •Chapter 21. Restricted Shells
- •Chapter 22. Process Substitution
- •Chapter 23. Functions
- •23.1. Complex Functions and Function Complexities
- •23.2. Local Variables
- •23.2.1. Local variables make recursion possible.
- •Chapter 24. Aliases
- •Chapter 25. List Constructs
- •Chapter 26. Arrays
- •Chapter 27. Files
- •Chapter 28. /dev and /proc
- •28.2. /proc
- •Chapter 29. Of Zeros and Nulls
- •Chapter 30. Debugging
- •Chapter 31. Options
- •Chapter 32. Gotchas
- •Chapter 33. Scripting With Style
- •33.1. Unofficial Shell Scripting Stylesheet
- •Chapter 34. Miscellany
- •34.2. Shell Wrappers
- •34.3. Tests and Comparisons: Alternatives
- •34.4. Optimizations
- •34.5. Assorted Tips
- •34.6. Oddities
- •34.7. Portability Issues
- •34.8. Shell Scripting Under Windows
- •Chapter 35. Bash, version 2
- •Chapter 36. Endnotes
- •36.1. Author's Note
- •36.2. About the Author
- •36.3. Tools Used to Produce This Book
- •36.3.1. Hardware
- •36.3.2. Software and Printware
- •36.4. Credits
- •Bibliography
- •Appendix A. Contributed Scripts
- •Appendix C. Exit Codes With Special Meanings
- •Appendix D. A Detailed Introduction to I/O and I/O Redirection
- •Appendix E. Localization
- •Appendix F. History Commands
- •Appendix G. A Sample .bashrc File
- •Appendix H. Converting DOS Batch Files to Shell Scripts
- •Appendix I. Exercises
- •Appendix J. Copyright
Chapter 10. 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.
10.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 loops
for (in)
This is the basic looping construct. It differs significantly from its C counterpart.
for arg in [list] do
command...
done
During each pass through the loop, arg takes on the value of each 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
Chapter 10. Loops and Branches |
98 |
Advanced Bash−Scripting Guide
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−10) 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. |
Chapter 10. Loops and Branches |
99 |
Advanced Bash−Scripting Guide
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 everything,
#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−11.
Example 10−5. Missing in [list] in a for loop
Chapter 10. Loops and Branches |
100 |
Advanced Bash−Scripting Guide
#!/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−32 , Example 10−9 and Example 12−29.
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
Chapter 10. Loops and Branches |
101 |
Advanced Bash−Scripting Guide
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
Here is yet another example of the [list] resulting from command substitution.
Example 10−8. 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 for the reader (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−9. Listing the symbolic links in a directory
#!/bin/bash
# symlinks.sh: Lists symbolic links in a directory.
ARGS=1
if [ $# −ne "$ARGS" ] then
directory=`pwd` else
directory=$1
fi
#Expect one command−line argument.
#If not 1 arg...
#current working directory
Chapter 10. Loops and Branches |
102 |
Advanced Bash−Scripting Guide
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.
exit 0
The stdout of a loop may be redirected to a file, as this slight modification to the previous example shows.
Example 10−10. Symbolic links in a directory, saved to a file
#!/bin/bash
# symlinks.sh: Lists symbolic links in a directory.
ARGS=1
OUTFILE=symlinks.list
if [ $# −ne "$ARGS" ] then
directory=`pwd` else
directory=$1
fi
#Expect one command−line argument.
#save file
#If not 1 arg...
#current working directory
echo "symbolic links in directory \"$directory\""
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−11. 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
Chapter 10. Loops and Branches |
103 |
Advanced Bash−Scripting Guide
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−6 and Example 26−7.
−−−
Now, for an example from "real life".
Example 10−12. 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.
Chapter 10. Loops and Branches |
104 |
Advanced Bash−Scripting Guide
# 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 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 10−13. 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−14. Another while loop
#!/bin/bash
echo
while [ "$var1" != "end" ] |
# while test "$var1" != "end" |
Chapter 10. Loops and Branches |
105 |
Advanced Bash−Scripting Guide
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−15. 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−24).
Example 10−16. 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 |
|
Chapter 10. Loops and Branches |
106 |
Advanced Bash−Scripting Guide
#+=================================================================+
#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.
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 10−17. until loop
#!/bin/bash
until [ "$var1" = end ] # Tests condition here, at top of loop. do
echo |
"Input variable #1 " |
echo |
"(end to exit)" |
read |
var1 |
echo |
"variable #1 = $var1" |
done |
|
exit 0 |
|
Chapter 10. Loops and Branches |
107 |