Chapter 11. Loops and Branches --11.1 Loops

370阅读 1评论2013-12-26 wuxiaobo_2009
分类:LINUX


点击(此处)折叠或打开

  1. 11.1. Loops

  2. A loop is a block of code that iterates [1] a list of commands as long as the loop control condition is true.

  3. for loops

  4. for arg in [list]

  5.     This is the basic looping construct. It differs significantly from its C counterpart.

  6.     for arg in [list]
  7.     do
  8.      command(s)...
  9.     done

  10.     Note

  11.     The argument list may contain wild cards.

  12.     If do is on same line as for, there needs to be a semicolon after list.

  13.     for arg in [list] ; do

  14.     #!/bin/bash
  15.     # Listing the planets.

  16.     for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto
  17.     do
  18.       echo $planet # Each planet on a separate line.
  19.     done

  20.     echo; echo

  21.     for planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto"
  22.         # All planets on same line.
  23.         # Entire 'list' enclosed in quotes creates a single variable.
  24.         # Why? Whitespace incorporated into the variable.
  25.     do
  26.       echo $planet
  27.     done

  28.     echo; echo "Whoops! Pluto is no longer a planet!"

  29.     exit 0

  30.     #!/bin/bash
  31.     # Planets revisited.

  32.     # Associate the name of each planet with its distance from the sun.

  33.     for planet in "Mercury 36" "Venus 67" "Earth 93" "Mars 142" "Jupiter 483"
  34.     do
  35.       set -- $planet # Parses variable "planet"
  36.                       #+ and sets positional parameters.
  37.       # The "--" prevents nasty surprises if $planet is null or
  38.       #+ begins with a dash.

  39.       # May need to save original positional parameters,
  40.       #+ since they get overwritten.
  41.       # One way of doing this is to use an array,
  42.       # original_params=("$@")

  43.       echo "$1 $2,000,000 miles from the sun"
  44.       #-------two tabs---concatenate zeroes onto parameter $2
  45.     done

  46.     # (Thanks, S.C., for additional clarification.)

  47.     exit 0

  48.     A variable may supply the [list] in a for loop.

  49.     Example 11-3. Fileinfo: operating on a file list contained in a variable

  50.     #!/bin/bash
  51.     # fileinfo.sh

  52.     FILES="/usr/sbin/accept
  53.     /usr/sbin/pwck
  54.     /usr/sbin/chroot
  55.     /usr/bin/fakefile
  56.     /sbin/badblocks
  57.     /sbin/ypbind" # List of files you are curious about.
  58.                       # Threw in a dummy file, /usr/bin/fakefile.

  59.     echo

  60.     for file in $FILES
  61.     do

  62.       if [ ! -e "$file" ] # Check if file exists.
  63.       then
  64.         echo "$file does not exist."; echo
  65.         continue # On to next.
  66.        fi

  67.       ls -l $file | awk '{ print $8 " file size: " $5 }' # Print 2 fields.
  68.       whatis `basename $file` # File info.
  69.       # Note that the whatis database needs to have been set up for this to work.
  70.       # To do this, as root run /usr/bin/makewhatis.
  71.       echo
  72.     done

  73.     exit 0

  74.     If the [list] in a for loop contains wild cards (* and ?) used in filename expansion, then globbing takes place.

  75.     Example 11-4. Operating on files with a for loop

  76.     #!/bin/bash
  77.     # list-glob.sh: Generating [list] in a for-loop, using "globbing" ...
  78.     # Globbing = filename expansion.

  79.     echo

  80.     for file in *
  81.     # ^ Bash performs filename expansion
  82.     #+ on expressions that globbing recognizes.
  83.     do
  84.       ls -l "$file" # Lists all files in $PWD (current directory).
  85.       # Recall that the wild card character "*" matches every filename,
  86.       #+ however, in "globbing," it doesn't match dot-files.

  87.       # If the pattern matches no file, it is expanded to itself.
  88.       # To prevent this, set the nullglob option
  89.       #+ (shopt -s nullglob).
  90.       # Thanks, S.C.
  91.     done

  92.     echo; echo

  93.     for file in [jx]*
  94.     do
  95.       rm -f $file # Removes only files beginning with "j" or "x" in $PWD.
  96.       echo "Removed file \"$file\"".
  97.     done

  98.     echo

  99.     exit 0

  100.     Omitting the in [list] part of a for loop causes the loop to operate on $@ -- the positional parameters. A particularly clever illustration of this is Example A-15. See also Example 15-17.

  101.     Example 11-5. Missing in [list] in a for loop

  102.     #!/bin/bash

  103.     # Invoke this script both with and without arguments,
  104.     #+ and see what happens.

  105.     for a
  106.     do
  107.      echo -n "$a "
  108.     done

  109.     # The 'in list' missing, therefore the loop operates on '$@'
  110.     #+ (command-line argument list, including whitespace).

  111.     echo

  112.     exit 0

  113.     It is possible to use command substitution to generate the [list] in a for loop. See also Example 16-54, Example 11-10 and Example 16-48.

  114.     Example 11-6. Generating the [list] in a for loop with command substitution

  115.     #!/bin/bash
  116.     # for-loopcmd.sh: for-loop with [list]
  117.     #+ generated by command substitution.

  118.     NUMBERS="9 7 3 8 37.53"

  119.     for number in `echo $NUMBERS` # for number in 9 7 3 8 37.53
  120.     do
  121.       echo -n "$number "
  122.     done

  123.     echo
  124.     exit 0

  125.     Here is a somewhat more complex example of using command substitution to create the [list].

  126.     Example 11-7. A grep replacement for binary files

  127.     #!/bin/bash
  128.     # bin-grep.sh: Locates matching strings in a binary file.

  129.     # A "grep" replacement for binary files.
  130.     # Similar effect to "grep -a"

  131.     E_BADARGS=65
  132.     E_NOFILE=66

  133.     if [ $# -ne 2 ]
  134.     then
  135.       echo "Usage: `basename $0` search_string filename"
  136.       exit $E_BADARGS
  137.     fi

  138.     if [ ! -f "$2" ]
  139.     then
  140.       echo "File \"$2\" does not exist."
  141.       exit $E_NOFILE
  142.     fi


  143.     IFS=$'\012' # Per suggestion of Anton Filippov.
  144.                       # was: IFS="\n"
  145.     for word in $( strings "$2" | grep "$1" )
  146.     # The "strings" command lists strings in binary files.
  147.     # Output then piped to "grep", which tests for desired string.
  148.     do
  149.       echo $word
  150.     done

  151.     # As S.C. points out, lines 23 - 30 could be replaced with the simpler
  152.     # strings "$2" | grep "$1" | tr -s "$IFS" '[\n*]'


  153.     # Try something like "./bin-grep.sh mem /bin/ls"
  154.     #+ to exercise this script.

  155.     exit 0

  156.     More of the same.

  157.     Example 11-8. Listing all users on the system

  158.     #!/bin/bash
  159.     # userlist.sh

  160.     PASSWORD_FILE=/etc/passwd
  161.     n=1 # User number

  162.     for name in $(awk 'BEGIN{FS=":"}{print $1}' < "$PASSWORD_FILE" )
  163.     # Field separator = : ^^^^^^
  164.     # Print first field ^^^^^^^^
  165.     # Get input from password file /etc/passwd ^^^^^^^^^^^^^^^^^
  166.     do
  167.       echo "USER #$n = $name"
  168.       let "n += 1"
  169.     done


  170.     # USER #1 = root
  171.     # USER #2 = bin
  172.     # USER #3 = daemon
  173.     # ...
  174.     # USER #33 = bozo

  175.     exit $?

  176.     # Discussion:
  177.     # ----------
  178.     # How is it that an ordinary user, or a script run by same,
  179.     #+ can read /etc/passwd? (Hint: Check the /etc/passwd file permissions.)
  180.     # Is this a security hole? Why or why not?

  181.     Yet another example of the [list] resulting from command substitution.

  182.     Example 11-9. Checking all the binaries in a directory for authorship

  183.     #!/bin/bash
  184.     # findstring.sh:
  185.     # Find a particular string in the binaries in a specified directory.

  186.     directory=/usr/bin/
  187.     fstring="Free Software Foundation" # See which files come from the FSF.

  188.     for file in $( find $directory -type f -name '*' | sort )
  189.     do
  190.       strings -f $file | grep "$fstring" | sed -e "s%$directory%%"
  191.       # In the "sed" expression,
  192.       #+ it is necessary to substitute for the normal "/" delimiter
  193.       #+ because "/" happens to be one of the characters filtered out.
  194.       # Failure to do so gives an error message. (Try it.)
  195.     done

  196.     exit $?

  197.     # Exercise (easy):
  198.     # ---------------
  199.     # Convert this script to take command-line parameters
  200.     #+ for $directory and $fstring.

  201.     A final example of [list] / command substitution, but this time the "command" is a function.

  202.     generate_list ()
  203.     {
  204.       echo "one two three"
  205.     }

  206.     for word in $(generate_list) # Let "word" grab output of function.
  207.     do
  208.       echo "$word"
  209.     done

  210.     # one
  211.     # two
  212.     # three

  213.     The output of a for loop may be piped to a command or commands.

  214.     Example 11-10. Listing the symbolic links in a directory

  215.     #!/bin/bash
  216.     # symlinks.sh: Lists symbolic links in a directory.


  217.     directory=${1-`pwd`}
  218.     # Defaults to current working directory,
  219.     #+ if not otherwise specified.
  220.     # Equivalent to code block below.
  221.     # ----------------------------------------------------------
  222.     # ARGS=1 # Expect one command-line argument.
  223.     #
  224.     # if [ $# -ne "$ARGS" ] # If not 1 arg...
  225.     # then
  226.     # directory=`pwd` # current working directory
  227.     # else
  228.     # directory=$1
  229.     # fi
  230.     # ----------------------------------------------------------

  231.     echo "symbolic links in directory \"$directory\""

  232.     for file in "$( find $directory -type l )" # -type l = symbolic links
  233.     do
  234.       echo "$file"
  235.     done | sort # Otherwise file list is unsorted.
  236.     # Strictly speaking, a loop isn't really necessary here,
  237.     #+ since the output of the "find" command is expanded into a single word.
  238.     # However, it's easy to understand and illustrative this way.

  239.     # As Dominik 'Aeneas' Schnitzer points out,
  240.     #+ failing to quote $( find $directory -type l )
  241.     #+ will choke on filenames with embedded whitespace.
  242.     # containing whitespace.

  243.     exit 0


  244.     # --------------------------------------------------------
  245.     # Jean Helou proposes the following alternative:

  246.     echo "symbolic links in directory \"$directory\""
  247.     # Backup of the current IFS. One can never be too cautious.
  248.     OLDIFS=$IFS
  249.     IFS=:

  250.     for file in $(find $directory -type l -printf "%p$IFS")
  251.     do # ^^^^^^^^^^^^^^^^
  252.            echo "$file"
  253.            done|sort

  254.     # And, James "Mike" Conley suggests modifying Helou's code thusly:

  255.     OLDIFS=$IFS
  256.     IFS='' # Null IFS means no word breaks
  257.     for file in $( find $directory -type l )
  258.     do
  259.       echo $file
  260.       done | sort

  261.     # This works in the "pathological" case of a directory name having
  262.     #+ an embedded colon.
  263.     # "This also fixes the pathological case of the directory name having
  264.     #+ a colon (or space in earlier example) as well."

  265.     The stdout of a loop may be redirected to a file, as this slight modification to the previous example shows.

  266.     Example 11-11. Symbolic links in a directory, saved to a file

  267.     #!/bin/bash
  268.     # symlinks.sh: Lists symbolic links in a directory.

  269.     OUTFILE=symlinks.list # save-file

  270.     directory=${1-`pwd`}
  271.     # Defaults to current working directory,
  272.     #+ if not otherwise specified.


  273.     echo "symbolic links in directory \"$directory\"" > "$OUTFILE"
  274.     echo "---------------------------" >> "$OUTFILE"

  275.     for file in "$( find $directory -type l )" # -type l = symbolic links
  276.     do
  277.       echo "$file"
  278.     done | sort >> "$OUTFILE" # stdout of loop
  279.     # ^^^^^^^^^^^^^ redirected to save file.

  280.     # echo "Output file = $OUTFILE"

  281.     exit $?

  282.     There is an alternative syntax to a for loop that will look very familiar to C programmers. This requires double parentheses.

  283.     Example 11-12. A C-style for loop

  284.     #!/bin/bash
  285.     # Multiple ways to count up to 10.

  286.     echo

  287.     # Standard syntax.
  288.     for a in 1 2 3 4 5 6 7 8 9 10
  289.     do
  290.       echo -n "$a "
  291.     done

  292.     echo; echo

  293.     # +==========================================+

  294.     # Using "seq" ...
  295.     for a in `seq 10`
  296.     do
  297.       echo -n "$a "
  298.     done

  299.     echo; echo

  300.     # +==========================================+

  301.     # Using brace expansion ...
  302.     # Bash, version 3+.
  303.     for a in {1..10}
  304.     do
  305.       echo -n "$a "
  306.     done

  307.     echo; echo

  308.     # +==========================================+

  309.     # Now, let's do the same, using C-like syntax.

  310.     LIMIT=10

  311.     for ((a=1; a <= LIMIT ; a++)) # Double parentheses, and naked "LIMIT"
  312.     do
  313.       echo -n "$a "
  314.     done # A construct borrowed from ksh93.

  315.     echo; echo

  316.     # +=========================================================================+

  317.     # Let's use the C "comma operator" to increment two variables simultaneously.

  318.     for ((a=1, b=1; a <= LIMIT ; a++, b++))
  319.     do # The comma concatenates operations.
  320.       echo -n "$a-$b "
  321.     done

  322.     echo; echo

  323.     exit 0

  324.     See also Example 27-16, Example 27-17, and Example A-6.

  325.     ---

  326.     Now, a for loop used in a "real-life" context.

  327.     Example 11-13. Using efax in batch mode

  328.     #!/bin/bash
  329.     # Faxing (must have 'efax' package installed).

  330.     EXPECTED_ARGS=2
  331.     E_BADARGS=85
  332.     MODEM_PORT="/dev/ttyS2" # May be different on your machine.
  333.     # ^^^^^ PCMCIA modem card default port.

  334.     if [ $# -ne $EXPECTED_ARGS ]
  335.     # Check for proper number of command-line args.
  336.     then
  337.        echo "Usage: `basename $0` phone# text-file"
  338.        exit $E_BADARGS
  339.     fi


  340.     if [ ! -f "$2" ]
  341.     then
  342.       echo "File $2 is not a text file."
  343.       # File is not a regular file, or does not exist.
  344.       exit $E_BADARGS
  345.     fi
  346.       

  347.     fax make $2 # Create fax-formatted files from text files.

  348.     for file in $(ls $2.0*) # Concatenate the converted files.
  349.                              # Uses wild card (filename "globbing")
  350.                  #+ in variable list.
  351.     do
  352.       fil="$fil $file"
  353.     done

  354.     efax -d "$MODEM_PORT" -t "T$1" $fil # Finally, do the work.
  355.     # Trying adding -o1 if above line fails.


  356.     # As S.C. points out, the for-loop can be eliminated with
  357.     # efax -d /dev/ttyS2 -o1 -t "T$1" $2.0*
  358.     #+ but it's not quite as instructive [grin].

  359.     exit $? # Also, efax sends diagnostic messages to stdout.

  360. while

  361.     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.

  362.     while [ condition ]
  363.     do
  364.      command(s)...
  365.     done

  366.     The bracket construct in a while loop is nothing more than our old friend, the test brackets used in an if/then test. In fact, a while loop can legally use the more versatile double-brackets construct (while [[ condition ]]).

  367.     As is the case with for loops, placing the do on the same line as the condition test requires a semicolon.

  368.     while [ condition ] ; do

  369.     Note that the test brackets are not mandatory in a while loop. See, for example, the getopts construct.

  370.     Example 11-14. Simple while loop

  371.     #!/bin/bash

  372.     var0=0
  373.     LIMIT=10

  374.     while [ "$var0" -lt "$LIMIT" ]
  375.     # ^ ^
  376.     # Spaces, because these are "test-brackets" . . .
  377.     do
  378.       echo -n "$var0 " # -n suppresses newline.
  379.       # ^ Space, to separate printed out numbers.

  380.       var0=`expr $var0 + 1` # var0=$(($var0+1)) also works.
  381.                               # var0=$((var0 + 1)) also works.
  382.                               # let "var0 += 1" also works.
  383.     done # Various other methods also work.

  384.     echo

  385.     exit 0

  386.     Example 11-15. Another while loop

  387.     #!/bin/bash

  388.     echo
  389.                                    # Equivalent to:
  390.     while [ "$var1" != "end" ] # while test "$var1" != "end"
  391.     do
  392.       echo "Input variable #1 (end to exit) "
  393.       read var1 # Not 'read $var1' (why?).
  394.       echo "variable #1 = $var1" # Need quotes because of "#" . . .
  395.       # If input is 'end', echoes it here.
  396.       # Does not test for termination condition until top of loop.
  397.       echo
  398.     done

  399.     exit 0

  400.     A while loop may have multiple conditions. Only the final condition determines when the loop terminates. This necessitates a slightly different loop syntax, however.

  401.     Example 11-16. while loop with multiple conditions

  402.     #!/bin/bash

  403.     var1=unset
  404.     previous=$var1

  405.     while echo "previous-variable = $previous"
  406.           echo
  407.           previous=$var1
  408.           [ "$var1" != end ] # Keeps track of what $var1 was previously.
  409.           # Four conditions on *while*, but only the final one controls loop.
  410.           # The *last* exit status is the one that counts.
  411.     do
  412.     echo "Input variable #1 (end to exit) "
  413.       read var1
  414.       echo "variable #1 = $var1"
  415.     done

  416.     # Try to figure out how this all works.
  417.     # It's a wee bit tricky.

  418.     exit 0

  419.     As with a for loop, a while loop may employ C-style syntax by using the double-parentheses construct (see also Example 8-5).

  420.     Example 11-17. C-style syntax in a while loop

  421.     #!/bin/bash
  422.     # wh-loopc.sh: Count to 10 in a "while" loop.

  423.     LIMIT=10 # 10 iterations.
  424.     a=1

  425.     while [ "$a" -le $LIMIT ]
  426.     do
  427.       echo -n "$a "
  428.       let "a+=1"
  429.     done # No surprises, so far.

  430.     echo; echo

  431.     # +=================================================================+

# Now, we'll 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,
    do                       #+ and no "$" preceding variables.
      echo -n "$a "
      ((a += 1))             # let "a+=1"
      # Yes, indeed.
      # Double parentheses permit incrementing a variable with C-like syntax.
    done

    echo

    # C and Java programmers can feel right at home in Bash.

    exit 0

    Inside its test brackets, a while loop can call a function.

    t=0

    condition ()
    {
      ((t++))

      if [ $t -lt 5 ]
      then
        return 0  # true
      else
        return 1  # false
      fi
    }

    while condition
    #     ^^^^^^^^^
    #     Function call -- four loop iterations.
    do
      echo "Still going: t = $t"
    done

    # Still going: t = 1
    # Still going: t = 2
    # Still going: t = 3
    # Still going: t = 4

    Similar to the if-test construct, a while loop can omit the test brackets.

    while condition
    do
       command(s) ...
    done

    By coupling the power of the read command with a while loop, we get the handy while read construct, useful for reading and parsing files.

    cat $filename |   # Supply input from a file.
    while read line   # As long as there is another line to read ...
    do
      ...
    done

    # =========== Snippet from "sd.sh" example script ========== #

      while read value   # Read one data point at a time.
      do
        rt=$(echo "scale=$SC; $rt + $value" | bc)
        (( ct++ ))
      done

      am=$(echo "scale=$SC; $rt / $ct" | bc)

      echo $am; return $ct   # This function "returns" TWO values!
      #  Caution: This little trick will not work if $ct > 255!
      #  To handle a larger number of data points,
      #+ simply comment out the "return $ct" above.
    } <"$datafile"   # Feed in data file.

    Note    

    A while loop may have its stdin redirected to a file by a < at its end.

    A while loop may have its stdin supplied by a pipe.
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(s)...
    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 loops, placing the do on the same line as the condition test requires a semicolon.

    until [ condition-is-true ] ; do

    Example 11-18. until loop

    #!/bin/bash

    END_CONDITION=end

    until [ "$var1" = "$END_CONDITION" ]
    # Tests condition here, at top of loop.
    do
      echo "Input variable #1 "
      echo "($END_CONDITION to exit)"
      read var1
      echo "variable #1 = $var1"
      echo
    done  

    #                     ---                        #

    #  As with "for" and "while" loops,
    #+ an "until" loop permits C-like test constructs.

    LIMIT=10
    var=0

    until (( var > LIMIT ))
    do  # ^^ ^     ^     ^^   No brackets, no $ prefixing variables.
      echo -n "$var "
      (( var++ ))
    done    # 0 1 2 3 4 5 6 7 8 9 10


    exit 0

How to choose between a for loop or a while loop or until loop? In C, you would typically use a for loop when the number of loop iterations is known beforehand. With Bash, however, the situation is fuzzier. The Bash for loop is more loosely structured and more flexible than its equivalent in other languages. Therefore, feel free to use whatever type of loop gets the job done in the simplest way.
Notes
[1]    

Iteration: Repeated execution of a command or group of commands, usually -- but not always, while a given condition holds, or until a given condition is met.



上一篇:bash 笔记 -- 10.2Parameter Substitution
下一篇:Nested Loops

文章评论