Previous Section  < Day Day Up >  Next Section

14.6. Looping Commands

Looping commands are used to execute a command or group of commands a set number of times or until a certain condition is met. The bash shell has three types of loops: for, while, and until.

14.6.1 The for Command

The for looping command is used to execute commands a finite number of times on a list of items. For example, you might use this loop to execute the same commands on a list of files or usernames. The for command is followed by a user-defined variable, the keyword in, and a list of words. The first time in the loop, the first word from the wordlist is assigned to the variable, and then shifted off the list. Once the word is assigned to the variable, the body of the loop is entered, and commands between the do and done keywords are executed. The next time around the loop, the second word is assigned to the variable, and so on. The body of the loop starts at the do keyword and ends at the done keyword. When all of the words in the list have been shifted off, the loop ends and program control continues after the done keyword.

FORMAT


for variable in word_list

do

    command(s)

done


Example 14.32.

(The Script)

    #!/bin/bash

    # Scriptname: forloop

1   for pal in Tom Dick Harry Joe

2   do

3        echo "Hi $pal"

4   done

5   echo "Out of loop"



(The Output)

Hi Tom

Hi Dick

Hi Harry

Hi Joe

Out of loop


EXPLANATION

  1. This for loop will iterate through the list of names, Tom, Dick, Harry, and Joe, shifting each one off to the left and assigning its value to the user-defined variable, pal, after it is used. As soon as all of the words are shifted and the wordlist is empty, the loop ends and execution starts after the done keyword. The first time in the loop, the variable pal will be assigned the word Tom. The second time through the loop, pal will be assigned Dick, the next time pal will be assigned Harry , and the last time pal will be assigned Joe.

  2. The do keyword is required after the wordlist. If it is used on the same line, the list must be terminated with a semicolon. For example:

    
    for pal in Tom Dick Harry Joe; do
    
    

  3. This is the body of the loop. After Tom is assigned to the variable pal, the commands in the body of the loop (i.e., all commands between the do and done keywords) are executed.

  4. The done keyword ends the loop. Once the last word in the list (Joe) has been assigned and shifted off, the loop exits, and execution starts at line 2.

  5. Control resumes here when the loop exits.

Example 14.33.

(The Command Line)

1   $ cat mylist

    tom

    patty

    ann

    jake



(The Script)

    #!/bin/bash

    # Scriptname: mailer

2   for person in $(cat mylist)    # `cat mylist` command substitution the alternate way

    do

3       mail $person < letter

        echo $person was sent a letter.

4   done

5   echo "The letter has been sent."


EXPLANATION

  1. The contents of a file called mylist are displayed.

  2. Command substitution is performed and the contents of mylist becomes the wordlist. The first time in the loop, tom is assigned to the variable person, then it is shifted off to be replaced with patty , and so forth.

  3. In the body of the loop, each user is mailed a copy of a file called letter.

  4. The done keyword marks the end of this loop iteration.

  5. When all of the users in the list have been sent mail and the loop has exited, this line is executed.

Example 14.34.

(The Script)

     #!/bin/bash

     # Scriptname: backup

     # Purpose: Create backup files and store

     # them in a backup directory.

     #



1    dir=/home/jody/ellie/backupscripts

2    for file in memo[1-5]

     do

3        if [ -f $file ]

         then

              cp $file $dir/$file.bak

              echo "$file is backed up in $dir"

         fi

4    done



(The Output)

memo1 is backed up in /home/jody/ellie/backupscripts

memo2 is backed up in /home/jody/ellie/backupscripts

memo3 is backed up in /home/jody/ellie/backupscripts

memo4 is backed up in /home/jody/ellie/backupscripts

memo5 is backed up in /home/jody/ellie/backupscripts


