Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Cooper M.Advanced bash-scripting guide.2002.pdf
Скачиваний:
13
Добавлен:
23.08.2013
Размер:
916.67 Кб
Скачать

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