Appendix A. 捐献的脚本

这些脚本展示了一些有趣的shell编程技术, 但是它们并不适合放入本文档的文本讲解中. 不过它们还是非常有用, 运行和分析它们都是很有意思的事.

译者: 这里留给那些有能力而且有多余时间的读者来详读, 个人认为翻译这些注释有点画蛇添足.


例子 A-1. mailformat: 格式化一个e-mail消息

  1 #!/bin/bash
  2 # mail-format.sh (ver. 1.1): Format e-mail messages.
  3 
  4 # Gets rid of carets, tabs, and also folds excessively long lines.
  5 
  6 # =================================================================
  7 #                 Standard Check for Script Argument(s)
  8 ARGS=1
  9 E_BADARGS=65
 10 E_NOFILE=66
 11 
 12 if [ $# -ne $ARGS ]  # Correct number of arguments passed to script?
 13 then
 14   echo "Usage: `basename $0` filename"
 15   exit $E_BADARGS
 16 fi
 17 
 18 if [ -f "$1" ]       # Check if file exists.
 19 then
 20     file_name=$1
 21 else
 22     echo "File \"$1\" does not exist."
 23     exit $E_NOFILE
 24 fi
 25 # =================================================================
 26 
 27 MAXWIDTH=70          # Width to fold excessively long lines to.
 28 
 29 # ---------------------------------
 30 # A variable can hold a sed script.
 31 sedscript='s/^>//
 32 s/^  *>//
 33 s/^  *//
 34 s/		*//'
 35 # ---------------------------------
 36 
 37 #  Delete carets and tabs at beginning of lines,
 38 #+ then fold lines to $MAXWIDTH characters.
 39 sed "$sedscript" $1 | fold -s --width=$MAXWIDTH
 40                         #  -s option to "fold"
 41                         #+ breaks lines at whitespace, if possible.
 42 
 43 
 44 #  This script was inspired by an article in a well-known trade journal
 45 #+ extolling a 164K MS Windows utility with similar functionality.
 46 #
 47 #  An nice set of text processing utilities and an efficient
 48 #+ scripting language provide an alternative to bloated executables.
 49 
 50 exit 0


例子 A-2. rn: 一个非常简单的文件重命名工具

这个脚本是例子 12-19的一个修改版.

  1 #! /bin/bash
  2 #
  3 # Very simpleminded filename "rename" utility (based on "lowercase.sh").
  4 #
  5 #  The "ren" utility, by Vladimir Lanin (lanin@csd2.nyu.edu),
  6 #+ does a much better job of this.
  7 
  8 
  9 ARGS=2
 10 E_BADARGS=65
 11 ONE=1                     # For getting singular/plural right (see below).
 12 
 13 if [ $# -ne "$ARGS" ]
 14 then
 15   echo "Usage: `basename $0` old-pattern new-pattern"
 16   # As in "rn gif jpg", which renames all gif files in working directory to jpg.
 17   exit $E_BADARGS
 18 fi
 19 
 20 number=0                  # Keeps track of how many files actually renamed.
 21 
 22 
 23 for filename in *$1*      #Traverse all matching files in directory.
 24 do
 25    if [ -f "$filename" ]  # If finds match...
 26    then
 27      fname=`basename $filename`            # Strip off path.
 28      n=`echo $fname | sed -e "s/$1/$2/"`   # Substitute new for old in filename.
 29      mv $fname $n                          # Rename.
 30      let "number += 1"
 31    fi
 32 done   
 33 
 34 if [ "$number" -eq "$ONE" ]                # For correct grammar.
 35 then
 36  echo "$number file renamed."
 37 else 
 38  echo "$number files renamed."
 39 fi 
 40 
 41 exit 0
 42 
 43 
 44 # Exercises:
 45 # ---------
 46 # What type of files will this not work on?
 47 # How can this be fixed?
 48 #
 49 #  Rewrite this script to process all the files in a directory
 50 #+ containing spaces in their names, and to rename them,
 51 #+ substituting an underscore for each space.


例子 A-3. blank-rename: 重命名包含空白的文件名

这是上一个脚本的简化版.

  1 #! /bin/bash
  2 # blank-rename.sh
  3 #
  4 # Substitutes underscores for blanks in all the filenames in a directory.
  5 
  6 ONE=1                     # For getting singular/plural right (see below).
  7 number=0                  # Keeps track of how many files actually renamed.
  8 FOUND=0                   # Successful return value.
  9 
 10 for filename in *         #Traverse all files in directory.
 11 do
 12      echo "$filename" | grep -q " "         #  Check whether filename
 13      if [ $? -eq $FOUND ]                   #+ contains space(s).
 14      then
 15        fname=$filename                      # Strip off path.
 16        n=`echo $fname | sed -e "s/ /_/g"`   # Substitute underscore for blank.
 17        mv "$fname" "$n"                     # Do the actual renaming.
 18        let "number += 1"
 19      fi
 20 done   
 21 
 22 if [ "$number" -eq "$ONE" ]                 # For correct grammar.
 23 then
 24  echo "$number file renamed."
 25 else 
 26  echo "$number files renamed."
 27 fi 
 28 
 29 exit 0


例子 A-4. encryptedpw: 使用一个本地加密口令, 上传到一个ftp服务器.

  1 #!/bin/bash
  2 
  3 # Example "ex72.sh" modified to use encrypted password.
  4 
  5 #  Note that this is still rather insecure,
  6 #+ since the decrypted password is sent in the clear.
  7 #  Use something like "ssh" if this is a concern.
  8 
  9 E_BADARGS=65
 10 
 11 if [ -z "$1" ]
 12 then
 13   echo "Usage: `basename $0` filename"
 14   exit $E_BADARGS
 15 fi  
 16 
 17 Username=bozo           # Change to suit.
 18 pword=/home/bozo/secret/password_encrypted.file
 19 # File containing encrypted password.
 20 
 21 Filename=`basename $1`  # Strips pathname out of file name.
 22 
 23 Server="XXX"
 24 Directory="YYY"         # Change above to actual server name & directory.
 25 
 26 
 27 Password=`cruft <$pword`          # Decrypt password.
 28 #  Uses the author's own "cruft" file encryption package,
 29 #+ based on the classic "onetime pad" algorithm,
 30 #+ and obtainable from:
 31 #+ Primary-site:   ftp://ibiblio.org/pub/Linux/utils/file
 32 #+                 cruft-0.2.tar.gz [16k]
 33 
 34 
 35 ftp -n $Server <<End-Of-Session
 36 user $Username $Password
 37 binary
 38 bell
 39 cd $Directory
 40 put $Filename
 41 bye
 42 End-Of-Session
 43 # -n option to "ftp" disables auto-logon.
 44 # Note that "bell" rings 'bell' after each file transfer.
 45 
 46 exit 0


例子 A-5. copy-cd: 拷贝一个数据CD

  1 #!/bin/bash
  2 # copy-cd.sh: copying a data CD
  3 
  4 CDROM=/dev/cdrom                           # CD ROM device
  5 OF=/home/bozo/projects/cdimage.iso         # output file
  6 #       /xxxx/xxxxxxx/                     Change to suit your system.
  7 BLOCKSIZE=2048
  8 SPEED=2                                    # May use higher speed if supported.
  9 DEVICE=cdrom
 10 # DEVICE="0,0"    on older versions of cdrecord.
 11 
 12 echo; echo "Insert source CD, but do *not* mount it."
 13 echo "Press ENTER when ready. "
 14 read ready                                 # Wait for input, $ready not used.
 15 
 16 echo; echo "Copying the source CD to $OF."
 17 echo "This may take a while. Please be patient."
 18 
 19 dd if=$CDROM of=$OF bs=$BLOCKSIZE          # Raw device copy.
 20 
 21 
 22 echo; echo "Remove data CD."
 23 echo "Insert blank CDR."
 24 echo "Press ENTER when ready. "
 25 read ready                                 # Wait for input, $ready not used.
 26 
 27 echo "Copying $OF to CDR."
 28 
 29 cdrecord -v -isosize speed=$SPEED dev=$DEVICE $OF
 30 # Uses Joerg Schilling's "cdrecord" package (see its docs).
 31 # http://www.fokus.gmd.de/nthp/employees/schilling/cdrecord.html
 32 
 33 
 34 echo; echo "Done copying $OF to CDR on device $CDROM."
 35 
 36 echo "Do you want to erase the image file (y/n)? "  # Probably a huge file.
 37 read answer
 38 
 39 case "$answer" in
 40 [yY]) rm -f $OF
 41       echo "$OF erased."
 42       ;;
 43 *)    echo "$OF not erased.";;
 44 esac
 45 
 46 echo
 47 
 48 # Exercise:
 49 # Change the above "case" statement to also accept "yes" and "Yes" as input.
 50 
 51 exit 0


例子 A-6. Collatz序列

  1 #!/bin/bash
  2 # collatz.sh
  3 
  4 #  The notorious "hailstone" or Collatz series.
  5 #  -------------------------------------------
  6 #  1) Get the integer "seed" from the command line.
  7 #  2) NUMBER <--- seed
  8 #  3) Print NUMBER.
  9 #  4)  If NUMBER is even, divide by 2, or
 10 #  5)+ if odd, multiply by 3 and add 1.
 11 #  6) NUMBER <--- result 
 12 #  7) Loop back to step 3 (for specified number of iterations).
 13 #
 14 #  The theory is that every sequence,
 15 #+ no matter how large the initial value,
 16 #+ eventually settles down to repeating "4,2,1..." cycles,
 17 #+ even after fluctuating through a wide range of values.
 18 #
 19 #  This is an instance of an "iterate",
 20 #+ an operation that feeds its output back into the input.
 21 #  Sometimes the result is a "chaotic" series.
 22 
 23 
 24 MAX_ITERATIONS=200
 25 # For large seed numbers (>32000), increase MAX_ITERATIONS.
 26 
 27 h=${1:-$$}                      #  Seed
 28                                 #  Use $PID as seed,
 29                                 #+ if not specified as command-line arg.
 30 
 31 echo
 32 echo "C($h) --- $MAX_ITERATIONS Iterations"
 33 echo
 34 
 35 for ((i=1; i<=MAX_ITERATIONS; i++))
 36 do
 37 
 38 echo -n "$h	"
 39 #          ^^^^^
 40 #           tab
 41 
 42   let "remainder = h % 2"
 43   if [ "$remainder" -eq 0 ]   # Even?
 44   then
 45     let "h /= 2"              # Divide by 2.
 46   else
 47     let "h = h*3 + 1"         # Multiply by 3 and add 1.
 48   fi
 49 
 50 
 51 COLUMNS=10                    # Output 10 values per line.
 52 let "line_break = i % $COLUMNS"
 53 if [ "$line_break" -eq 0 ]
 54 then
 55   echo
 56 fi  
 57 
 58 done
 59 
 60 echo
 61 
 62 #  For more information on this mathematical function,
 63 #+ see "Computers, Pattern, Chaos, and Beauty", by Pickover, p. 185 ff.,
 64 #+ as listed in the bibliography.
 65 
 66 exit 0