EXPLANATION

  1. The variable dir is assigned the directory where the backup scripts are to be stored.

  2. The wordlist will consist of all files in the current working directory with names starting with memo and ending with numbers between 1 and 5. Each filename will be assigned, one at time, to the variable file for each iteration of the loop.

  3. When the body of the loop is entered, the file will be tested to make sure it exists and is a real file. If so, it will be copied into the directory /home/jody/ellie/backupscripts with the .bak extension appended to its name.

  4. The done marks the end of the loop.

14.6.2 The $* and $@ Variables in Wordlists

When expanded, the $* and $@ are the same unless enclosed in double quotes. $* evaluates to one string, whereas $@ evaluates to a list of separate words.

Example 14.35.

(The Script)

     #!/bin/bash

     # Scriptname: greet

1    for name in $*        # same as for name in $@

2    do

         echo Hi $name

3    done



(The Command Line)

$ greet Dee Bert Lizzy Tommy

Hi Dee

Hi Bert

Hi Lizzy

Hi Tommy


EXPLANATION

  1. $* and $@ expand to a list of all the positional parameters, in this case, the arguments passed in from the command line: Dee, Bert, Lizzy, and Tommy. Each name in the list will be assigned, in turn, to the variable name in the for loop.

  2. The commands in the body of the loop are executed until the list is empty.

  3. The done keyword marks the end of the loop body.

Example 14.36.

(The Script)

     #!/bin/bash

     # Scriptname: permx



1    for file         # Empty wordlist

     do

2      if [[ -f $file && ! -x $file ]]

       then

3             chmod +x $file

              echo $file now has execute permission

       fi

     done

(The Command Line)

4   $ permx *

    addon now has execute permission

    checkon now has execute permission

    doit now has execute permission


EXPLANATION

  1. If the for loop is not provided with a wordlist, it iterates through the positional parameters. This is the same as for file in $*.

  2. The filenames are coming in from the command line. The shell expands the asterisk (*) to all filenames in the current working directory. If the file is a plain file and does not have execute permission, line 3 is executed.

  3. Execute permission is added for each file being processed.

  4. At the command line, the asterisk will be evaluated by the shell as a wildcard and all files in the current directory will be replaced for the *. The files will be passed as arguments to the permx script.

14.6.3 The while Command

The while command evaluates the command following it and, if its exit status is 0, the commands in the body of the loop (commands between do and done) are executed. When the done keyword is reached, control is returned to the top of the loop and the while command checks the exit status of the command again. Until the exit status of the command being evaluated by the while becomes nonzero, the loop continues. When the exit status reaches nonzero, program execution starts after the done keyword.

FORMAT


while command

do

    command(s)

done


Example 14.37.

(The Script)

    #!/bin/bash

    # Scriptname: num

1   num=0          # Initialize num

2   while (( $num < 10 ))[a]  # or  while [ num -lt 10 ]

    do

        echo  -n "$num "

3       let num+=1           # Increment num

    done

4   echo -e "\nAfter loop exits, continue running here"



(The Output)

    0 1 2 3 4 5 6 7 8 9

4   After loop exits, continue running here


EXPLANATION

  1. This is the initialization step. The variable num is assigned 0.

  2. The while command is followed by the let command. The let command evaluates the arithmetic expression, returning an exit status of 0 (true) if the condition is true (i.e., if the value of num is less than 10, the body of the loop is entered).

  3. In the body of the loop, the value of num is incremented by one. If the value of num never changes, the loop would iterate infinitely or until the process is killed.

  4. After the loop exits, the echo command (with the –e option) prints a newline and the string.

Example 14.38.

(The Script)

    #!/bin/bash

    # Scriptname: quiz

1   echo "Who was the 2nd U.S. president to be impeached?"

    read answer

2   while [[ "$answer" != "Bill Clinton" ]]

3   do

         echo "Wrong try again!"

4        read answer

5   done

6   echo You got it!



(The Output)

Who was the 2nd U.S. president to be impeached? Ronald Reagan

Wrong try again!

Who was the 2nd U.S. president to be impeached?  I give up

Wrong try again!