例子 A-7. days-between: 计算两个日期之间天数差

  1 #!/bin/bash
  2 # days-between.sh:    Number of days between two dates.
  3 # Usage: ./days-between.sh [M]M/[D]D/YYYY [M]M/[D]D/YYYY
  4 #
  5 # Note: Script modified to account for changes in Bash 2.05b
  6 #+      that closed the loophole permitting large negative
  7 #+      integer return values.
  8 
  9 ARGS=2                # Two command line parameters expected.
 10 E_PARAM_ERR=65        # Param error.
 11 
 12 REFYR=1600            # Reference year.
 13 CENTURY=100
 14 DIY=365
 15 ADJ_DIY=367           # Adjusted for leap year + fraction.
 16 MIY=12
 17 DIM=31
 18 LEAPCYCLE=4
 19 
 20 MAXRETVAL=255         #  Largest permissable
 21                       #+ positive return value from a function.
 22 
 23 diff=                 # Declare global variable for date difference.
 24 value=                # Declare global variable for absolute value.
 25 day=                  # Declare globals for day, month, year.
 26 month=
 27 year=
 28 
 29 
 30 Param_Error ()        # Command line parameters wrong.
 31 {
 32   echo "Usage: `basename $0` [M]M/[D]D/YYYY [M]M/[D]D/YYYY"
 33   echo "       (date must be after 1/3/1600)"
 34   exit $E_PARAM_ERR
 35 }  
 36 
 37 
 38 Parse_Date ()                 # Parse date from command line params.
 39 {
 40   month=${1%%/**}
 41   dm=${1%/**}                 # Day and month.
 42   day=${dm#*/}
 43   let "year = `basename $1`"  # Not a filename, but works just the same.
 44 }  
 45 
 46 
 47 check_date ()                 # Checks for invalid date(s) passed.
 48 {
 49   [ "$day" -gt "$DIM" ] || [ "$month" -gt "$MIY" ] || [ "$year" -lt "$REFYR" ] && Param_Error
 50   # Exit script on bad value(s).
 51   # Uses "or-list / and-list".
 52   #
 53   # Exercise: Implement more rigorous date checking.
 54 }
 55 
 56 
 57 strip_leading_zero () #  Better to strip possible leading zero(s)
 58 {                     #+ from day and/or month
 59   return ${1#0}       #+ since otherwise Bash will interpret them
 60 }                     #+ as octal values (POSIX.2, sect 2.9.2.1).
 61 
 62 
 63 day_index ()          # Gauss' Formula:
 64 {                     # Days from Jan. 3, 1600 to date passed as param.
 65 
 66   day=$1
 67   month=$2
 68   year=$3
 69 
 70   let "month = $month - 2"
 71   if [ "$month" -le 0 ]
 72   then
 73     let "month += 12"
 74     let "year -= 1"
 75   fi  
 76 
 77   let "year -= $REFYR"
 78   let "indexyr = $year / $CENTURY"
 79 
 80 
 81   let "Days = $DIY*$year + $year/$LEAPCYCLE - $indexyr + $indexyr/$LEAPCYCLE + $ADJ_DIY*$month/$MIY + $day - $DIM"
 82   #  For an in-depth explanation of this algorithm, see
 83   #+ http://home.t-online.de/home/berndt.schwerdtfeger/cal.htm
 84 
 85 
 86   echo $Days
 87 
 88 }  
 89 
 90 
 91 calculate_difference ()            # Difference between to day indices.
 92 {
 93   let "diff = $1 - $2"             # Global variable.
 94 }  
 95 
 96 
 97 abs ()                             #  Absolute value
 98 {                                  #  Uses global "value" variable.
 99   if [ "$1" -lt 0 ]                #  If negative
100   then                             #+ then
101     let "value = 0 - $1"           #+ change sign,
102   else                             #+ else
103     let "value = $1"               #+ leave it alone.
104   fi
105 }
106 
107 
108 
109 if [ $# -ne "$ARGS" ]              # Require two command line params.
110 then
111   Param_Error
112 fi  
113 
114 Parse_Date $1
115 check_date $day $month $year       #  See if valid date.
116 
117 strip_leading_zero $day            #  Remove any leading zeroes
118 day=$?                             #+ on day and/or month.
119 strip_leading_zero $month
120 month=$?
121 
122 let "date1 = `day_index $day $month $year`"
123 
124 
125 Parse_Date $2
126 check_date $day $month $year
127 
128 strip_leading_zero $day
129 day=$?
130 strip_leading_zero $month
131 month=$?
132 
133 date2=$(day_index $day $month $year) # Command substitution.
134 
135 
136 calculate_difference $date1 $date2
137 
138 abs $diff                            # Make sure it's positive.
139 diff=$value
140 
141 echo $diff
142 
143 exit 0
144 #  Compare this script with
145 #+ the implementation of Gauss' Formula in a C program at:
146 #+    http://buschencrew.hypermart.net/software/datedif


例子 A-8. 构造一个"字典"

  1 #!/bin/bash
  2 # makedict.sh  [make dictionary]
  3 
  4 # Modification of /usr/sbin/mkdict script.
  5 # Original script copyright 1993, by Alec Muffett.
  6 #
  7 #  This modified script included in this document in a manner
  8 #+ consistent with the "LICENSE" document of the "Crack" package
  9 #+ that the original script is a part of.
 10 
 11 #  This script processes text files to produce a sorted list
 12 #+ of words found in the files.
 13 #  This may be useful for compiling dictionaries
 14 #+ and for lexicographic research.
 15 
 16 
 17 E_BADARGS=65
 18 
 19 if [ ! -r "$1" ]                     #  Need at least one
 20 then                                 #+ valid file argument.
 21   echo "Usage: $0 files-to-process"
 22   exit $E_BADARGS
 23 fi  
 24 
 25 
 26 # SORT="sort"                        #  No longer necessary to define options
 27                                      #+ to sort. Changed from original script.
 28 
 29 cat $* |                             # Contents of specified files to stdout.
 30         tr A-Z a-z |                 # Convert to lowercase.
 31         tr ' ' '\012' |              # New: change spaces to newlines.
 32 #       tr -cd '\012[a-z][0-9]' |    #  Get rid of everything non-alphanumeric
 33                                      #+ (original script).
 34         tr -c '\012a-z'  '\012' |    #  Rather than deleting
 35                                      #+ now change non-alpha to newlines.
 36         sort |                       # $SORT options unnecessary now.
 37         uniq |                       # Remove duplicates.
 38         grep -v '^#' |               # Delete lines beginning with a hashmark.
 39         grep -v '^$'                 # Delete blank lines.
 40 
 41 exit 0	


例子 A-9. Soundex转换

  1 #!/bin/bash
  2 # soundex.sh: Calculate "soundex" code for names
  3 
  4 # =======================================================
  5 #        Soundex script
  6 #              by
  7 #         Mendel Cooper
  8 #     thegrendel@theriver.com
  9 #       23 January, 2002
 10 #
 11 #   Placed in the Public Domain.
 12 #
 13 # A slightly different version of this script appeared in
 14 #+ Ed Schaefer's July, 2002 "Shell Corner" column
 15 #+ in "Unix Review" on-line,
 16 #+ http://www.unixreview.com/documents/uni1026336632258/
 17 # =======================================================
 18 
 19 
 20 ARGCOUNT=1                     # Need name as argument.
 21 E_WRONGARGS=70
 22 
 23 if [ $# -ne "$ARGCOUNT" ]
 24 then
 25   echo "Usage: `basename $0` name"
 26   exit $E_WRONGARGS
 27 fi  
 28 
 29 
 30 assign_value ()                #  Assigns numerical value
 31 {                              #+ to letters of name.
 32 
 33   val1=bfpv                    # 'b,f,p,v' = 1
 34   val2=cgjkqsxz                # 'c,g,j,k,q,s,x,z' = 2
 35   val3=dt                      #  etc.
 36   val4=l
 37   val5=mn
 38   val6=r
 39 
 40 # Exceptionally clever use of 'tr' follows.
 41 # Try to figure out what is going on here.
 42 
 43 value=$( echo "$1" \
 44 | tr -d wh \
 45 | tr $val1 1 | tr $val2 2 | tr $val3 3 \
 46 | tr $val4 4 | tr $val5 5 | tr $val6 6 \
 47 | tr -s 123456 \
 48 | tr -d aeiouy )
 49 
 50 # Assign letter values.
 51 # Remove duplicate numbers, except when separated by vowels.
 52 # Ignore vowels, except as separators, so delete them last.
 53 # Ignore 'w' and 'h', even as separators, so delete them first.
 54 #
 55 # The above command substitution lays more pipe than a plumber <g>.
 56 
 57 }  
 58 
 59 
 60 input_name="$1"
 61 echo
 62 echo "Name = $input_name"
 63 
 64 
 65 # Change all characters of name input to lowercase.
 66 # ------------------------------------------------
 67 name=$( echo $input_name | tr A-Z a-z )
 68 # ------------------------------------------------
 69 # Just in case argument to script is mixed case.
 70 
 71 
 72 # Prefix of soundex code: first letter of name.
 73 # --------------------------------------------
 74 
 75 
 76 char_pos=0                     # Initialize character position. 
 77 prefix0=${name:$char_pos:1}
 78 prefix=`echo $prefix0 | tr a-z A-Z`
 79                                # Uppercase 1st letter of soundex.
 80 
 81 let "char_pos += 1"            # Bump character position to 2nd letter of name.
 82 name1=${name:$char_pos}
 83 
 84 
 85 # ++++++++++++++++++++++++++ Exception Patch +++++++++++++++++++++++++++++++++
 86 #  Now, we run both the input name and the name shifted one char to the right
 87 #+ through the value-assigning function.
 88 #  If we get the same value out, that means that the first two characters
 89 #+ of the name have the same value assigned, and that one should cancel.
 90 #  However, we also need to test whether the first letter of the name
 91 #+ is a vowel or 'w' or 'h', because otherwise this would bollix things up.
 92 
 93 char1=`echo $prefix | tr A-Z a-z`    # First letter of name, lowercased.
 94 
 95 assign_value $name
 96 s1=$value
 97 assign_value $name1
 98 s2=$value
 99 assign_value $char1
100 s3=$value
101 s3=9$s3                              #  If first letter of name is a vowel
102                                      #+ or 'w' or 'h',
103                                      #+ then its "value" will be null (unset).
104 				     #+ Therefore, set it to 9, an otherwise
105 				     #+ unused value, which can be tested for.
106 
107 
108 if [[ "$s1" -ne "$s2" || "$s3" -eq 9 ]]
109 then
110   suffix=$s2
111 else  
112   suffix=${s2:$char_pos}
113 fi  
114 # ++++++++++++++++++++++ end Exception Patch +++++++++++++++++++++++++++++++++
115 
116 
117 padding=000                    # Use at most 3 zeroes to pad.
118 
119 
120 soun=$prefix$suffix$padding    # Pad with zeroes.
121 
122 MAXLEN=4                       # Truncate to maximum of 4 chars.
123 soundex=${soun:0:$MAXLEN}
124 
125 echo "Soundex = $soundex"
126 
127 echo
128 
129 #  The soundex code is a method of indexing and classifying names
130 #+ by grouping together the ones that sound alike.
131 #  The soundex code for a given name is the first letter of the name,
132 #+ followed by a calculated three-number code.
133 #  Similar sounding names should have almost the same soundex codes.
134 
135 #   Examples:
136 #   Smith and Smythe both have a "S-530" soundex.
137 #   Harrison = H-625
138 #   Hargison = H-622
139 #   Harriman = H-655
140 
141 #  This works out fairly well in practice, but there are numerous anomalies.
142 #
143 #
144 #  The U.S. Census and certain other governmental agencies use soundex,
145 #  as do genealogical researchers.
146 #
147 #  For more information,
148 #+ see the "National Archives and Records Administration home page",
149 #+ http://www.nara.gov/genealogy/soundex/soundex.html
150 
151 
152 
153 # Exercise:
154 # --------
155 # Simplify the "Exception Patch" section of this script.
156 
157 exit 0


例子 A-10. "Game of Life"

  1 #!/bin/bash
  2 # life.sh: "Life in the Slow Lane"
  3 # Version 2: Patched by Daniel Albers
  4 #+           to allow non-square grids as input.
  5 
  6 # ##################################################################### #
  7 # This is the Bash script version of John Conway's "Game of Life".      #
  8 # "Life" is a simple implementation of cellular automata.               #
  9 # --------------------------------------------------------------------- #
 10 # On a rectangular grid, let each "cell" be either "living" or "dead".  #
 11 # Designate a living cell with a dot, and a dead one with a blank space.#
 12 #  Begin with an arbitrarily drawn dot-and-blank grid,                  #
 13 #+ and let this be the starting generation, "generation 0".             #
 14 # Determine each successive generation by the following rules:          #
 15 # 1) Each cell has 8 neighbors, the adjoining cells                     #
 16 #+   left, right, top, bottom, and the 4 diagonals.                     #
 17 #                       123                                             #
 18 #                       4*5                                             #
 19 #                       678                                             #
 20 #                                                                       #
 21 # 2) A living cell with either 2 or 3 living neighbors remains alive.   #
 22 # 3) A dead cell with 3 living neighbors becomes alive (a "birth").     #
 23 SURVIVE=2                                                               #
 24 BIRTH=3                                                                 #
 25 # 4) All other cases result in a dead cell for the next generation.     #
 26 # ##################################################################### #
 27 
 28 
 29 startfile=gen0   # Read the starting generation from the file "gen0".
 30                  # Default, if no other file specified when invoking script.
 31                  #
 32 if [ -n "$1" ]   # Specify another "generation 0" file.
 33 then
 34     startfile="$1"
 35 fi  
 36 
 37 ############################################
 38 #  Abort script if "startfile" not specified
 39 #+ AND
 40 #+ "gen0" not present.
 41 
 42 E_NOSTARTFILE=68
 43 
 44 if [ ! -e "$startfile" ]
 45 then
 46   echo "Startfile \""$startfile"\" missing!"
 47   exit $E_NOSTARTFILE
 48 fi
 49 ############################################
 50 
 51 
 52 ALIVE1=.
 53 DEAD1=_
 54                  # Represent living and "dead" cells in the start-up file.
 55 
 56 #  ---------------------------------------------------------- #
 57 #  This script uses a 10 x 10 grid (may be increased,
 58 #+ but a large grid will will cause very slow execution).
 59 ROWS=10
 60 COLS=10
 61 #  Change above two variables to match grid size, if necessary.
 62 #  ---------------------------------------------------------- #
 63 
 64 GENERATIONS=10          #  How many generations to cycle through.
 65                         #  Adjust this upwards,
 66                         #+ if you have time on your hands.
 67 
 68 NONE_ALIVE=80           #  Exit status on premature bailout,
 69                         #+ if no cells left alive.
 70 TRUE=0
 71 FALSE=1
 72 ALIVE=0
 73 DEAD=1
 74 
 75 avar=                   # Global; holds current generation.
 76 generation=0            # Initialize generation count.
 77 
 78 # =================================================================
 79 
 80 
 81 let "cells = $ROWS * $COLS"
 82                         # How many cells.
 83 
 84 declare -a initial      # Arrays containing "cells".
 85 declare -a current
 86 
 87 display ()
 88 {
 89 
 90 alive=0                 # How many cells "alive" at any given time.
 91                         # Initially zero.
 92 
 93 declare -a arr
 94 arr=( `echo "$1"` )     # Convert passed arg to array.
 95 
 96 element_count=${#arr[*]}
 97 
 98 local i
 99 local rowcheck
100 
101 for ((i=0; i<$element_count; i++))
102 do
103 
104   # Insert newline at end of each row.
105   let "rowcheck = $i % COLS"
106   if [ "$rowcheck" -eq 0 ]
107   then
108     echo                # Newline.
109     echo -n "      "    # Indent.
110   fi  
111 
112   cell=${arr[i]}
113 
114   if [ "$cell" = . ]
115   then
116     let "alive += 1"
117   fi  
118 
119   echo -n "$cell" | sed -e 's/_/ /g'
120   # Print out array and change underscores to spaces.
121 done  
122 
123 return
124 
125 }
126 
127 IsValid ()                            # Test whether cell coordinate valid.
128 {
129 
130   if [ -z "$1"  -o -z "$2" ]          # Mandatory arguments missing?
131   then
132     return $FALSE
133   fi
134 
135 local row
136 local lower_limit=0                   # Disallow negative coordinate.
137 local upper_limit
138 local left
139 local right
140 
141 let "upper_limit = $ROWS * $COLS - 1" # Total number of cells.
142 
143 
144 if [ "$1" -lt "$lower_limit" -o "$1" -gt "$upper_limit" ]
145 then
146   return $FALSE                       # Out of array bounds.
147 fi  
148 
149 row=$2
150 let "left = $row * $COLS"             # Left limit.
151 let "right = $left + $COLS - 1"       # Right limit.
152 
153 if [ "$1" -lt "$left" -o "$1" -gt "$right" ]
154 then
155   return $FALSE                       # Beyond row boundary.
156 fi  
157 
158 return $TRUE                          # Valid coordinate.
159 
160 }  
161 
162 
163 IsAlive ()              # Test whether cell is alive.
164                         # Takes array, cell number, state of cell as arguments.
165 {
166   GetCount "$1" $2      # Get alive cell count in neighborhood.
167   local nhbd=$?
168 
169 
170   if [ "$nhbd" -eq "$BIRTH" ]  # Alive in any case.
171   then
172     return $ALIVE
173   fi
174 
175   if [ "$3" = "." -a "$nhbd" -eq "$SURVIVE" ]
176   then                  # Alive only if previously alive.
177     return $ALIVE
178   fi  
179 
180   return $DEAD          # Default.
181 
182 }  
183 
184 
185 GetCount ()             # Count live cells in passed cell's neighborhood.
186                         # Two arguments needed:
187 			# $1) variable holding array
188 			# $2) cell number
189 {
190   local cell_number=$2
191   local array
192   local top
193   local center
194   local bottom
195   local r
196   local row
197   local i
198   local t_top
199   local t_cen
200   local t_bot
201   local count=0
202   local ROW_NHBD=3
203 
204   array=( `echo "$1"` )
205 
206   let "top = $cell_number - $COLS - 1"    # Set up cell neighborhood.
207   let "center = $cell_number - 1"
208   let "bottom = $cell_number + $COLS - 1"
209   let "r = $cell_number / $COLS"
210 
211   for ((i=0; i<$ROW_NHBD; i++))           # Traverse from left to right. 
212   do
213     let "t_top = $top + $i"
214     let "t_cen = $center + $i"
215     let "t_bot = $bottom + $i"
216 
217 
218     let "row = $r"                        # Count center row of neighborhood.
219     IsValid $t_cen $row                   # Valid cell position?
220     if [ $? -eq "$TRUE" ]
221     then
222       if [ ${array[$t_cen]} = "$ALIVE1" ] # Is it alive?
223       then                                # Yes?
224         let "count += 1"                  # Increment count.
225       fi	
226     fi  
227 
228     let "row = $r - 1"                    # Count top row.          
229     IsValid $t_top $row
230     if [ $? -eq "$TRUE" ]
231     then
232       if [ ${array[$t_top]} = "$ALIVE1" ] 
233       then
234         let "count += 1"
235       fi	
236     fi  
237 
238     let "row = $r + 1"                    # Count bottom row.
239     IsValid $t_bot $row
240     if [ $? -eq "$TRUE" ]
241     then
242       if [ ${array[$t_bot]} = "$ALIVE1" ] 
243       then
244         let "count += 1"
245       fi	
246     fi  
247 
248   done  
249 
250 
251   if [ ${array[$cell_number]} = "$ALIVE1" ]
252   then
253     let "count -= 1"        #  Make sure value of tested cell itself
254   fi                        #+ is not counted.
255 
256 
257   return $count
258   
259 }
260 
261 next_gen ()               # Update generation array.
262 {
263 
264 local array
265 local i=0
266 
267 array=( `echo "$1"` )     # Convert passed arg to array.
268 
269 while [ "$i" -lt "$cells" ]
270 do
271   IsAlive "$1" $i ${array[$i]}   # Is cell alive?
272   if [ $? -eq "$ALIVE" ]
273   then                           #  If alive, then
274     array[$i]=.                  #+ represent the cell as a period.
275   else  
276     array[$i]="_"                #  Otherwise underscore
277    fi                            #+ (which will later be converted to space).  
278   let "i += 1" 
279 done   
280 
281 
282 # let "generation += 1"   # Increment generation count.
283 # Why was the above line commented out?
284 
285 
286 # Set variable to pass as parameter to "display" function.
287 avar=`echo ${array[@]}`   # Convert array back to string variable.
288 display "$avar"           # Display it.
289 echo; echo
290 echo "Generation $generation  -  $alive alive"
291 
292 if [ "$alive" -eq 0 ]
293 then
294   echo
295   echo "Premature exit: no more cells alive!"
296   exit $NONE_ALIVE        #  No point in continuing
297 fi                        #+ if no live cells.
298 
299 }
300 
301 
302 # =========================================================
303 
304 # main ()
305 
306 # Load initial array with contents of startup file.
307 initial=( `cat "$startfile" | sed -e '/#/d' | tr -d '\n' |\
308 sed -e 's/\./\. /g' -e 's/_/_ /g'` )
309 # Delete lines containing '#' comment character.
310 # Remove linefeeds and insert space between elements.
311 
312 clear          # Clear screen.
313 
314 echo #         Title
315 echo "======================="
316 echo "    $GENERATIONS generations"
317 echo "           of"
318 echo "\"Life in the Slow Lane\""
319 echo "======================="
320 
321 
322 # -------- Display first generation. --------
323 Gen0=`echo ${initial[@]}`
324 display "$Gen0"           # Display only.
325 echo; echo
326 echo "Generation $generation  -  $alive alive"
327 # -------------------------------------------
328 
329 
330 let "generation += 1"     # Increment generation count.
331 echo
332 
333 # ------- Display second generation. -------
334 Cur=`echo ${initial[@]}`
335 next_gen "$Cur"          # Update & display.
336 # ------------------------------------------
337 
338 let "generation += 1"     # Increment generation count.
339 
340 # ------ Main loop for displaying subsequent generations ------
341 while [ "$generation" -le "$GENERATIONS" ]
342 do
343   Cur="$avar"
344   next_gen "$Cur"
345   let "generation += 1"
346 done
347 # ==============================================================
348 
349 echo
350 
351 exit 0   # END
352 
353 
354 
355 # The grid in this script has a "boundary problem."
356 # The the top, bottom, and sides border on a void of dead cells.
357 # Exercise: Change the script to have the grid wrap around,
358 # +         so that the left and right sides will "touch,"      
359 # +         as will the top and bottom.
360 #
361 # Exercise: Create a new "gen0" file to seed this script.
362 #           Use a 12 x 16 grid, instead of the original 10 x 10 one.
363 #           Make the necessary changes to the script,
364 #+          so it will run with the altered file.
365 #
366 # Exercise: Modify this script so that it can determine the grid size
367 #+          from the "gen0" file, and set any variables necessary
368 #+          for the script to run.
369 #           This would make unnecessary any changes to variables
370 #+          in the script for an altered grid size.


例子 A-11. "Game of Life"的数据文件

  1 # This is an example "generation 0" start-up file for "life.sh".
  2 # --------------------------------------------------------------
  3 #  The "gen0" file is a 10 x 10 grid using a period (.) for live cells,
  4 #+ and an underscore (_) for dead ones. We cannot simply use spaces
  5 #+ for dead cells in this file because of a peculiarity in Bash arrays.
  6 #  [Exercise for the reader: explain this.]
  7 #
  8 # Lines beginning with a '#' are comments, and the script ignores them.
  9 __.__..___
 10 ___._.____
 11 ____.___..
 12 _._______.
 13 ____._____
 14 ..__...___
 15 ____._____
 16 ___...____
 17 __.._..___
 18 _..___..__

+++

下面的两个脚本是由多伦多大学的Mark Moraes编写的. 请参考附件文件"Moraes-COPYRIGHT", 详细的指明了授权与约定.


例子 A-12. behead: 去掉信件与新消息的头

  1 #! /bin/sh
  2 # Strips off the header from a mail/News message i.e. till the first
  3 # empty line
  4 # Mark Moraes, University of Toronto
  5 
  6 # ==> These comments added by author of this document.
  7 
  8 if [ $# -eq 0 ]; then
  9 # ==> If no command line args present, then works on file redirected to stdin.
 10 	sed -e '1,/^$/d' -e '/^[ 	]*$/d'
 11 	# --> Delete empty lines and all lines until 
 12 	# --> first one beginning with white space.
 13 else
 14 # ==> If command line args present, then work on files named.
 15 	for i do
 16 		sed -e '1,/^$/d' -e '/^[ 	]*$/d' $i
 17 		# --> Ditto, as above.
 18 	done
 19 fi
 20 
 21 # ==> Exercise: Add error checking and other options.
 22 # ==>
 23 # ==> Note that the small sed script repeats, except for the arg passed.
 24 # ==> Does it make sense to embed it in a function? Why or why not?


例子 A-13. ftpget: 通过ftp下载文件

  1 #! /bin/sh 
  2 # $Id: ftpget,v 1.2 91/05/07 21:15:43 moraes Exp $ 
  3 # Script to perform batch anonymous ftp. Essentially converts a list of
  4 # of command line arguments into input to ftp.
  5 # ==> This script is nothing but a shell wrapper around "ftp" . . .
  6 # Simple, and quick - written as a companion to ftplist 
  7 # -h specifies the remote host (default prep.ai.mit.edu) 
  8 # -d specifies the remote directory to cd to - you can provide a sequence 
  9 # of -d options - they will be cd'ed to in turn. If the paths are relative, 
 10 # make sure you get the sequence right. Be careful with relative paths - 
 11 # there are far too many symlinks nowadays.  
 12 # (default is the ftp login directory)
 13 # -v turns on the verbose option of ftp, and shows all responses from the 
 14 # ftp server.  
 15 # -f remotefile[:localfile] gets the remote file into localfile 
 16 # -m pattern does an mget with the specified pattern. Remember to quote 
 17 # shell characters.  
 18 # -c does a local cd to the specified directory
 19 # For example, 
 20 # 	ftpget -h expo.lcs.mit.edu -d contrib -f xplaces.shar:xplaces.sh \
 21 #		-d ../pub/R3/fixes -c ~/fixes -m 'fix*' 
 22 # will get xplaces.shar from ~ftp/contrib on expo.lcs.mit.edu, and put it in
 23 # xplaces.sh in the current working directory, and get all fixes from
 24 # ~ftp/pub/R3/fixes and put them in the ~/fixes directory. 
 25 # Obviously, the sequence of the options is important, since the equivalent
 26 # commands are executed by ftp in corresponding order
 27 #
 28 # Mark Moraes <moraes@csri.toronto.edu>, Feb 1, 1989 
 29 #
 30 
 31 
 32 # ==> These comments added by author of this document.
 33 
 34 # PATH=/local/bin:/usr/ucb:/usr/bin:/bin
 35 # export PATH
 36 # ==> Above 2 lines from original script probably superfluous.
 37 
 38 E_BADARGS=65
 39 
 40 TMPFILE=/tmp/ftp.$$
 41 # ==> Creates temp file, using process id of script ($$)
 42 # ==> to construct filename.
 43 
 44 SITE=`domainname`.toronto.edu
 45 # ==> 'domainname' similar to 'hostname'
 46 # ==> May rewrite this to parameterize this for general use.
 47 
 48 usage="Usage: $0 [-h remotehost] [-d remotedirectory]... [-f remfile:localfile]... \
 49 		[-c localdirectory] [-m filepattern] [-v]"
 50 ftpflags="-i -n"
 51 verbflag=
 52 set -f 		# So we can use globbing in -m
 53 set x `getopt vh:d:c:m:f: $*`
 54 if [ $? != 0 ]; then
 55 	echo $usage
 56 	exit $E_BADARGS
 57 fi
 58 shift
 59 trap 'rm -f ${TMPFILE} ; exit' 0 1 2 3 15
 60 # ==> Delete tempfile in case of abnormal exit from script.
 61 echo "user anonymous ${USER-gnu}@${SITE} > ${TMPFILE}"
 62 # ==> Added quotes (recommended in complex echoes).
 63 echo binary >> ${TMPFILE}
 64 for i in $*   # ==> Parse command line args.
 65 do
 66 	case $i in
 67 	-v) verbflag=-v; echo hash >> ${TMPFILE}; shift;;
 68 	-h) remhost=$2; shift 2;;
 69 	-d) echo cd $2 >> ${TMPFILE}; 
 70 	    if [ x${verbflag} != x ]; then
 71 	        echo pwd >> ${TMPFILE};
 72 	    fi;
 73 	    shift 2;;
 74 	-c) echo lcd $2 >> ${TMPFILE}; shift 2;;
 75 	-m) echo mget "$2" >> ${TMPFILE}; shift 2;;
 76 	-f) f1=`expr "$2" : "\([^:]*\).*"`; f2=`expr "$2" : "[^:]*:\(.*\)"`;
 77 	    echo get ${f1} ${f2} >> ${TMPFILE}; shift 2;;
 78 	--) shift; break;;
 79 	esac
 80         # ==> 'lcd' and 'mget' are ftp commands. See "man ftp" . . .
 81 done
 82 if [ $# -ne 0 ]; then
 83 	echo $usage
 84 	exit $E_BADARGS
 85         # ==> Changed from "exit 2" to conform with style standard.
 86 fi
 87 if [ x${verbflag} != x ]; then
 88 	ftpflags="${ftpflags} -v"
 89 fi
 90 if [ x${remhost} = x ]; then
 91 	remhost=prep.ai.mit.edu
 92 	# ==> Change to match appropriate ftp site.
 93 fi
 94 echo quit >> ${TMPFILE}
 95 # ==> All commands saved in tempfile.
 96 
 97 ftp ${ftpflags} ${remhost} < ${TMPFILE}
 98 # ==> Now, tempfile batch processed by ftp.
 99 
100 rm -f ${TMPFILE}
101 # ==> Finally, tempfile deleted (you may wish to copy it to a logfile).
102 
103 
104 # ==> Exercises:
105 # ==> ---------
106 # ==> 1) Add error checking.
107 # ==> 2) Add bells & whistles.

+

Antek Sawicki捐献了下面的脚本, 这个脚本非常聪明的使用了参数替换操作符, 我们在Section 9.3中讨论了参数替换操作符.


例子 A-14. password: 产生随机的8个字符的密码

  1 #!/bin/bash
  2 # May need to be invoked with  #!/bin/bash2  on older machines.
  3 #
  4 # Random password generator for Bash 2.x by Antek Sawicki <tenox@tenox.tc>,
  5 # who generously gave permission to the document author to use it here.
  6 #
  7 # ==> Comments added by document author ==>
  8 
  9 
 10 MATRIX="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 11 # ==> Password will consist of alphanumeric characters.
 12 LENGTH="8"
 13 # ==> May change 'LENGTH' for longer password.
 14 
 15 
 16 while [ "${n:=1}" -le "$LENGTH" ]
 17 # ==> Recall that := is "default substitution" operator.
 18 # ==> So, if 'n' has not been initialized, set it to 1.
 19 do
 20 	PASS="$PASS${MATRIX:$(($RANDOM%${#MATRIX})):1}"
 21 	# ==> Very clever, but tricky.
 22 
 23 	# ==> Starting from the innermost nesting...
 24 	# ==> ${#MATRIX} returns length of array MATRIX.
 25 
 26 	# ==> $RANDOM%${#MATRIX} returns random number between 1
 27 	# ==> and [length of MATRIX] - 1.
 28 
 29 	# ==> ${MATRIX:$(($RANDOM%${#MATRIX})):1}
 30 	# ==> returns expansion of MATRIX at random position, by length 1. 
 31 	# ==> See {var:pos:len} parameter substitution in Chapter 9.
 32 	# ==> and the associated examples.
 33 
 34 	# ==> PASS=... simply pastes this result onto previous PASS (concatenation).
 35 
 36 	# ==> To visualize this more clearly, uncomment the following line
 37 	#                 echo "$PASS"
 38 	# ==> to see PASS being built up,
 39 	# ==> one character at a time, each iteration of the loop.
 40 
 41 	let n+=1
 42 	# ==> Increment 'n' for next pass.
 43 done
 44 
 45 echo "$PASS"      # ==> Or, redirect to a file, as desired.
 46 
 47 exit 0

+

James R. Van Zandt捐献了这个脚本, 使用命名管道, 用他的话来说, "引用与转义的真正练习".


例子 A-15. fifo: 使用命名管道来做每日的备份

  1 #!/bin/bash
  2 # ==> Script by James R. Van Zandt, and used here with his permission.
  3 
  4 # ==> Comments added by author of this document.
  5 
  6   
  7   HERE=`uname -n`    # ==> hostname
  8   THERE=bilbo
  9   echo "starting remote backup to $THERE at `date +%r`"
 10   # ==> `date +%r` returns time in 12-hour format, i.e. "08:08:34 PM".
 11   
 12   # make sure /pipe really is a pipe and not a plain file
 13   rm -rf /pipe
 14   mkfifo /pipe       # ==> Create a "named pipe", named "/pipe".
 15   
 16   # ==> 'su xyz' runs commands as user "xyz".
 17   # ==> 'ssh' invokes secure shell (remote login client).
 18   su xyz -c "ssh $THERE \"cat >/home/xyz/backup/${HERE}-daily.tar.gz\" < /pipe"&
 19   cd /
 20   tar -czf - bin boot dev etc home info lib man root sbin share usr var >/pipe
 21   # ==> Uses named pipe, /pipe, to communicate between processes:
 22   # ==> 'tar/gzip' writes to /pipe and 'ssh' reads from /pipe.
 23 
 24   # ==> The end result is this backs up the main directories, from / on down.
 25 
 26   # ==>  What are the advantages of a "named pipe" in this situation,
 27   # ==>+ as opposed to an "anonymous pipe", with |?
 28   # ==>  Will an anonymous pipe even work here?
 29 
 30 
 31   exit 0

+

Stephane Chazelas捐献了这个脚本, 用来展示如何不使用数组来产生素数


例子 A-16. 使用模操作符来产生素数

  1 #!/bin/bash
  2 # primes.sh: Generate prime numbers, without using arrays.
  3 # Script contributed by Stephane Chazelas.
  4 
  5 #  This does *not* use the classic "Sieve of Eratosthenes" algorithm,
  6 #+ but instead uses the more intuitive method of testing each candidate number
  7 #+ for factors (divisors), using the "%" modulo operator.
  8 
  9 
 10 LIMIT=1000                    # Primes 2 - 1000
 11 
 12 Primes()
 13 {
 14  (( n = $1 + 1 ))             # Bump to next integer.
 15  shift                        # Next parameter in list.
 16 #  echo "_n=$n i=$i_"
 17  
 18  if (( n == LIMIT ))
 19  then echo $*
 20  return
 21  fi
 22 
 23  for i; do                    # "i" gets set to "@", previous values of $n.
 24 #   echo "-n=$n i=$i-"
 25    (( i * i > n )) && break   # Optimization.
 26    (( n % i )) && continue    # Sift out non-primes using modulo operator.
 27    Primes $n $@               # Recursion inside loop.
 28    return
 29    done
 30 
 31    Primes $n $@ $n            # Recursion outside loop.
 32                               # Successively accumulate positional parameters.
 33                               # "$@" is the accumulating list of primes.
 34 }
 35 
 36 Primes 1
 37 
 38 exit 0
 39 
 40 #  Uncomment lines 16 and 24 to help figure out what is going on.
 41 
 42 #  Compare the speed of this algorithm for generating primes
 43 #+ with the Sieve of Eratosthenes (ex68.sh).
 44 
 45 #  Exercise: Rewrite this script without recursion, for faster execution.

+

这是Jordi Sanfeliu的tree脚本的升级版, 由Rick Boivie编写.


例子 A-17. tree: 显示目录树

  1 #!/bin/bash
  2 # tree.sh
  3 
  4 #  Written by Rick Boivie.
  5 #  Used with permission.
  6 #  This is a revised and simplified version of a script
  7 #+ by Jordi Sanfeliu (and patched by Ian Kjos).
  8 #  This script replaces the earlier version used in
  9 #+ previous releases of the Advanced Bash Scripting Guide.
 10 
 11 # ==> Comments added by the author of this document.
 12 
 13 
 14 search () {
 15 for dir in `echo *`
 16 #  ==> `echo *` lists all the files in current working directory,
 17 #+ ==> without line breaks.
 18 #  ==> Similar effect to for dir in *
 19 #  ==> but "dir in `echo *`" will not handle filenames with blanks.
 20 do
 21   if [ -d "$dir" ] ; then # ==> If it is a directory (-d)...
 22   zz=0                    # ==> Temp variable, keeping track of directory level.
 23   while [ $zz != $1 ]     # Keep track of inner nested loop.
 24     do
 25       echo -n "| "        # ==> Display vertical connector symbol,
 26                           # ==> with 2 spaces & no line feed in order to indent.
 27       zz=`expr $zz + 1`   # ==> Increment zz.
 28     done
 29 
 30     if [ -L "$dir" ] ; then # ==> If directory is a symbolic link...
 31       echo "+---$dir" `ls -l $dir | sed 's/^.*'$dir' //'`
 32       # ==> Display horiz. connector and list directory name, but...
 33       # ==> delete date/time part of long listing.
 34     else
 35       echo "+---$dir"       # ==> Display horizontal connector symbol...
 36       # ==> and print directory name.
 37       numdirs=`expr $numdirs + 1` # ==> Increment directory count.
 38       if cd "$dir" ; then         # ==> If can move to subdirectory...
 39         search `expr $1 + 1`      # with recursion ;-)
 40         # ==> Function calls itself.
 41         cd ..
 42       fi
 43     fi
 44   fi
 45 done
 46 }
 47 
 48 if [ $# != 0 ] ; then
 49   cd $1 # move to indicated directory.
 50   #else # stay in current directory
 51 fi
 52 
 53 echo "Initial directory = `pwd`"
 54 numdirs=0
 55 
 56 search 0
 57 echo "Total directories = $numdirs"
 58 
 59 exit 0

经过Noah Friedman的授权, 他的string function脚本可以在本书中使用, 这个脚本本质上就是复制了一些C库的字符串操作函数.


例子 A-18. string functions: C风格的字符串函数

  1 #!/bin/bash
  2 
  3 # string.bash --- bash emulation of string(3) library routines
  4 # Author: Noah Friedman <friedman@prep.ai.mit.edu>
  5 # ==>     Used with his kind permission in this document.
  6 # Created: 1992-07-01
  7 # Last modified: 1993-09-29
  8 # Public domain
  9 
 10 # Conversion to bash v2 syntax done by Chet Ramey
 11 
 12 # Commentary:
 13 # Code:
 14 
 15 #:docstring strcat:
 16 # Usage: strcat s1 s2
 17 #
 18 # Strcat appends the value of variable s2 to variable s1. 
 19 #
 20 # Example:
 21 #    a="foo"
 22 #    b="bar"
 23 #    strcat a b
 24 #    echo $a
 25 #    => foobar
 26 #
 27 #:end docstring:
 28 
 29 ###;;;autoload   ==> Autoloading of function commented out.
 30 function strcat ()
 31 {
 32     local s1_val s2_val
 33 
 34     s1_val=${!1}                        # indirect variable expansion
 35     s2_val=${!2}
 36     eval "$1"=\'"${s1_val}${s2_val}"\'
 37     # ==> eval $1='${s1_val}${s2_val}' avoids problems,
 38     # ==> if one of the variables contains a single quote.
 39 }
 40 
 41 #:docstring strncat:
 42 # Usage: strncat s1 s2 $n
 43 # 
 44 # Line strcat, but strncat appends a maximum of n characters from the value
 45 # of variable s2.  It copies fewer if the value of variabl s2 is shorter
 46 # than n characters.  Echoes result on stdout.
 47 #
 48 # Example:
 49 #    a=foo
 50 #    b=barbaz
 51 #    strncat a b 3
 52 #    echo $a
 53 #    => foobar
 54 #
 55 #:end docstring:
 56 
 57 ###;;;autoload
 58 function strncat ()
 59 {
 60     local s1="$1"
 61     local s2="$2"
 62     local -i n="$3"
 63     local s1_val s2_val
 64 
 65     s1_val=${!s1}                       # ==> indirect variable expansion
 66     s2_val=${!s2}
 67 
 68     if [ ${#s2_val} -gt ${n} ]; then
 69        s2_val=${s2_val:0:$n}            # ==> substring extraction
 70     fi
 71 
 72     eval "$s1"=\'"${s1_val}${s2_val}"\'
 73     # ==> eval $1='${s1_val}${s2_val}' avoids problems,
 74     # ==> if one of the variables contains a single quote.
 75 }
 76 
 77 #:docstring strcmp:
 78 # Usage: strcmp $s1 $s2
 79 #
 80 # Strcmp compares its arguments and returns an integer less than, equal to,
 81 # or greater than zero, depending on whether string s1 is lexicographically
 82 # less than, equal to, or greater than string s2.
 83 #:end docstring:
 84 
 85 ###;;;autoload
 86 function strcmp ()
 87 {
 88     [ "$1" = "$2" ] && return 0
 89 
 90     [ "${1}" '<' "${2}" ] > /dev/null && return -1
 91 
 92     return 1
 93 }
 94 
 95 #:docstring strncmp:
 96 # Usage: strncmp $s1 $s2 $n
 97 # 
 98 # Like strcmp, but makes the comparison by examining a maximum of n
 99 # characters (n less than or equal to zero yields equality).
100 #:end docstring:
101 
102 ###;;;autoload
103 function strncmp ()
104 {
105     if [ -z "${3}" -o "${3}" -le "0" ]; then
106        return 0
107     fi
108    
109     if [ ${3} -ge ${#1} -a ${3} -ge ${#2} ]; then
110        strcmp "$1" "$2"
111        return $?
112     else
113        s1=${1:0:$3}
114        s2=${2:0:$3}
115        strcmp $s1 $s2
116        return $?
117     fi
118 }
119 
120 #:docstring strlen:
121 # Usage: strlen s
122 #
123 # Strlen returns the number of characters in string literal s.
124 #:end docstring:
125 
126 ###;;;autoload
127 function strlen ()
128 {
129     eval echo "\${#${1}}"
130     # ==> Returns the length of the value of the variable
131     # ==> whose name is passed as an argument.
132 }
133 
134 #:docstring strspn:
135 # Usage: strspn $s1 $s2
136 # 
137 # Strspn returns the length of the maximum initial segment of string s1,
138 # which consists entirely of characters from string s2.
139 #:end docstring:
140 
141 ###;;;autoload
142 function strspn ()
143 {
144     # Unsetting IFS allows whitespace to be handled as normal chars. 
145     local IFS=
146     local result="${1%%[!${2}]*}"
147  
148     echo ${#result}
149 }
150 
151 #:docstring strcspn:
152 # Usage: strcspn $s1 $s2
153 #
154 # Strcspn returns the length of the maximum initial segment of string s1,
155 # which consists entirely of characters not from string s2.
156 #:end docstring:
157 
158 ###;;;autoload
159 function strcspn ()
160 {
161     # Unsetting IFS allows whitspace to be handled as normal chars. 
162     local IFS=
163     local result="${1%%[${2}]*}"
164  
165     echo ${#result}
166 }
167 
168 #:docstring strstr:
169 # Usage: strstr s1 s2
170 # 
171 # Strstr echoes a substring starting at the first occurrence of string s2 in
172 # string s1, or nothing if s2 does not occur in the string.  If s2 points to
173 # a string of zero length, strstr echoes s1.
174 #:end docstring:
175 
176 ###;;;autoload
177 function strstr ()
178 {
179     # if s2 points to a string of zero length, strstr echoes s1
180     [ ${#2} -eq 0 ] && { echo "$1" ; return 0; }
181 
182     # strstr echoes nothing if s2 does not occur in s1
183     case "$1" in
184     *$2*) ;;
185     *) return 1;;
186     esac
187 
188     # use the pattern matching code to strip off the match and everything
189     # following it
190     first=${1/$2*/}
191 
192     # then strip off the first unmatched portion of the string
193     echo "${1##$first}"
194 }
195 
196 #:docstring strtok:
197 # Usage: strtok s1 s2
198 #
199 # Strtok considers the string s1 to consist of a sequence of zero or more
200 # text tokens separated by spans of one or more characters from the
201 # separator string s2.  The first call (with a non-empty string s1
202 # specified) echoes a string consisting of the first token on stdout. The
203 # function keeps track of its position in the string s1 between separate
204 # calls, so that subsequent calls made with the first argument an empty
205 # string will work through the string immediately following that token.  In
206 # this way subsequent calls will work through the string s1 until no tokens
207 # remain.  The separator string s2 may be different from call to call.
208 # When no token remains in s1, an empty value is echoed on stdout.
209 #:end docstring:
210 
211 ###;;;autoload
212 function strtok ()
213 {
214  :
215 }
216 
217 #:docstring strtrunc:
218 # Usage: strtrunc $n $s1 {$s2} {$...}
219 #
220 # Used by many functions like strncmp to truncate arguments for comparison.
221 # Echoes the first n characters of each string s1 s2 ... on stdout. 
222 #:end docstring:
223 
224 ###;;;autoload
225 function strtrunc ()
226 {
227     n=$1 ; shift
228     for z; do
229         echo "${z:0:$n}"
230     done
231 }
232 
233 # provide string
234 
235 # string.bash ends here
236 
237 
238 # ========================================================================== #
239 # ==> Everything below here added by the document author.
240 
241 # ==> Suggested use of this script is to delete everything below here,
242 # ==> and "source" this file into your own scripts.
243 
244 # strcat
245 string0=one
246 string1=two
247 echo
248 echo "Testing \"strcat\" function:"
249 echo "Original \"string0\" = $string0"
250 echo "\"string1\" = $string1"
251 strcat string0 string1
252 echo "New \"string0\" = $string0"
253 echo
254 
255 # strlen
256 echo
257 echo "Testing \"strlen\" function:"
258 str=123456789
259 echo "\"str\" = $str"
260 echo -n "Length of \"str\" = "
261 strlen str
262 echo
263 
264 
265 
266 # Exercise:
267 # --------
268 # Add code to test all the other string functions above.
269 
270 
271 exit 0

这个复杂的数组用例使用了md5sum检查和命令来编码目录信息, 此脚本由Michael Zick编写.


例子 A-19. 目录信息

  1 #! /bin/bash
  2 # directory-info.sh
  3 # Parses and lists directory information.
  4 
  5 # NOTE: Change lines 273 and 353 per "README" file.
  6 
  7 # Michael Zick is the author of this script.
  8 # Used here with his permission.
  9 
 10 # Controls
 11 # If overridden by command arguments, they must be in the order:
 12 #   Arg1: "Descriptor Directory"
 13 #   Arg2: "Exclude Paths"
 14 #   Arg3: "Exclude Directories"
 15 #
 16 # Environment Settings override Defaults.
 17 # Command arguments override Environment Settings.
 18 
 19 # Default location for content addressed file descriptors.
 20 MD5UCFS=${1:-${MD5UCFS:-'/tmpfs/ucfs'}}
 21 
 22 # Directory paths never to list or enter
 23 declare -a \
 24   EXCLUDE_PATHS=${2:-${EXCLUDE_PATHS:-'(/proc /dev /devfs /tmpfs)'}}
 25 
 26 # Directories never to list or enter
 27 declare -a \
 28   EXCLUDE_DIRS=${3:-${EXCLUDE_DIRS:-'(ucfs lost+found tmp wtmp)'}}
 29 
 30 # Files never to list or enter
 31 declare -a \
 32   EXCLUDE_FILES=${3:-${EXCLUDE_FILES:-'(core "Name with Spaces")'}}
 33 
 34 
 35 # Here document used as a comment block.
 36 : <<LSfieldsDoc
 37 # # # # # List Filesystem Directory Information # # # # #
 38 #
 39 #	ListDirectory "FileGlob" "Field-Array-Name"
 40 # or
 41 #	ListDirectory -of "FileGlob" "Field-Array-Filename"
 42 #	'-of' meaning 'output to filename'
 43 # # # # #
 44 
 45 String format description based on: ls (GNU fileutils) version 4.0.36
 46 
 47 Produces a line (or more) formatted:
 48 inode permissions hard-links owner group ...
 49 32736 -rw-------    1 mszick   mszick
 50 
 51 size    day month date hh:mm:ss year path
 52 2756608 Sun Apr 20 08:53:06 2003 /home/mszick/core
 53 
 54 Unless it is formatted:
 55 inode permissions hard-links owner group ...
 56 266705 crw-rw----    1    root  uucp
 57 
 58 major minor day month date hh:mm:ss year path
 59 4,  68 Sun Apr 20 09:27:33 2003 /dev/ttyS4
 60 NOTE: that pesky comma after the major number
 61 
 62 NOTE: the 'path' may be multiple fields:
 63 /home/mszick/core
 64 /proc/982/fd/0 -> /dev/null
 65 /proc/982/fd/1 -> /home/mszick/.xsession-errors
 66 /proc/982/fd/13 -> /tmp/tmpfZVVOCs (deleted)
 67 /proc/982/fd/7 -> /tmp/kde-mszick/ksycoca
 68 /proc/982/fd/8 -> socket:[11586]
 69 /proc/982/fd/9 -> pipe:[11588]
 70 
 71 If that isn't enough to keep your parser guessing,
 72 either or both of the path components may be relative:
 73 ../Built-Shared -> Built-Static
 74 ../linux-2.4.20.tar.bz2 -> ../../../SRCS/linux-2.4.20.tar.bz2
 75 
 76 The first character of the 11 (10?) character permissions field:
 77 's' Socket
 78 'd' Directory
 79 'b' Block device
 80 'c' Character device
 81 'l' Symbolic link
 82 NOTE: Hard links not marked - test for identical inode numbers
 83 on identical filesystems.
 84 All information about hard linked files are shared, except
 85 for the names and the name's location in the directory system.
 86 NOTE: A "Hard link" is known as a "File Alias" on some systems.
 87 '-' An undistingushed file
 88 
 89 Followed by three groups of letters for: User, Group, Others
 90 Character 1: '-' Not readable; 'r' Readable
 91 Character 2: '-' Not writable; 'w' Writable
 92 Character 3, User and Group: Combined execute and special
 93 '-' Not Executable, Not Special
 94 'x' Executable, Not Special
 95 's' Executable, Special
 96 'S' Not Executable, Special
 97 Character 3, Others: Combined execute and sticky (tacky?)
 98 '-' Not Executable, Not Tacky
 99 'x' Executable, Not Tacky
100 't' Executable, Tacky
101 'T' Not Executable, Tacky
102 
103 Followed by an access indicator
104 Haven't tested this one, it may be the eleventh character
105 or it may generate another field
106 ' ' No alternate access
107 '+' Alternate access
108 LSfieldsDoc
109 
110 
111 ListDirectory()
112 {
113 	local -a T
114 	local -i of=0		# Default return in variable
115 #	OLD_IFS=$IFS		# Using BASH default ' \t\n'
116 
117 	case "$#" in
118 	3)	case "$1" in
119 		-of)	of=1 ; shift ;;
120 		 * )	return 1 ;;
121 		esac ;;
122 	2)	: ;;		# Poor man's "continue"
123 	*)	return 1 ;;
124 	esac
125 
126 	# NOTE: the (ls) command is NOT quoted (")
127 	T=( $(ls --inode --ignore-backups --almost-all --directory \
128 	--full-time --color=none --time=status --sort=none \
129 	--format=long $1) )
130 
131 	case $of in
132 	# Assign T back to the array whose name was passed as $2
133 		0) eval $2=\( \"\$\{T\[@\]\}\" \) ;;
134 	# Write T into filename passed as $2
135 		1) echo "${T[@]}" > "$2" ;;
136 	esac
137 	return 0
138    }
139 
140 # # # # # Is that string a legal number? # # # # #
141 #
142 #	IsNumber "Var"
143 # # # # # There has to be a better way, sigh...
144 
145 IsNumber()
146 {
147 	local -i int
148 	if [ $# -eq 0 ]
149 	then
150 		return 1
151 	else
152 		(let int=$1)  2>/dev/null
153 		return $?	# Exit status of the let thread
154 	fi
155 }
156 
157 # # # # # Index Filesystem Directory Information # # # # #
158 #
159 #	IndexList "Field-Array-Name" "Index-Array-Name"
160 # or
161 #	IndexList -if Field-Array-Filename Index-Array-Name
162 #	IndexList -of Field-Array-Name Index-Array-Filename
163 #	IndexList -if -of Field-Array-Filename Index-Array-Filename
164 # # # # #
165 
166 : <<IndexListDoc
167 Walk an array of directory fields produced by ListDirectory
168 
169 Having suppressed the line breaks in an otherwise line oriented
170 report, build an index to the array element which starts each line.
171 
172 Each line gets two index entries, the first element of each line
173 (inode) and the element that holds the pathname of the file.
174 
175 The first index entry pair (Line-Number==0) are informational:
176 Index-Array-Name[0] : Number of "Lines" indexed
177 Index-Array-Name[1] : "Current Line" pointer into Index-Array-Name
178 
179 The following index pairs (if any) hold element indexes into
180 the Field-Array-Name per:
181 Index-Array-Name[Line-Number * 2] : The "inode" field element.
182 NOTE: This distance may be either +11 or +12 elements.
183 Index-Array-Name[(Line-Number * 2) + 1] : The "pathname" element.
184 NOTE: This distance may be a variable number of elements.
185 Next line index pair for Line-Number+1.
186 IndexListDoc
187 
188 
189 
190 IndexList()
191 {
192 	local -a LIST			# Local of listname passed
193 	local -a -i INDEX=( 0 0 )	# Local of index to return
194 	local -i Lidx Lcnt
195 	local -i if=0 of=0		# Default to variable names
196 
197 	case "$#" in			# Simplistic option testing
198 		0) return 1 ;;
199 		1) return 1 ;;
200 		2) : ;;			# Poor man's continue
201 		3) case "$1" in
202 			-if) if=1 ;;
203 			-of) of=1 ;;
204 			 * ) return 1 ;;
205 		   esac ; shift ;;
206 		4) if=1 ; of=1 ; shift ; shift ;;
207 		*) return 1
208 	esac
209 
210 	# Make local copy of list
211 	case "$if" in
212 		0) eval LIST=\( \"\$\{$1\[@\]\}\" \) ;;
213 		1) LIST=( $(cat $1) ) ;;
214 	esac
215 
216 	# Grok (grope?) the array
217 	Lcnt=${#LIST[@]}
218 	Lidx=0
219 	until (( Lidx >= Lcnt ))
220 	do
221 	if IsNumber ${LIST[$Lidx]}
222 	then
223 		local -i inode name
224 		local ft
225 		inode=Lidx
226 		local m=${LIST[$Lidx+2]}	# Hard Links field
227 		ft=${LIST[$Lidx+1]:0:1} 	# Fast-Stat
228 		case $ft in
229 		b)	((Lidx+=12)) ;;		# Block device
230 		c)	((Lidx+=12)) ;;		# Character device
231 		*)	((Lidx+=11)) ;;		# Anything else
232 		esac
233 		name=Lidx
234 		case $ft in
235 		-)	((Lidx+=1)) ;;		# The easy one
236 		b)	((Lidx+=1)) ;;		# Block device
237 		c)	((Lidx+=1)) ;;		# Character device
238 		d)	((Lidx+=1)) ;;		# The other easy one
239 		l)	((Lidx+=3)) ;;		# At LEAST two more fields
240 #  A little more elegance here would handle pipes,
241 #+ sockets, deleted files - later.
242 		*)	until IsNumber ${LIST[$Lidx]} || ((Lidx >= Lcnt))
243 			do
244 				((Lidx+=1))
245 			done
246 			;;			# Not required
247 		esac
248 		INDEX[${#INDEX[*]}]=$inode
249 		INDEX[${#INDEX[*]}]=$name
250 		INDEX[0]=${INDEX[0]}+1		# One more "line" found
251 # echo "Line: ${INDEX[0]} Type: $ft Links: $m Inode: \
252 # ${LIST[$inode]} Name: ${LIST[$name]}"
253 
254 	else
255 		((Lidx+=1))
256 	fi
257 	done
258 	case "$of" in
259 		0) eval $2=\( \"\$\{INDEX\[@\]\}\" \) ;;
260 		1) echo "${INDEX[@]}" > "$2" ;;
261 	esac
262 	return 0				# What could go wrong?
263 }
264 
265 # # # # # Content Identify File # # # # #
266 #
267 #	DigestFile Input-Array-Name Digest-Array-Name
268 # or
269 #	DigestFile -if Input-FileName Digest-Array-Name
270 # # # # #
271 
272 # Here document used as a comment block.
273 : <<DigestFilesDoc
274 
275 The key (no pun intended) to a Unified Content File System (UCFS)
276 is to distinguish the files in the system based on their content.
277 Distinguishing files by their name is just, so, 20th Century.
278 
279 The content is distinguished by computing a checksum of that content.
280 This version uses the md5sum program to generate a 128 bit checksum
281 representative of the file's contents.
282 There is a chance that two files having different content might
283 generate the same checksum using md5sum (or any checksum).  Should
284 that become a problem, then the use of md5sum can be replace by a
285 cyrptographic signature.  But until then...
286 
287 The md5sum program is documented as outputting three fields (and it
288 does), but when read it appears as two fields (array elements).  This
289 is caused by the lack of whitespace between the second and third field.
290 So this function gropes the md5sum output and returns:
291 	[0]	32 character checksum in hexidecimal (UCFS filename)
292 	[1]	Single character: ' ' text file, '*' binary file
293 	[2]	Filesystem (20th Century Style) name
294 	Note: That name may be the character '-' indicating STDIN read.
295 
296 DigestFilesDoc
297 
298 
299 
300 DigestFile()
301 {
302 	local if=0		# Default, variable name
303 	local -a T1 T2
304 
305 	case "$#" in
306 	3)	case "$1" in
307 		-if)	if=1 ; shift ;;
308 		 * )	return 1 ;;
309 		esac ;;
310 	2)	: ;;		# Poor man's "continue"
311 	*)	return 1 ;;
312 	esac
313 
314 	case $if in
315 	0) eval T1=\( \"\$\{$1\[@\]\}\" \)
316 	   T2=( $(echo ${T1[@]} | md5sum -) )
317 	   ;;
318 	1) T2=( $(md5sum $1) )
319 	   ;;
320 	esac
321 
322 	case ${#T2[@]} in
323 	0) return 1 ;;
324 	1) return 1 ;;
325 	2) case ${T2[1]:0:1} in		# SanScrit-2.0.5
326 	   \*) T2[${#T2[@]}]=${T2[1]:1}
327 	       T2[1]=\*
328 	       ;;
329 	    *) T2[${#T2[@]}]=${T2[1]}
330 	       T2[1]=" "
331 	       ;;
332 	   esac
333 	   ;;
334 	3) : ;; # Assume it worked
335 	*) return 1 ;;
336 	esac
337 
338 	local -i len=${#T2[0]}
339 	if [ $len -ne 32 ] ; then return 1 ; fi
340 	eval $2=\( \"\$\{T2\[@\]\}\" \)
341 }
342 
343 # # # # # Locate File # # # # #
344 #
345 #	LocateFile [-l] FileName Location-Array-Name
346 # or
347 #	LocateFile [-l] -of FileName Location-Array-FileName
348 # # # # #
349 
350 # A file location is Filesystem-id and inode-number
351 
352 # Here document used as a comment block.
353 : <<StatFieldsDoc
354 	Based on stat, version 2.2
355 	stat -t and stat -lt fields
356 	[0]	name
357 	[1]	Total size
358 		File - number of bytes
359 		Symbolic link - string length of pathname
360 	[2]	Number of (512 byte) blocks allocated
361 	[3]	File type and Access rights (hex)
362 	[4]	User ID of owner
363 	[5]	Group ID of owner
364 	[6]	Device number
365 	[7]	Inode number
366 	[8]	Number of hard links
367 	[9]	Device type (if inode device) Major
368 	[10]	Device type (if inode device) Minor
369 	[11]	Time of last access
370 		May be disabled in 'mount' with noatime
371 		atime of files changed by exec, read, pipe, utime, mknod (mmap?)
372 		atime of directories changed by addition/deletion of files
373 	[12]	Time of last modification
374 		mtime of files changed by write, truncate, utime, mknod
375 		mtime of directories changed by addtition/deletion of files
376 	[13]	Time of last change
377 		ctime reflects time of changed inode information (owner, group
378 		permissions, link count
379 -*-*- Per:
380 	Return code: 0
381 	Size of array: 14
382 	Contents of array
383 	Element 0: /home/mszick
384 	Element 1: 4096
385 	Element 2: 8
386 	Element 3: 41e8
387 	Element 4: 500
388 	Element 5: 500
389 	Element 6: 303
390 	Element 7: 32385
391 	Element 8: 22
392 	Element 9: 0
393 	Element 10: 0
394 	Element 11: 1051221030
395 	Element 12: 1051214068
396 	Element 13: 1051214068
397 
398 	For a link in the form of linkname -> realname
399 	stat -t  linkname returns the linkname (link) information
400 	stat -lt linkname returns the realname information
401 
402 	stat -tf and stat -ltf fields
403 	[0]	name
404 	[1]	ID-0?		# Maybe someday, but Linux stat structure
405 	[2]	ID-0?		# does not have either LABEL nor UUID
406 				# fields, currently information must come
407 				# from file-system specific utilities
408 	These will be munged into:
409 	[1]	UUID if possible
410 	[2]	Volume Label if possible
411 	Note: 'mount -l' does return the label and could return the UUID
412 
413 	[3]	Maximum length of filenames
414 	[4]	Filesystem type
415 	[5]	Total blocks in the filesystem
416 	[6]	Free blocks
417 	[7]	Free blocks for non-root user(s)
418 	[8]	Block size of the filesystem
419 	[9]	Total inodes
420 	[10]	Free inodes
421 
422 -*-*- Per:
423 	Return code: 0
424 	Size of array: 11
425 	Contents of array
426 	Element 0: /home/mszick
427 	Element 1: 0
428 	Element 2: 0
429 	Element 3: 255
430 	Element 4: ef53
431 	Element 5: 2581445
432 	Element 6: 2277180
433 	Element 7: 2146050
434 	Element 8: 4096
435 	Element 9: 1311552
436 	Element 10: 1276425
437 
438 StatFieldsDoc
439 
440 
441 #	LocateFile [-l] FileName Location-Array-Name
442 #	LocateFile [-l] -of FileName Location-Array-FileName
443 
444 LocateFile()
445 {
446 	local -a LOC LOC1 LOC2
447 	local lk="" of=0
448 
449 	case "$#" in
450 	0) return 1 ;;
451 	1) return 1 ;;
452 	2) : ;;
453 	*) while (( "$#" > 2 ))
454 	   do
455 	      case "$1" in
456 	       -l) lk=-1 ;;
457 	      -of) of=1 ;;
458 	        *) return 1 ;;
459 	      esac
460 	   shift
461            done ;;
462 	esac
463 
464 # More Sanscrit-2.0.5
465       # LOC1=( $(stat -t $lk $1) )
466       # LOC2=( $(stat -tf $lk $1) )
467       # Uncomment above two lines if system has "stat" command installed.
468 	LOC=( ${LOC1[@]:0:1} ${LOC1[@]:3:11}
469 	      ${LOC2[@]:1:2} ${LOC2[@]:4:1} )
470 
471 	case "$of" in
472 		0) eval $2=\( \"\$\{LOC\[@\]\}\" \) ;;
473 		1) echo "${LOC[@]}" > "$2" ;;
474 	esac
475 	return 0
476 # Which yields (if you are lucky, and have "stat" installed)
477 # -*-*- Location Discriptor -*-*-
478 #	Return code: 0
479 #	Size of array: 15
480 #	Contents of array
481 #	Element 0: /home/mszick		20th Century name
482 #	Element 1: 41e8			Type and Permissions
483 #	Element 2: 500			User
484 #	Element 3: 500			Group
485 #	Element 4: 303			Device
486 #	Element 5: 32385		inode
487 #	Element 6: 22			Link count
488 #	Element 7: 0			Device Major
489 #	Element 8: 0			Device Minor
490 #	Element 9: 1051224608		Last Access
491 #	Element 10: 1051214068		Last Modify
492 #	Element 11: 1051214068		Last Status
493 #	Element 12: 0			UUID (to be)
494 #	Element 13: 0			Volume Label (to be)
495 #	Element 14: ef53		Filesystem type
496 }
497 
498 
499 
500 # And then there was some test code
501 
502 ListArray() # ListArray Name
503 {
504 	local -a Ta
505 
506 	eval Ta=\( \"\$\{$1\[@\]\}\" \)
507 	echo
508 	echo "-*-*- List of Array -*-*-"
509 	echo "Size of array $1: ${#Ta[*]}"
510 	echo "Contents of array $1:"
511 	for (( i=0 ; i<${#Ta[*]} ; i++ ))
512 	do
513 	    echo -e "\tElement $i: ${Ta[$i]}"
514 	done
515 	return 0
516 }
517 
518 declare -a CUR_DIR
519 # For small arrays
520 ListDirectory "${PWD}" CUR_DIR
521 ListArray CUR_DIR
522 
523 declare -a DIR_DIG
524 DigestFile CUR_DIR DIR_DIG
525 echo "The new \"name\" (checksum) for ${CUR_DIR[9]} is ${DIR_DIG[0]}"
526 
527 declare -a DIR_ENT
528 # BIG_DIR # For really big arrays - use a temporary file in ramdisk
529 # BIG-DIR # ListDirectory -of "${CUR_DIR[11]}/*" "/tmpfs/junk2"
530 ListDirectory "${CUR_DIR[11]}/*" DIR_ENT
531 
532 declare -a DIR_IDX
533 # BIG-DIR # IndexList -if "/tmpfs/junk2" DIR_IDX
534 IndexList DIR_ENT DIR_IDX
535 
536 declare -a IDX_DIG
537 # BIG-DIR # DIR_ENT=( $(cat /tmpfs/junk2) )
538 # BIG-DIR # DigestFile -if /tmpfs/junk2 IDX_DIG
539 DigestFile DIR_ENT IDX_DIG
540 # Small (should) be able to parallize IndexList & DigestFile
541 # Large (should) be able to parallize IndexList & DigestFile & the assignment
542 echo "The \"name\" (checksum) for the contents of ${PWD} is ${IDX_DIG[0]}"
543 
544 declare -a FILE_LOC
545 LocateFile ${PWD} FILE_LOC
546 ListArray FILE_LOC
547 
548 exit 0

Stephane Chazelas向我们展示了如何在Bash脚本中使用面向对象的编程方法.


例子 A-20. 面向对象数据库

  1 #!/bin/bash
  2 # obj-oriented.sh: Object-oriented programming in a shell script.
  3 # Script by Stephane Chazelas.
  4 
  5 #  Important Note:
  6 #  --------- ----
  7 #  If running this script under version 3 or later of Bash,
  8 #+ replace all periods in function names with a "legal" character,
  9 #+ for example, an underscore.
 10 
 11 
 12 person.new()        # Looks almost like a class declaration in C++.
 13 {
 14   local obj_name=$1 name=$2 firstname=$3 birthdate=$4
 15 
 16   eval "$obj_name.set_name() {
 17           eval \"$obj_name.get_name() {
 18                    echo \$1
 19                  }\"
 20         }"
 21 
 22   eval "$obj_name.set_firstname() {
 23           eval \"$obj_name.get_firstname() {
 24                    echo \$1
 25                  }\"
 26         }"
 27 
 28   eval "$obj_name.set_birthdate() {
 29           eval \"$obj_name.get_birthdate() {
 30             echo \$1
 31           }\"
 32           eval \"$obj_name.show_birthdate() {
 33             echo \$(date -d \"1/1/1970 0:0:\$1 GMT\")
 34           }\"
 35           eval \"$obj_name.get_age() {
 36             echo \$(( (\$(date +%s) - \$1) / 3600 / 24 / 365 ))
 37           }\"
 38         }"
 39 
 40   $obj_name.set_name $name
 41   $obj_name.set_firstname $firstname
 42   $obj_name.set_birthdate $birthdate
 43 }
 44 
 45 echo
 46 
 47 person.new self Bozeman Bozo 101272413
 48 # Create an instance of "person.new" (actually passing args to the function).
 49 
 50 self.get_firstname       #   Bozo
 51 self.get_name            #   Bozeman
 52 self.get_age             #   28
 53 self.get_birthdate       #   101272413
 54 self.show_birthdate      #   Sat Mar 17 20:13:33 MST 1973
 55 
 56 echo
 57 
 58 #  typeset -f
 59 #+ to see the created functions (careful, it scrolls off the page).
 60 
 61 exit 0

Mariusz Gniazdowski发布了一个可以在脚本中使用的hash库.


例子 A-21. hash函数库

  1 # Hash:
  2 # Hash function library
  3 # Author: Mariusz Gniazdowski <mgniazd-at-gmail.com>
  4 # Date: 2005-04-07
  5 
  6 # Functions making emulating hashes in Bash a little less painful.
  7 
  8 
  9 #    Limitations:
 10 #  * Only global variables are supported.
 11 #  * Each hash instance generates one global variable per value.
 12 #  * Variable names collisions are possible
 13 #+   if you define variable like __hash__hashname_key
 14 #  * Keys must use chars that can be part of a Bash variable name
 15 #+   (no dashes, periods, etc.).
 16 #  * The hash is created as a variable:
 17 #    ... hashname_keyname
 18 #    So if somone will create hashes like:
 19 #      myhash_ + mykey = myhash__mykey
 20 #      myhash + _mykey = myhash__mykey
 21 #    Then there will be a collision.
 22 #    (This should not pose a major problem.)
 23 
 24 
 25 Hash_config_varname_prefix=__hash__
 26 
 27 
 28 # Emulates:  hash[key]=value
 29 #
 30 # Params:
 31 # 1 - hash
 32 # 2 - key
 33 # 3 - value
 34 function hash_set {
 35 	eval "${Hash_config_varname_prefix}${1}_${2}=\"${3}\""
 36 }
 37 
 38 
 39 # Emulates:  value=hash[key]
 40 #
 41 # Params:
 42 # 1 - hash
 43 # 2 - key
 44 # 3 - value (name of global variable to set)
 45 function hash_get_into {
 46 	eval "$3=\"\$${Hash_config_varname_prefix}${1}_${2}\""
 47 }
 48 
 49 
 50 # Emulates:  echo hash[key]
 51 #
 52 # Params:
 53 # 1 - hash
 54 # 2 - key
 55 # 3 - echo params (like -n, for example)
 56 function hash_echo {
 57 	eval "echo $3 \"\$${Hash_config_varname_prefix}${1}_${2}\""
 58 }
 59 
 60 
 61 # Emulates:  hash1[key1]=hash2[key2]
 62 #
 63 # Params:
 64 # 1 - hash1
 65 # 2 - key1
 66 # 3 - hash2
 67 # 4 - key2
 68 function hash_copy {
 69 	eval "${Hash_config_varname_prefix}${1}_${2}=\"\$${Hash_config_varname_prefix}${3}_${4}\""
 70 }
 71 
 72 
 73 # Emulates:  hash[keyN-1]=hash[key2]=...hash[key1]
 74 #
 75 # Copies first key to rest of keys.
 76 #
 77 # Params:
 78 # 1 - hash1
 79 # 2 - key1
 80 # 3 - key2
 81 # . . .
 82 # N - keyN
 83 function hash_dup {
 84 	local hashName="$1" keyName="$2"
 85 	shift 2
 86 	until [ ${#} -le 0 ]; do
 87 		eval "${Hash_config_varname_prefix}${hashName}_${1}=\"\$${Hash_config_varname_prefix}${hashName}_${keyName}\""
 88 		shift;
 89 	done;
 90 }
 91 
 92 
 93 # Emulates:  unset hash[key]
 94 #
 95 # Params:
 96 # 1 - hash
 97 # 2 - key
 98 function hash_unset {
 99 	eval "unset ${Hash_config_varname_prefix}${1}_${2}"
100 }
101 
102 
103 # Emulates something similar to:  ref=&hash[key]
104 #
105 # The reference is name of the variable in which value is held.
106 #
107 # Params:
108 # 1 - hash
109 # 2 - key
110 # 3 - ref - Name of global variable to set.
111 function hash_get_ref_into {
112 	eval "$3=\"${Hash_config_varname_prefix}${1}_${2}\""
113 }
114 
115 
116 # Emulates something similar to:  echo &hash[key]
117 #
118 # That reference is name of variable in which value is held.
119 #
120 # Params:
121 # 1 - hash
122 # 2 - key
123 # 3 - echo params (like -n for example)
124 function hash_echo_ref {
125 	eval "echo $3 \"${Hash_config_varname_prefix}${1}_${2}\""
126 }
127 
128 
129 
130 # Emulates something similar to:  $$hash[key](param1, param2, ...)
131 #
132 # Params:
133 # 1 - hash
134 # 2 - key
135 # 3,4, ... - Function parameters
136 function hash_call {
137 	local hash key
138 	hash=$1
139 	key=$2
140 	shift 2
141 	eval "eval \"\$${Hash_config_varname_prefix}${hash}_${key} \\\"\\\$@\\\"\""
142 }
143 
144 
145 # Emulates something similar to:  isset(hash[key]) or hash[key]==NULL
146 #
147 # Params:
148 # 1 - hash
149 # 2 - key
150 # Returns:
151 # 0 - there is such key
152 # 1 - there is no such key
153 function hash_is_set {
154 	eval "if [[ \"\${${Hash_config_varname_prefix}${1}_${2}-a}\" = \"a\" && 
155 			\"\${${Hash_config_varname_prefix}${1}_${2}-b}\" = \"b\" ]]; then return 1; else return 0; fi"
156 }
157 
158 
159 # Emulates something similar to:
160 #   foreach($hash as $key => $value) { fun($key,$value); }
161 #
162 # It is possible to write different variations of this function.
163 # Here we use a function call to make it as "generic" as possible.
164 #
165 # Params:
166 # 1 - hash
167 # 2 - function name
168 function hash_foreach {
169 	local keyname oldIFS="$IFS"
170 	IFS=' '
171 	for i in $(eval "echo \${!${Hash_config_varname_prefix}${1}_*}"); do
172 		keyname=$(eval "echo \${i##${Hash_config_varname_prefix}${1}_}")
173 		eval "$2 $keyname \"\$$i\""
174 	done
175 	IFS="$oldIFS"
176 }
177 
178 # NOTE: In lines 103 and 116, ampersand changed.
179 #       But, it doesn't matter, because these are comment lines anyhow.

这是个例子脚本, 这个脚本使用了前面的hash库.


例子 A-22. 使用hash函数来给文本上色

  1 #!/bin/bash
  2 # hash-example.sh: Colorizing text.
  3 # Author: Mariusz Gniazdowski <mgniazd-at-gmail.com>
  4 
  5 . Hash.lib      # Load the library of functions.
  6 
  7 hash_set colors red          "\033[0;31m"
  8 hash_set colors blue         "\033[0;34m"
  9 hash_set colors light_blue   "\033[1;34m"
 10 hash_set colors light_red    "\033[1;31m"
 11 hash_set colors cyan         "\033[0;36m"
 12 hash_set colors light_green  "\033[1;32m"
 13 hash_set colors light_gray   "\033[0;37m"
 14 hash_set colors green        "\033[0;32m"
 15 hash_set colors yellow       "\033[1;33m"
 16 hash_set colors light_purple "\033[1;35m"
 17 hash_set colors purple       "\033[0;35m"
 18 hash_set colors reset_color  "\033[0;00m"
 19 
 20 
 21 # $1 - keyname
 22 # $2 - value
 23 try_colors() {
 24 	echo -en "$2"
 25 	echo "This line is $1."
 26 }
 27 hash_foreach colors try_colors
 28 hash_echo colors reset_color -en
 29 
 30 echo -e '\nLet us overwrite some colors with yellow.\n'
 31 # It's hard to read yellow text on some terminals.
 32 hash_dup colors yellow   red light_green blue green light_gray cyan
 33 hash_foreach colors try_colors
 34 hash_echo colors reset_color -en
 35 
 36 echo -e '\nLet us delete them and try colors once more . . .\n'
 37 
 38 for i in red light_green blue green light_gray cyan; do
 39 	hash_unset colors $i
 40 done
 41 hash_foreach colors try_colors
 42 hash_echo colors reset_color -en
 43 
 44 hash_set other txt "Other examples . . ."
 45 hash_echo other txt
 46 hash_get_into other txt text
 47 echo $text
 48 
 49 hash_set other my_fun try_colors
 50 hash_call other my_fun   purple "`hash_echo colors purple`"
 51 hash_echo colors reset_color -en
 52 
 53 echo; echo "Back to normal?"; echo
 54 
 55 exit $?
 56 
 57 #  On some terminals, the "light" colors print in bold,
 58 #  and end up looking darker than the normal ones.
 59 #  Why is this?

站在一个比较难的观点来阐明hash的结构.


例子 A-23. 深入hash函数

  1 #!/bin/bash
  2 # $Id: ha.sh,v 1.2 2005/04/21 23:24:26 oliver Exp $
  3 # Copyright 2005 Oliver Beckstein
  4 # Released under the GNU Public License
  5 # Author of script granted permission for inclusion in ABS Guide.
  6 # (Thank you!)
  7 
  8 #----------------------------------------------------------------
  9 # pseudo hash based on indirect parameter expansion
 10 # API: access through functions:
 11 # 
 12 # create the hash:
 13 #  
 14 #      newhash Lovers
 15 #
 16 # add entries (note single quotes for spaces)
 17 #    
 18 #      addhash Lovers Tristan Isolde
 19 #      addhash Lovers 'Romeo Montague' 'Juliet Capulet'
 20 #
 21 # access value by key
 22 #
 23 #      gethash Lovers Tristan   ---->  Isolde
 24 #
 25 # show all keys
 26 #
 27 #      keyshash Lovers         ----> 'Tristan'  'Romeo Montague'
 28 #
 29 #
 30 # convention: instead of perls' foo{bar} = boing' syntax,
 31 # use
 32 #       '_foo_bar=boing' (two underscores, no spaces)
 33 #
 34 # 1) store key   in _NAME_keys[]
 35 # 2) store value in _NAME_values[] using the same integer index
 36 # The integer index for the last entry is _NAME_ptr
 37 #
 38 # NOTE: No error or sanity checks, just bare bones.
 39 
 40 
 41 function _inihash () {
 42     # private function
 43     # call at the beginning of each procedure
 44     # defines: _keys _values _ptr
 45     #
 46     # usage: _inihash NAME
 47     local name=$1
 48     _keys=_${name}_keys
 49     _values=_${name}_values
 50     _ptr=_${name}_ptr
 51 }
 52 
 53 function newhash () {
 54     # usage: newhash NAME
 55     #        NAME should not contain spaces or '.';
 56     #        actually: it must be a legal name for a bash variable
 57     # We rely on bash automatically recognising arrays.
 58     local name=$1 
 59     local _keys _values _ptr
 60     _inihash ${name}
 61     eval ${_ptr}=0
 62 }
 63 
 64 
 65 function addhash () {
 66     # usage: addhash NAME KEY 'VALUE with spaces'
 67     #        arguments with spaces need to be quoted with single quotes ''
 68     local name=$1 k="$2" v="$3" 
 69     local _keys _values _ptr
 70     _inihash ${name}
 71 
 72     #echo "DEBUG(addhash): ${_ptr}=${!_ptr}"
 73 
 74     eval let ${_ptr}=${_ptr}+1
 75     eval "$_keys[${!_ptr}]=\"${k}\""
 76     eval "$_values[${!_ptr}]=\"${v}\""
 77 }
 78 
 79 function gethash () {
 80     # usage: gethash NAME KEY
 81     #        returns boing
 82     #        ERR=0 if entry found, 1 otherwise
 83     # Thats not a proper hash---we simply linearly search through the keys
 84     local name=$1 key="$2" 
 85     local _keys _values _ptr 
 86     local k v i found h
 87     _inihash ${name}
 88     
 89     # _ptr holds the highest index in the hash
 90     found=0
 91 
 92     for i in $(seq 1 ${!_ptr}); do
 93 	h="\${${_keys}[${i}]}"  # safer to do it in two steps
 94 	eval k=${h}             # (especially when quoting for spaces)
 95 	if [ "${k}" = "${key}" ]; then found=1; break; fi
 96     done;
 97 
 98     [ ${found} = 0 ] && return 1;
 99     # else: i is the index that matches the key
100     h="\${${_values}[${i}]}"
101     eval echo "${h}"
102     return 0;	
103 }
104 
105 function keyshash () {
106     # usage: keyshash NAME
107     # returns list of all keys defined for hash name
108     local name=$1 key="$2" 
109     local _keys _values _ptr 
110     local k i h
111     _inihash ${name}
112     
113     # _ptr holds the highest index in the hash
114     for i in $(seq 1 ${!_ptr}); do
115 	h="\${${_keys}[${i}]}"   # Safer to do it in two steps
116 	eval k=${h}              # (especially when quoting for spaces)
117 	echo -n "'${k}' "
118     done;
119 }
120 
121 
122 # --------------------------------------------------------------------
123 
124 # Now, let's test it.
125 # (Per comments at the beginning of the script.)
126 newhash Lovers
127 addhash Lovers Tristan Isolde
128 addhash Lovers 'Romeo Montague' 'Juliet Capulet'
129 
130 # Output results.
131 echo
132 gethash Lovers Tristan      # Isolde
133 echo
134 keyshash Lovers             # 'Tristan' 'Romeo Montague'
135 echo; echo
136 
137 
138 exit 0
139 
140 # Exercise: Add error checks to the functions.

下面这个脚本可以用来安装和挂载那些小的USB keychain"硬件设备"(译者: 就是U盘一类的东西).


例子 A-24. 挂载USB keychain型的存储设备

  1 #!/bin/bash
  2 # ==> usb.sh
  3 # ==> Script for mounting and installing pen/keychain USB storage devices.
  4 # ==> Runs as root at system startup (see below).
  5 # ==>
  6 # ==> Newer Linux distros (2004 or later) autodetect
  7 # ==> and install USB pen drives, and therefore don't need this script.
  8 # ==> But, it's still instructive.
  9  
 10 #  This code is free software covered by GNU GPL license version 2 or above.
 11 #  Please refer to http://www.gnu.org/ for the full license text.
 12 #
 13 #  Some code lifted from usb-mount by Michael Hamilton's usb-mount (LGPL)
 14 #+ see http://users.actrix.co.nz/michael/usbmount.html
 15 #
 16 #  INSTALL
 17 #  -------
 18 #  Put this in /etc/hotplug/usb/diskonkey.
 19 #  Then look in /etc/hotplug/usb.distmap, and copy all usb-storage entries
 20 #+ into /etc/hotplug/usb.usermap, substituting "usb-storage" for "diskonkey".
 21 #  Otherwise this code is only run during the kernel module invocation/removal
 22 #+ (at least in my tests), which defeats the purpose.
 23 #
 24 #  TODO
 25 #  ----
 26 #  Handle more than one diskonkey device at one time (e.g. /dev/diskonkey1
 27 #+ and /mnt/diskonkey1), etc. The biggest problem here is the handling in
 28 #+ devlabel, which I haven't yet tried.
 29 #
 30 #  AUTHOR and SUPPORT
 31 #  ------------------
 32 #  Konstantin Riabitsev, <icon linux duke edu>.
 33 #  Send any problem reports to my email address at the moment.
 34 #
 35 # ==> Comments added by ABS Guide author.
 36 
 37 
 38 
 39 SYMLINKDEV=/dev/diskonkey
 40 MOUNTPOINT=/mnt/diskonkey
 41 DEVLABEL=/sbin/devlabel
 42 DEVLABELCONFIG=/etc/sysconfig/devlabel
 43 IAM=$0
 44 
 45 ##
 46 # Functions lifted near-verbatim from usb-mount code.
 47 #
 48 function allAttachedScsiUsb {
 49     find /proc/scsi/ -path '/proc/scsi/usb-storage*' -type f | xargs grep -l 'Attached: Yes'
 50 }
 51 function scsiDevFromScsiUsb {
 52     echo $1 | awk -F"[-/]" '{ n=$(NF-1);  print "/dev/sd" substr("abcdefghijklmnopqrstuvwxyz", n+1,
 53  1) }'
 54 }
 55 
 56 if [ "${ACTION}" = "add" ] && [ -f "${DEVICE}" ]; then
 57     ##
 58     # lifted from usbcam code.
 59     #
 60     if [ -f /var/run/console.lock ]; then
 61         CONSOLEOWNER=`cat /var/run/console.lock`
 62     elif [ -f /var/lock/console.lock ]; then
 63         CONSOLEOWNER=`cat /var/lock/console.lock`
 64     else
 65         CONSOLEOWNER=
 66     fi
 67     for procEntry in $(allAttachedScsiUsb); do
 68         scsiDev=$(scsiDevFromScsiUsb $procEntry)
 69         #  Some bug with usb-storage?
 70         #  Partitions are not in /proc/partitions until they are accessed
 71         #+ somehow.
 72         /sbin/fdisk -l $scsiDev >/dev/null
 73         ##
 74         #  Most devices have partitioning info, so the data would be on
 75         #+ /dev/sd?1. However, some stupider ones don't have any partitioning
 76         #+ and use the entire device for data storage. This tries to
 77         #+ guess semi-intelligently if we have a /dev/sd?1 and if not, then
 78         #+ it uses the entire device and hopes for the better.
 79         #
 80         if grep -q `basename $scsiDev`1 /proc/partitions; then
 81             part="$scsiDev""1"
 82         else
 83             part=$scsiDev
 84         fi
 85         ##
 86         #  Change ownership of the partition to the console user so they can
 87         #+ mount it.
 88         #
 89         if [ ! -z "$CONSOLEOWNER" ]; then
 90             chown $CONSOLEOWNER:disk $part
 91         fi
 92         ##
 93         # This checks if we already have this UUID defined with devlabel.
 94         # If not, it then adds the device to the list.
 95         #
 96         prodid=`$DEVLABEL printid -d $part`
 97         if ! grep -q $prodid $DEVLABELCONFIG; then
 98             # cross our fingers and hope it works
 99             $DEVLABEL add -d $part -s $SYMLINKDEV 2>/dev/null