Who was the 2nd U.S. president to be impeached?  Bill Clinton

You got it!


EXPLANATION

  1. The echo command prompts the user, Who was the 2nd U.S. president to be impeached? The read command waits for input from the user. The input will be stored in the variable answer.

  2. The while loop is entered and the test command, the bracket, tests the expression. If the variable answer does not exactly equal the string Bill Clinton, the body of the loop is entered and commands between the do and done are executed.

  3. The do keyword is the start of the loop body.

  4. The user is asked to re-enter input.

  5. The done keyword marks the end of the loop body. Control is returned to the top of the while loop, and the expression is tested again. As long as answer does not evaluate to Bill Clinton, the loop will continue to iterate. When the user enters Bill Clinton, the loop ends. Program control goes to line 6.

  6. When the body of the loop ends, control starts here.

Example 14.39.

(The Script)

$ cat sayit

    #!/bin/bash

    # Scriptname: sayit

    echo  Type q to quit.

    go=start

1   while [[ -n "$go" ]]   # Make sure to double quote the variable

    do

2       echo -n I love you.

3       read word

4       if [[ $word == [Qq] ]]

        then         # [ "$word" = q -o "$word" = Q ]  Old style

            echo "I'll always love you!"

            go=

        fi

    done



(The Output)

Type q to quit.

I love you.       <-- When user presses Enter, the program continues

I love you.

I love you.

I love you.

I love you.q

I'll always love you!

$


EXPLANATION

  1. The command after the while is executed and its exit status tested. The –n option to the test command tests for a non-null string. Because variable go initially has a value, the test is successful, producing a zero exit status. If the variable go is not enclosed in double quotes and the variable is null, the test command would complain:

    
    go: test: argument expected
    
    

  2. The loop is entered. The string I love you is echoed to the screen.

  3. The read command waits for user input.

  4. The expresson is tested. If the user enters a q or Q, the string I'll always love you! is displayed, and the variable go is set to null. When the while loop is re-entered, the test is unsuccessful because the variable is null. The loop terminates. Control goes to the line after the done statement. In this example, the script will terminate because there are no more lines to execute.

14.6.4 The until Command

The until command is used like the while command, but executes the loop statements only if the command after until fails, that is, if the command returns an exit status of nonzero. When the done keyword is reached, control is returned to the top of the loop and the until command checks the exit status of the command again. Until the exit status of the command being evaluated by until becomes 0, the loop continues. When the exit status reaches 0, the loop exits, and program execution starts after the done keyword.

FORMAT


until command

do

    command(s)

done


Example 14.40.

    #!/bin/bash

1   until who | grep linda

2   do

        sleep 5

3   done

    talk linda@dragonwings


EXPLANATION

  1. The until loop tests the exit status of the last command in the pipeline, grep. The who command lists who is logged on this machine and pipes its output to grep. The grep command will return a 0 exit status (success) only when it finds user linda.

  2. If user linda has not logged on, the body of the loop is entered and the program sleeps for five seconds.

  3. When linda logs on, the exit status of the grep command will be zero and control will go to the statements following the done keyword.

Example 14.41.

(The Script)

    #!/bin/bash

    # Scriptname: hour



1   hour=0

2   until (( hour > 24 ))

    do

3       case "$hour" in

        [0-9]|1[0-1])    echo "Good morning!"

             ;;

        12)  echo "Lunch time."

             ;;

        1[3-7])echo "Siesta time."

             ;;

        *)   echo "Good night."

             ;;

        esac

4       (( hour+=1 ))   # Don't forget to increment the hour

5   done



(The Output)

Good morning!

Good morning!

    ...

Lunch time.

Siesta time.

    ...

Good night.

    ...


EXPLANATION

  1. The variable hour is initialized to 0.

  2. The let command (( )) tests the arithmetic expression for an hour greater than 24. If the hour is not greater than 24, the body of the loop is entered. The until loop is entered if the command following it returns a nonzero exit status. Until the condition is true, the loop continues to iterate.

  3. The case command evaluates the hour variable and tests each of the case statements for a match.

  4. The hour variable is incremented before control returns to the top of the loop.

  5. The done command marks the end of the loop body.