100         fi
101         ##
102         # Check if the mount point exists and create if it doesn't.
103         #
104         if [ ! -e $MOUNTPOINT ]; then
105             mkdir -p $MOUNTPOINT
106         fi
107         ##
108         # Take care of /etc/fstab so mounting is easy.
109         #
110         if ! grep -q "^$SYMLINKDEV" /etc/fstab; then
111             # Add an fstab entry
112             echo -e \
113                 "$SYMLINKDEV\t\t$MOUNTPOINT\t\tauto\tnoauto,owner,kudzu 0 0" \
114                 >> /etc/fstab
115         fi
116     done
117     if [ ! -z "$REMOVER" ]; then
118         ##
119         # Make sure this script is triggered on device removal.
120         #
121         mkdir -p `dirname $REMOVER`
122         ln -s $IAM $REMOVER
123     fi
124 elif [ "${ACTION}" = "remove" ]; then
125     ##
126     # If the device is mounted, unmount it cleanly.
127     #
128     if grep -q "$MOUNTPOINT" /etc/mtab; then
129         # unmount cleanly
130         umount -l $MOUNTPOINT
131     fi
132     ##
133     # Remove it from /etc/fstab if it's there.
134     #
135     if grep -q "^$SYMLINKDEV" /etc/fstab; then
136         grep -v "^$SYMLINKDEV" /etc/fstab > /etc/.fstab.new
137         mv -f /etc/.fstab.new /etc/fstab
138     fi
139 fi
140 
141 exit 0

这个脚本对于站点管理员来说很有用: 这是一个可以保存weblog的脚本.


例子 A-25. 保存weblog

  1 #!/bin/bash
  2 # archiveweblogs.sh v1.0
  3 
  4 # Troy Engel <tengel@fluid.com>
  5 # Slightly modified by document author.
  6 # Used with permission.
  7 #
  8 #  This script will preserve the normally rotated and
  9 #+ thrown away weblogs from a default RedHat/Apache installation.
 10 #  It will save the files with a date/time stamp in the filename,
 11 #+ bzipped, to a given directory.
 12 #
 13 #  Run this from crontab nightly at an off hour,
 14 #+ as bzip2 can suck up some serious CPU on huge logs:
 15 #  0 2 * * * /opt/sbin/archiveweblogs.sh
 16 
 17 
 18 PROBLEM=66
 19 
 20 # Set this to your backup dir.
 21 BKP_DIR=/opt/backups/weblogs
 22 
 23 # Default Apache/RedHat stuff
 24 LOG_DAYS="4 3 2 1"
 25 LOG_DIR=/var/log/httpd
 26 LOG_FILES="access_log error_log"
 27 
 28 # Default RedHat program locations
 29 LS=/bin/ls
 30 MV=/bin/mv
 31 ID=/usr/bin/id
 32 CUT=/bin/cut
 33 COL=/usr/bin/column
 34 BZ2=/usr/bin/bzip2
 35 
 36 # Are we root?
 37 USER=`$ID -u`
 38 if [ "X$USER" != "X0" ]; then
 39   echo "PANIC: Only root can run this script!"
 40   exit $PROBLEM
 41 fi
 42 
 43 # Backup dir exists/writable?
 44 if [ ! -x $BKP_DIR ]; then
 45   echo "PANIC: $BKP_DIR doesn't exist or isn't writable!"
 46   exit $PROBLEM
 47 fi
 48 
 49 # Move, rename and bzip2 the logs
 50 for logday in $LOG_DAYS; do
 51   for logfile in $LOG_FILES; do
 52     MYFILE="$LOG_DIR/$logfile.$logday"
 53     if [ -w $MYFILE ]; then
 54       DTS=`$LS -lgo --time-style=+%Y%m%d $MYFILE | $COL -t | $CUT -d ' ' -f7`
 55       $MV $MYFILE $BKP_DIR/$logfile.$DTS
 56       $BZ2 $BKP_DIR/$logfile.$DTS
 57     else
 58       # Only spew an error if the file exits (ergo non-writable).
 59       if [ -f $MYFILE ]; then
 60         echo "ERROR: $MYFILE not writable. Skipping."
 61       fi
 62     fi
 63   done
 64 done
 65 
 66 exit 0