14.6.5 The select Command and Menus

The here document is an easy method for creating menus, but bash introduces another looping mechanism, called the select loop, used primarily for creating menus. A menu of numerically listed items is displayed to standard error. The PS3 prompt is used to prompt the user for input; by default, PS3 is #?. After the PS3 prompt is displayed, the shell waits for user input. The input should be one of the numbers in the menu list. The input is stored in the special shell REPLY variable. The number in the REPLY variable is associated with the string to the right of the parentheses in the list of selections.

The case command is used with the select command to allow the user to make a selection from the menu and, based on that selection, execute commands. The LINES and COLUMNS variables can be used to determine the layout of the menu items displayed on the terminal. (These variables are built into versions of bash 2.x, but are not built into earlier versions; if they have not been defined, you can define and export them in the .bash_profile.) The output is displayed to standard error, each item preceded by a number and closing parenthesis, and the PS3 prompt is displayed at the bottom of the menu. Because the select command is a looping command, it is important to remember to use either the break command to get out of the loop, or the exit command to exit the script.

FORMAT


select var in wordlist

do

    command(s)

done


Example 14.42.

(The Script)

    #!/bin/bash

    # Scriptname: runit



1   PS3="Select a program to execute: "

2   select program  in 'ls -F' pwd date

3   do

4        $program

5   done



(The Command Line)

Select a program to execute: 2

1) ls -F

2) pwd

3) date

/home/ellie

Select a program to execute: 1

1) ls -F

2) pwd

3) date

12abcrty abc12  doit* progs/  xyz

Select a program to execute: 3

1) ls -F

2) pwd

3) date

Sun Mar 12 13:28:25 PST 2004


EXPLANATION

  1. The PS3 prompt is assigned the string that will appear below the menu that the select loop displays. This prompt is $# by default and is sent to standard error, the screen.

  2. The select loop consists of a variable called program and the three-word list that will be displayed in the menu: ls –F, pwd, and date. The words in this list are UNIX/ Linux commands, but they could be any words (e.g., red, green, yellow, or cheese, bread, milk, crackers). If the word has a space, it must be quoted (e.g., 'ls –F')

  3. The do keyword starts the body of the select loop.

  4. When the user selects numbers in the menu, that number will be equated with the word value to the right of the number after the parentheses. For example, if the user selects number 2, that is associated with pwd and pwd is stored in the program variable. $program evaluates to the executable command, pwd; the command is executed.

  5. The done keyword marks the end of the body of statements in the select loop. Control will return to the top of the loop. This loop will continue to execute until the user presses ^C.

Example 14.43.

(The Script)

    #!/bin/bash

    # Scriptname: goodboys



1   PS3="Please choose one of the three boys : "

2   select choice in tom dan guy

3   do

4       case "$choice" in

        tom)

            echo Tom is a cool dude!

5            break;;         # break out of the select loop

6       dan | guy )

            echo Dan and Guy are both wonderful.

            break;;

        *)

7           echo "$REPLY is not one of your choices" 1>&2

            echo "Try again."

            ;;

8       esac

9   done



(The Command Line)

$ goodboys

1) tom

2) dan

3) guy

Please choose one of the three boys : 2

Dan and Guy are both wonderful.

$ goodboys

1) tom

2) dan

3) guy

Please choose one of the three boys : 4

4 is not one of your choices

Try again.

Please choose one of the three boys : 1

Tom is a cool dude!

$