你怎么做才能阻止shell扩展或者重新解释字符串?


例子 A-26. 保护字符串的字面含义

  1 #! /bin/bash
  2 # protect_literal.sh
  3 
  4 # set -vx
  5 
  6 :<<-'_Protect_Literal_String_Doc'
  7 
  8     Copyright (c) Michael S. Zick, 2003; All Rights Reserved
  9     License: Unrestricted reuse in any form, for any purpose.
 10     Warranty: None
 11     Revision: $ID$
 12 
 13     Documentation redirected to the Bash no-operation.
 14     Bash will '/dev/null' this block when the script is first read.
 15     (Uncomment the above set command to see this action.)
 16 
 17     Remove the first (Sha-Bang) line when sourcing this as a library
 18     procedure.  Also comment out the example use code in the two
 19     places where shown.
 20 
 21 
 22     Usage:
 23         _protect_literal_str 'Whatever string meets your ${fancy}'
 24         Just echos the argument to standard out, hard quotes
 25         restored.
 26 
 27         $(_protect_literal_str 'Whatever string meets your ${fancy}')
 28         as the right-hand-side of an assignment statement.
 29 
 30     Does:
 31         As the right-hand-side of an assignment, preserves the
 32         hard quotes protecting the contents of the literal during
 33         assignment.
 34 
 35     Notes:
 36         The strange names (_*) are used to avoid trampling on
 37         the user's chosen names when this is sourced as a
 38         library.
 39 
 40 _Protect_Literal_String_Doc
 41 
 42 # The 'for illustration' function form
 43 
 44 _protect_literal_str() {
 45 
 46 # Pick an un-used, non-printing character as local IFS.
 47 # Not required, but shows that we are ignoring it.
 48     local IFS=$'\x1B'               # \ESC character
 49 
 50 # Enclose the All-Elements-Of in hard quotes during assignment.
 51     local tmp=$'\x27'$@$'\x27'
 52 #    local tmp=$'\''$@$'\''         # Even uglier.
 53 
 54     local len=${#tmp}               # Info only.
 55     echo $tmp is $len long.         # Output AND information.
 56 }
 57 
 58 # This is the short-named version.
 59 _pls() {
 60     local IFS=$'x1B'                # \ESC character (not required)
 61     echo $'\x27'$@$'\x27'           # Hard quoted parameter glob
 62 }
 63 
 64 # :<<-'_Protect_Literal_String_Test'
 65 # # # Remove the above "# " to disable this code. # # #
 66 
 67 # See how that looks when printed.
 68 echo
 69 echo "- - Test One - -"
 70 _protect_literal_str 'Hello $user'
 71 _protect_literal_str 'Hello "${username}"'
 72 echo
 73 
 74 # Which yields:
 75 # - - Test One - -
 76 # 'Hello $user' is 13 long.
 77 # 'Hello "${username}"' is 21 long.
 78 
 79 #  Looks as expected, but why all of the trouble?
 80 #  The difference is hidden inside the Bash internal order
 81 #+ of operations.
 82 #  Which shows when you use it on the RHS of an assignment.
 83 
 84 # Declare an array for test values.
 85 declare -a arrayZ
 86 
 87 # Assign elements with various types of quotes and escapes.
 88 arrayZ=( zero "$(_pls 'Hello ${Me}')" 'Hello ${You}' "\'Pass: ${pw}\'" )
 89 
 90 # Now list that array and see what is there.
 91 echo "- - Test Two - -"
 92 for (( i=0 ; i<${#arrayZ[*]} ; i++ ))
 93 do
 94     echo  Element $i: ${arrayZ[$i]} is: ${#arrayZ[$i]} long.
 95 done
 96 echo
 97 
 98 # Which yields:
 99 # - - Test Two - -
100 # Element 0: zero is: 4 long.           # Our marker element
101 # Element 1: 'Hello ${Me}' is: 13 long. # Our "$(_pls '...' )"
102 # Element 2: Hello ${You} is: 12 long.  # Quotes are missing
103 # Element 3: \'Pass: \' is: 10 long.    # ${pw} expanded to nothing
104 
105 # Now make an assignment with that result.
106 declare -a array2=( ${arrayZ[@]} )
107 
108 # And print what happened.
109 echo "- - Test Three - -"
110 for (( i=0 ; i<${#array2[*]} ; i++ ))
111 do
112     echo  Element $i: ${array2[$i]} is: ${#array2[$i]} long.
113 done
114 echo
115 
116 # Which yields:
117 # - - Test Three - -
118 # Element 0: zero is: 4 long.           # Our marker element.
119 # Element 1: Hello ${Me} is: 11 long.   # Intended result.
120 # Element 2: Hello is: 5 long.          # ${You} expanded to nothing.
121 # Element 3: 'Pass: is: 6 long.         # Split on the whitespace.
122 # Element 4: ' is: 1 long.              # The end quote is here now.
123 
124 #  Our Element 1 has had its leading and trailing hard quotes stripped.
125 #  Although not shown, leading and trailing whitespace is also stripped.
126 #  Now that the string contents are set, Bash will always, internally,
127 #+ hard quote the contents as required during its operations.
128 
129 #  Why?
130 #  Considering our "$(_pls 'Hello ${Me}')" construction:
131 #  " ... " -> Expansion required, strip the quotes.
132 #  $( ... ) -> Replace with the result of..., strip this.
133 #  _pls ' ... ' -> called with literal arguments, strip the quotes.
134 #  The result returned includes hard quotes; BUT the above processing
135 #+ has already been done, so they become part of the value assigned.
136 #
137 #  Similarly, during further usage of the string variable, the ${Me}
138 #+ is part of the contents (result) and survives any operations
139 #  (Until explicitly told to evaluate the string).
140 
141 #  Hint: See what happens when the hard quotes ($'\x27') are replaced
142 #+ with soft quotes ($'\x22') in the above procedures.
143 #  Interesting also is to remove the addition of any quoting.
144 
145 # _Protect_Literal_String_Test
146 # # # Remove the above "# " to disable this code. # # #
147 
148 exit 0

如果你确实想让shell扩展或者重新解释字符串的话, 该怎么办?


例子 A-27. 不保护字符串的字面含义

  1 #! /bin/bash
  2 # unprotect_literal.sh
  3 
  4 # set -vx
  5 
  6 :<<-'_UnProtect_Literal_String_Doc'
  7 
  8     Copyright (c) Michael S. Zick, 2003; All Rights Reserved
  9     License: Unrestricted reuse in any form, for any purpose.
 10     Warranty: None
 11     Revision: $ID$
 12 
 13     Documentation redirected to the Bash no-operation. Bash will
 14     '/dev/null' this block when the script is first read.
 15     (Uncomment the above set command to see this action.)
 16 
 17     Remove the first (Sha-Bang) line when sourcing this as a library
 18     procedure.  Also comment out the example use code in the two
 19     places where shown.
 20 
 21 
 22     Usage:
 23         Complement of the "$(_pls 'Literal String')" function.
 24         (See the protect_literal.sh example.)
 25 
 26         StringVar=$(_upls ProtectedSringVariable)
 27 
 28     Does:
 29         When used on the right-hand-side of an assignment statement;
 30         makes the substitions embedded in the protected string.
 31 
 32     Notes:
 33         The strange names (_*) are used to avoid trampling on
 34         the user's chosen names when this is sourced as a
 35         library.
 36 
 37 
 38 _UnProtect_Literal_String_Doc
 39 
 40 _upls() {
 41     local IFS=$'x1B'                # \ESC character (not required)
 42     eval echo $@                    # Substitution on the glob.
 43 }
 44 
 45 # :<<-'_UnProtect_Literal_String_Test'
 46 # # # Remove the above "# " to disable this code. # # #
 47 
 48 
 49 _pls() {
 50     local IFS=$'x1B'                # \ESC character (not required)
 51     echo $'\x27'$@$'\x27'           # Hard quoted parameter glob
 52 }
 53 
 54 # Declare an array for test values.
 55 declare -a arrayZ
 56 
 57 # Assign elements with various types of quotes and escapes.
 58 arrayZ=( zero "$(_pls 'Hello ${Me}')" 'Hello ${You}' "\'Pass: ${pw}\'" )
 59 
 60 # Now make an assignment with that result.
 61 declare -a array2=( ${arrayZ[@]} )
 62 
 63 # Which yielded:
 64 # - - Test Three - -
 65 # Element 0: zero is: 4 long            # Our marker element.
 66 # Element 1: Hello ${Me} is: 11 long    # Intended result.
 67 # Element 2: Hello is: 5 long           # ${You} expanded to nothing.
 68 # Element 3: 'Pass: is: 6 long          # Split on the whitespace.
 69 # Element 4: ' is: 1 long               # The end quote is here now.
 70 
 71 # set -vx
 72 
 73 #  Initialize 'Me' to something for the embedded ${Me} substitution.
 74 #  This needs to be done ONLY just prior to evaluating the
 75 #+ protected string.
 76 #  (This is why it was protected to begin with.)
 77 
 78 Me="to the array guy."
 79 
 80 # Set a string variable destination to the result.
 81 newVar=$(_upls ${array2[1]})
 82 
 83 # Show what the contents are.
 84 echo $newVar
 85 
 86 # Do we really need a function to do this?
 87 newerVar=$(eval echo ${array2[1]})
 88 echo $newerVar
 89 
 90 #  I guess not, but the _upls function gives us a place to hang
 91 #+ the documentation on.
 92 #  This helps when we forget what a # construction like:
 93 #+ $(eval echo ... ) means.
 94 
 95 # What if Me isn't set when the protected string is evaluated?
 96 unset Me
 97 newestVar=$(_upls ${array2[1]})
 98 echo $newestVar
 99 
100 # Just gone, no hints, no runs, no errors.
101 
102 #  Why in the world?
103 #  Setting the contents of a string variable containing character
104 #+ sequences that have a meaning in Bash is a general problem in
105 #+ script programming.
106 #
107 #  This problem is now solved in eight lines of code
108 #+ (and four pages of description).
109 
110 #  Where is all this going?
111 #  Dynamic content Web pages as an array of Bash strings.
112 #  Content set per request by a Bash 'eval' command
113 #+ on the stored page template.
114 #  Not intended to replace PHP, just an interesting thing to do.
115 ###
116 #  Don't have a webserver application?
117 #  No problem, check the example directory of the Bash source;
118 #+ there is a Bash script for that also.
119 
120 # _UnProtect_Literal_String_Test
121 # # # Remove the above "# " to disable this code. # # #
122 
123 exit 0

这个强大的脚本帮助我们抓住垃圾邮件服务器.


例子 A-28. 鉴定是否是垃圾邮件服务器

   1 #!/bin/bash
   2 
   3 # $Id: is_spammer.bash,v 1.12.2.11 2004/10/01 21:42:33 mszick Exp $
   4 # Above line is RCS info.
   5 
   6 # The latest version of this script is available from http://www.morethan.org.
   7 #
   8 # Spammer-identification
   9 # by Michael S. Zick
  10 # Used in the ABS Guide with permission.
  11 
  12 
  13 
  14 #######################################################
  15 # Documentation
  16 # See also "Quickstart" at end of script.
  17 #######################################################
  18 
  19 :<<-'__is_spammer_Doc_'
  20 
  21     Copyright (c) Michael S. Zick, 2004
  22     License: Unrestricted reuse in any form, for any purpose.
  23     Warranty: None -{Its a script; the user is on their own.}-
  24 
  25 Impatient?
  26     Application code: goto "# # # Hunt the Spammer' program code # # #"
  27     Example output: ":<<-'_is_spammer_outputs_'"
  28     How to use: Enter script name without arguments.
  29                 Or goto "Quickstart" at end of script.
  30 
  31 Provides
  32     Given a domain name or IP(v4) address as input:
  33 
  34     Does an exhaustive set of queries to find the associated
  35     network resources (short of recursing into TLDs).
  36 
  37     Checks the IP(v4) addresses found against Blacklist
  38     nameservers.
  39 
  40     If found to be a blacklisted IP(v4) address,
  41     reports the blacklist text records.
  42     (Usually hyper-links to the specific report.)
  43 
  44 Requires
  45     A working Internet connection.
  46     (Exercise: Add check and/or abort if not on-line when running script.)
  47     Bash with arrays (2.05b+).
  48 
  49     The external program 'dig' --
  50     a utility program provided with the 'bind' set of programs.
  51     Specifically, the version which is part of Bind series 9.x
  52     See: http://www.isc.org
  53 
  54     All usages of 'dig' are limited to wrapper functions,
  55     which may be rewritten as required.
  56     See: dig_wrappers.bash for details.
  57          ("Additional documentation" -- below)
  58 
  59 Usage
  60     Script requires a single argument, which may be:
  61     1) A domain name;
  62     2) An IP(v4) address;
  63     3) A filename, with one name or address per line.
  64 
  65     Script accepts an optional second argument, which may be:
  66     1) A Blacklist server name;
  67     2) A filename, with one Blacklist server name per line.
  68 
  69     If the second argument is not provided, the script uses
  70     a built-in set of (free) Blacklist servers.
  71 
  72     See also, the Quickstart at the end of this script (after 'exit').
  73 
  74 Return Codes
  75     0 - All OK
  76     1 - Script failure
  77     2 - Something is Blacklisted
  78 
  79 Optional environment variables
  80     SPAMMER_TRACE
  81         If set to a writable file,
  82         script will log an execution flow trace.
  83 
  84     SPAMMER_DATA
  85         If set to a writable file, script will dump its
  86         discovered data in the form of GraphViz file.
  87         See: http://www.research.att.com/sw/tools/graphviz
  88 
  89     SPAMMER_LIMIT
  90         Limits the depth of resource tracing.
  91 
  92         Default is 2 levels.
  93 
  94         A setting of 0 (zero) means 'unlimited' . . .
  95           Caution: script might recurse the whole Internet!
  96 
  97         A limit of 1 or 2 is most useful when processing
  98         a file of domain names and addresses.
  99         A higher limit can be useful when hunting spam gangs.
 100 
 101 
 102 Additional documentation
 103     Download the archived set of scripts
 104     explaining and illustrating the function contained within this script.
 105     http://personal.riverusers.com/mszick_clf.tar.bz2
 106 
 107 
 108 Study notes
 109     This script uses a large number of functions.
 110     Nearly all general functions have their own example script.
 111     Each of the example scripts have tutorial level comments.
 112 
 113 Scripting project
 114     Add support for IP(v6) addresses.
 115     IP(v6) addresses are recognized but not processed.
 116 
 117 Advanced project
 118     Add the reverse lookup detail to the discovered information.
 119 
 120     Report the delegation chain and abuse contacts.
 121 
 122     Modify the GraphViz file output to include the
 123     newly discovered information.
 124 
 125 __is_spammer_Doc_
 126 
 127 #######################################################
 128 
 129 
 130 
 131 
 132 #### Special IFS settings used for string parsing. ####
 133 
 134 # Whitespace == :Space:Tab:Line Feed:Carriage Return:
 135 WSP_IFS=$'\x20'$'\x09'$'\x0A'$'\x0D'
 136 
 137 # No Whitespace == Line Feed:Carriage Return
 138 NO_WSP=$'\x0A'$'\x0D'
 139 
 140 # Field separator for dotted decimal IP addresses
 141 ADR_IFS=${NO_WSP}'.'
 142 
 143 # Array to dotted string conversions
 144 DOT_IFS='.'${WSP_IFS}
 145 
 146 # # # Pending operations stack machine # # #
 147 # This set of functions described in func_stack.bash.
 148 # (See "Additional documentation" above.)
 149 # # #
 150 
 151 # Global stack of pending operations.
 152 declare -f -a _pending_
 153 # Global sentinel for stack runners
 154 declare -i _p_ctrl_
 155 # Global holder for currently executing function
 156 declare -f _pend_current_
 157 
 158 # # # Debug version only - remove for regular use # # #
 159 #
 160 # The function stored in _pend_hook_ is called
 161 # immediately before each pending function is
 162 # evaluated.  Stack clean, _pend_current_ set.
 163 #
 164 # This thingy demonstrated in pend_hook.bash.
 165 declare -f _pend_hook_
 166 # # #
 167 
 168 # The do nothing function
 169 pend_dummy() { : ; }
 170 
 171 # Clear and initialize the function stack.
 172 pend_init() {
 173     unset _pending_[@]
 174     pend_func pend_stop_mark
 175     _pend_hook_='pend_dummy'  # Debug only.
 176 }
 177 
 178 # Discard the top function on the stack.
 179 pend_pop() {
 180     if [ ${#_pending_[@]} -gt 0 ]
 181     then
 182         local -i _top_
 183         _top_=${#_pending_[@]}-1
 184         unset _pending_[$_top_]
 185     fi
 186 }
 187 
 188 # pend_func function_name [$(printf '%q\n' arguments)]
 189 pend_func() {
 190