EXPLANATION

  1. The PS3 prompt will be printed below the menu created by the select loop on line 2.

  2. The select loop is entered. It causes the words in its list to be displayed as a numbered menu.

  3. The loop body starts here.

  4. The variable choice is assigned the first value on the list, after which the value is shifted off the list and the next item will be first.

  5. The break statement sends loop control after line 9.

  6. If either dan or guy are selected, the following echo command is executed, followed by the break command, sending control after line 9.

  7. The built-in REPLY variable contains the number of the current list item; that is, 1, 2, or 3.

  8. This marks the end of the case command.

  9. The done marks the end of the select loop.

Example 14.44.

(The Script)

    #!/bin/bash

    # Scriptname: ttype

    # Purpose: set the terminal type

    # Author: Andy Admin



1   COLUMNS=60

2   LINES=1

3   PS3="Please enter the terminal type: "

4   select choice in wyse50 vt200 xterm sun

    do

5       case "$REPLY" in

        1)

6            export TERM=$choice

             echo "TERM=$choice"

             break;;            # break out of the select loop

        2 | 3 )

             export TERM=$choice



             echo "TERM=$choice"

             break;;

        4)

             export TERM=$choice

             echo "TERM=$choice"

             break;;

        *)

7            echo -e "$REPLY is not a valid choice. Try again\n" 1>&2

8            REPLY=    # Causes the menu to be redisplayed

                  ;;

        esac

9   done

(The Command Line)

$ ttype

1) wyse50    2) vt200   3) xterm    4) sun

Please enter the terminal type : 4

TERM=sun



$ ttype

1) wyse50    2) vt200   3) xterm    4) sun

Please enter the terminal type : 3

TERM=xterm



$ ttype

1) wyse50    2) vt200   3) xterm    4) sun

Please enter the terminal type : 7

7 is not a valid choice. Try again.



1) wyse50    2) vt200   3) xterm    4) sun

Please enter the terminal type:

TERM=vt200


EXPLANATION

  1. The COLUMNS variable is set to the width of the terminal display in columns for menus created with the select loop. The default is 80.

  2. The LINES variable controls the vertical display of the select menu on the terminal. The default is 24 lines. When you change the LINES value to 1, the menu items will be printed on one line, instead of vertically as in the last example.

  3. The PS3 prompt is set and will appear below the menu choices.

  4. The select loop will print a menu with four selections: wyse50, vt200, xterm, and sun. The variable choice will be assigned one of these values based on the user's response held in the REPLY variable. If REPLY is 1, wyse50 is assigned to choice; if REPLY is 2, vt200 is assigned to choice; if REPLY is 3, xterm is assigned to choice; and if REPLY is 4, sun is assigned to choice.

  5. The REPLY variable evaluates to the user's input selection.

  6. The terminal type is assigned, exported, and printed.

  7. If the user does not enter a number between 1 and 4, he or she will be prompted again. Note that the menu does not appear, just the PS3 prompt.

  8. If the REPLY variable is set to null (e.g., REPLY=), the menu will be redisplayed.

  9. The end of the select loop.

14.6.6 Looping Control Commands

If some condition occurs, you may want to break out of a loop, return to the top of the loop, or provide a way to stop an infinite loop. The bash shell provides loop control commands to handle these kinds of situations.

The shift Command

The shift command shifts the parameter list to the left a specified number of times. The shift command without an argument shifts the parameter list once to the left. Once the list is shifted, the parameter is removed permanently. Often, the shift command is used in a while loop when iterating through a list of positional parameters.

FORMAT


shift [n]


Example 14.45.

(Without a Loop)

(The Script)

    #!/bin/bash

    # Scriptname: shifter

1   set joe mary tom sam

2   shift

3   echo $*

4   set $(date)

5   echo  $*

6   shift 5

7   echo  $*

8   shift 2



(The Output)

3   mary tom sam

5   Thu Mar 18 10:00:12 PST 2004

7   2001

8   shift: shift count must be <= $#


EXPLANATION

  1. The set command sets the positional parameters. $1 is assigned joe, $2 is assigned mary , $3 is assigned tom, and $4 is assigned sam. $* represents all of the parameters.

  2. The shift command shifts the positional parameters to the left; joe is shifted off.

  3. The parameter list is printed after the shift.

  4. The set command resets the positional parameters to the output of the UNIX date command.

  5. The new parameter list is printed.

  6. This time the list is shifted 5 times to the left.

  7. The new parameter list is printed.

  8. By attempting to shift more times than there are parameters, the shell sends a message to standard error stating that the shift command cannot shift off more parameters than it has. $# is the total number of positional parameters. On versions of bash 2.x, no error message occurs.

Example 14.46.

(With a Loop)

(The Script)

    #!/bin/bash

    # Name: doit

    # Purpose: shift through command-line arguments

    # Usage: doit [args]

1   while (( $# > 0 ))

    do

2       echo  $*

3       shift

4   done



(The Command Line and Output)

$ doit a b c d e

a b c d e

b c d e

c d e

d e

e


EXPLANATION

  1. The while command tests the numeric expression. If the number of positional parameters ($#) is greater than 0, the body of the loop is entered. The positional parameters are coming from the command line as arguments. There are five.

  2. All positional parameters are printed.

  3. The parameter list is shifted once to the left.

  4. The body of the loop ends here; control returns to the top of the loop. Each time the loop is entered, the shift command causes the parameter list to be decreased by one. After the first shift, $# (number of positional parameters) is four. When $# has been decreased to 0, the loop ends.

Example 14.47.

(The Script)

    #!/bin/bash

    # Scriptname: dater

    # Purpose: set positional parameters with the set command

    # and shift through the parameters.



1   set $(date)

2   while (( $# > 0 ))

    do

3       echo $1

4       shift

    done



(The Output)

Wed

Mar

17

19:25:00

PST

2004


EXPLANATION

  1. The set command takes the output of the date command and assigns the output to positional parameters $1 through $6.

  2. The while command tests whether the number of positional parameters ($#) is greater than 0. If true, the body of the loop is entered.

  3. The echo command displays the value of $1, the first positional parameter.

  4. The shift command shifts the parameter list once to the left. Each time through the loop, the list is shifted until the list is empty. At that time, $# will be zero and the loop terminates.

The break Command

The built-in break command is used to force immediate exit from a loop, but not from a program. (To leave a program, the exit command is used.) After the break command is executed, control starts after the done keyword. The break command causes an exit from the innermost loop, so if you have nested loops, the break command takes a number as an argument, allowing you to break out of a specific outer loop. If you are nested in three loops, the outermost loop is loop number 3, the next nested loop is loop number 2, and the innermost nested loop is loop number 1. The break is useful for exiting from an infinite loop.

FORMAT


break [n]


Example 14.48.

   #!/bin/bash

   # Scriptname: loopbreak



1   while true; do

2       echo Are you ready to move on\?

        read answer

3       if [[  "$answer"  ==  [Yy] ]]

        then




EXPLANATION

  1. The true command is a UNIX/Linux command that always exits with 0 status. It is often used to start an infinite loop. It is okay to put the do statement on the same line as the while command, as long as a semicolon separates them. The body of the loop is entered.

  2. The user is asked for input. The user's input is assigned to the variable answer.

  3. If $answer evaluates to Y or y , control goes to line 4.

  4. The break command is executed, the loop is exited, and control goes to line 7. The line Here we are is printed. Until the user answers with a Y or y , the program will continue to ask for input. This could go on forever!

  5. If the test fails in line 3, the else commands are executed. When the body of the loop ends at the done keyword, control starts again at the top of the while at line 1.

  6. This is the end of the loop body.

  7. Control starts here after the break command is executed.

The continue Command

The continue command returns control to the top of the loop if some condition becomes true. All commands below the continue will be ignored. If nested within a number of loops, the continue command returns control to the innermost loop. If a number is given as its argument, control can then be started at the top of any loop. If you are nested in three loops, the outermost loop is loop number 3, the next nested loop is loop number 2, and the innermost nested loop is loop number 1.[4]

[4] If the continue command is given a number higher than the number of loops, the loop exits.

FORMAT


continue [n]


Example 14.49.

(The mailing List)

    $ cat mail_list

    ernie

    john

    richard

    melanie

    greg

    robin



(The Script)

    #!/bin/bash

    # Scriptname: mailem

    # Purpose: To send a list



        else

4            mail $name < memo

        fi

5   done


EXPLANATION

  1. After command substitution, $(cat mail_list) or `cat mail_list`, the for loop will iterate through the list of names from the file called mail_list.

  2. If the name matches richard, the continue command is executed and control goes back to top of the loop where the loop expression is evaluated. Because richard has already been shifted off the list, the next user, melanie, will be assigned to the variable name. Old style: if [ "$name" = richard ] ; then

  3. The continue command returns control to the top of the loop, skipping any commands in the rest of the loop body.

  4. All users in the list, except richard, will be mailed a copy of the file memo.

  5. This is the end of the loop body.

Nested Loops and Loop Control

When you are using nested loops, the break and continue commands can be given a numeric, integer argument so that control can go from the inner loop to an outer loop.

Example 14.50.

(The Script)

    #!/bin/bash

    # Scriptname: months





(The Output)

Processing the month of Jan. OK?

Processing the month of Feb. OK? y

Process week 1 of Feb? y

Now processing  week 1 of Feb.

Done processing...

Processing the month of Feb. OK? y

Process week 2 of Feb? y

Now processing week 2 of Feb.

Done processing...

Processing the month of Feb. OK? n

Processing the month of Mar. OK? n

Processing the month of Apr. OK? n

Processing the month of May. OK? n


EXPLANATION

  1. The outer for loop is started. The first time in the loop, Jan is assigned to month.

  2. The inner for loop starts. The first time in this loop, 1 is assigned to week. The inner loop iterates completely before going back to the outer loop.

  3. If the user enters either an n or presses Enter, line 4 is executed.

  4. The continue command with an argument of 2 starts control at the top of the second outermost loop. The continue without an argument returns control to the top of the innermost loop.

  5. Control is returned to the innermost for loop.

  6. This done terminates the innermost loop.

  7. This done terminates the outermost loop.

14.6.7 I/O Redirection and Subshells

Input can be piped or redirected to a loop from a file. Output can also be piped or redirected to a file from a loop. The shell starts a subshell to handle I/O redirection and pipes. Any variables defined within the loop will not be known to the rest of the script when the loop terminates.

Redirecting the Output of a Loop to a File

Output from a bash loop can be sent to a file rather than to the screen. See Example 14.51.

Example 14.51.

(The Command Line)

1   $ cat memo

    abc

    def

    ghi



(The Script)

    #!/bin/bash

    # Program name: numberit

    # Put line numbers on all lines of memo

2   if (( $# <  1 ))

    then

3       echo "Usage: $0 filename " >&2

        exit 1

    fi

4   count=1                      # Initialize count

5   cat $1 | while read line

    # Input is coming from file provided at command line

    do

6      ((count == 1)) && echo "Processing file $1..." > /dev/tty

7      echo -e "$count\t$line"

8      let count+=1

9   done > tmp$$              # Output is going to a temporary file

10  mv tmp$$ $1



(The Command Line)

11  $ numberit memo

    Processing file memo



12  $ cat memo

    1   abc

    2   def

    3   ghi


EXPLANATION

  1. The contents of file memo are displayed.

  2. If the user did not provide a command-line argument when running this script, the number of arguments ($#) will be less than one and the error message appears.

  3. The usage message is sent to stderr (>&2) if the number of arguments is less than 1.

  4. The count variable is assigned the value 1.

  5. The UNIX/Linux cat command displays the contents of the filename stored in $1, and the output is piped to the while loop. The read command is assigned the first line of the file the first time in the loop, the second line of the file the next time through the loop, and so forth. The read command returns a 0 exit status if it is successful in reading input and 1 if it fails.

  6. If the value of count is 1, the echo command is executed and its output is sent to /dev/tty , the screen.

  7. The echo command prints the value of count, followed by the line in the file.

  8. The count is incremented by one.

  9. The output of this entire loop, each line of the file in $1, is redirected to the file tmp$$, with the exception of the first line of the file, which is redirected to the terminal, /dev/tty.[a]

    [a] $$ expands to the PID number of the current shell. By appending this number to the filename, the filename is made unique.

  10. The tmp file is renamed to the filename assigned to $1.

  11. The program is executed. The file to be processed is called memo.

  12. The file memo is displayed after the script has finished, demonstrating that line numbers have been prepended to each line.

Piping the Output of a Loop to a UNIX/Linux Command

Output can be either piped to another command(s) or redirected to a file.

Example 14.52.

(The Script)

    #!/bin/bash

1   for i in 7 9 2 3 4  5

2   do

        echo  $i

3   done | sort –n



(The Output)

2

3

4

5

7

9


EXPLANATION

  1. The for loop iterates through a list of unsorted numbers.

  2. In the body of the loop, the numbers are printed. This output will be piped into the UNIX/Linux sort command, a numerical sort.

  3. The pipe is created after the done keyword. The loop is run in a subshell.

14.6.8 Running Loops in the Background

Loops can be executed to run in the background. The program can continue without waiting for the loop to finish processing.

Example 14.53.

(The Script)

    #!/bin/bash

1   for person in bob jim joe sam

    do

2       mail $person < memo

3   done &


EXPLANATION

  1. The for loop shifts through each of the names in the wordlist: bob, jim, joe, and sam. Each of the names is assigned to the variable person, in turn.

  2. In the body of the loop, each person is sent the contents of the memo file.

  3. The ampersand at the end of the done keyword causes the loop to be executed in the background. The program will continue to run while the loop is executing.

14.6.9 The IFS and Loops

The shell's internal field separator (IFS) evaluates to spaces, tabs, and the newline character. It is used as a word (token) separator for commands that parse lists of words, such as read, set, and for. It can be reset by the user if a different separator will be used in a list. Before changing its value, it is a good idea to save the original value of the IFS in another variable. Then it is easy to return to its default value, if needed.

Example 14.54.

(The Script )

    #/bin/bash

    # Scriptname: runit2

    # IFS is the internal field separator and defaults to

    # spaces, tabs, and newlines.

    # In this script it is changed to a colon.



1   names=Tom:Dick:Harry:John

2   oldifs="$IFS"           # Save the original value of IFS



3   IFS=":"



4   for persons in $names

    do

5       echo  Hi $persons

    done



6   IFS="$oldifs"           # Reset the IFS to old value



7   set Jill Jane Jolene    # Set positional parameters

8   for girl in $*

    do

9       echo Howdy $girl

    done



(The Output)

5   Hi Tom

    Hi Dick

    Hi Harry

    Hi John

9   Howdy Jill

    Howdy Jane

    Howdy Jolene


EXPLANATION

  1. The names variable is set to the string Tom:Dick:Harry:John. Each of the words is separated by a colon.

  2. The value of IFS, whitespace, is assigned to another variable, oldifs. Because the value of the IFS is whitespace, it must be quoted to preserve it.

  3. The IFS is assigned a colon. Now the colon is used to separate words.

  4. After variable substitution, the for loop will iterate through each of the names, using the colon as the internal field separator between the words.

  5. Each of the names in the wordlist is displayed.

  6. The IFS is reassigned its original value stored in oldifs.

  7. The positional parameters are set. $1 is assigned Jill, $2 is assigned Jane, and $3 is assigned Jolene.

  8. $* evaluates to all the positional parameters, Jill, Jane, and Jolene. The for loop assigns each of the names to the girl variable, in turn, through each iteration of the loop.

  9. Each of the names in the parameter list is displayed.

    Previous Section  < Day Day Up >  Next Section