当前位置: 首页 > 文档资料 > Shell 中文文档 >

Appendix A. Contributed Scripts

优质
小牛编辑
113浏览
2023-12-01

These scripts, while not fitting into the text of this document, doillustrate some interesting shell programming techniques. They are useful,too. Have fun analyzing and running them.

Example A-1. mailformat: Formatting an e-mail message

   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

Example A-2. rn: A simple-minded file rename utility

This script is a modification of Example 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.

Example A-3. blank-rename: renames filenames containingblanks

This is an even simpler-minded version of previous script.

   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

Example A-4. encryptedpw: Uploading to an ftp site,using a locally encrypted password

   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

Example A-5. copy-cd: Copying a data 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

Example A-6. Collatz series

   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

Example A-7. days-between: Calculate number of daysbetween two dates

   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 (NULL, 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

Example A-8. Make a "dictionary"

   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	

Example A-9. Soundex conversion

   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

Example A-10. "Game of Life"

https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-10.txt

Example A-11. Data file for "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 _..___..__

+++

The following two scripts are by Mark Moraes of the Universityof Toronto. See the enclosed file "Moraes-COPYRIGHT"for permissions and restrictions.

Example A-12. behead: Removing mail and news message headers

   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?

Example A-13. ftpget: Downloading files via ftp

https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-13.txt

+

Antek Sawicki contributed the following script, which makes veryclever use of the parameter substitution operators discussed inSection 9.3.

Example A-14. password: Generating random8-character passwords

   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 contributed this script,which uses named pipes and, in his words, "really exercisesquoting and escaping".

Example A-15. fifo: Making daily backups, using named pipes

   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

+

St閜hane Chazelas contributed the following script todemonstrate that generating prime numbers does not requirearrays.

Example A-16. Generating prime numbers using the modulo operator

   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.

+

This is Rick Boivie's revision of Jordi Sanfeliu'streescript.

Example A-17. tree: Displaying a directory 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 gave permission to use his stringfunctionscript, which essentially reproduces some of theC-library string manipulation functions.

Example A-18. string functions: C-like string functions

https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-18.txt

Michael Zick's complex array example uses the md5sumcheck sum command to encode directoryinformation.

Example A-19. Directory information

https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-19.txt

Sthane Chazelas demonstrates object-oriented programming in aBash script.

Example A-20. Object-oriented database

   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 contributes a hashlibrary for use in scripts.

Example A-21. Library of hash functions

   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.

Here is an example script using the foregoing hash library.

Example A-22. Colorizing text using hash functions

   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?
  60 

Now for a script that installs and mountsthose cute USB keychain solid-state "hard drives."

Example A-23. Mounting USB keychain storage devices

   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

Here is something to warm the hearts of webmasters and mistresseseverywhere: a script that saves weblogs.

Example A-24. Preserving weblogs

   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

How do you keep the shell from expanding and reinterpretingstrings?

Example A-25. Protecting literal strings

   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

What if you wantthe shell to expandand reinterpret strings?

Example A-26. Unprotecting literal strings

   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

This powerful script helps hunt down spammers.

Example A-27. Spammer Identification

https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-27.txt

Another anti-spam script.

Example A-28. Spammer Hunt

https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-28.txt

"Little Monster's"front end to wget.

Example A-29. Making wgeteasier to use

https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-29.txt

Example A-30. A "podcasting"script

   1 #!/bin/bash
   2
   3 # bashpodder.sh:
   4 # By Linc 10/1/2004
   5 # Find the latest script at http://linc.homeunix.org:8080/scripts/bashpodder
   6 # Last revision 12/14/2004 - Many Contributors!
   7 # If you use this and have made improvements or have comments
   8 # drop me an email at linc dot fessenden at gmail dot com
   9 # I'd appreciate it!
  10
  11 # ==>  ABS Guide extra comments.
  12
  13 # ==>  Author of this script has kindly granted permission
  14 # ==>+ for inclusion in ABS Guide.
  15
  16
  17 # ==> ################################################################
  18 #
  19 # ==> What is "podcasting"?
  20
  21 # ==> It's broadcasting "radio shows" over the Internet.
  22 # ==> These shows can be played on iPods and other music file players.
  23
  24 # ==> This script makes it possible.
  25 # ==> See documentation at the script author's site, above.
  26
  27 # ==> ################################################################
  28
  29
  30 # Make script crontab friendly:
  31 cd $(dirname $0)
  32 # ==> Change to directory where this script lives.
  33
  34 # datadir is the directory you want podcasts saved to:
  35 datadir=$(date +%Y-%m-%d)
  36 # ==> Will create a directory with the name: YYYY-MM-DD
  37
  38 # Check for and create datadir if necessary:
  39 if test ! -d $datadir
  40         then
  41         mkdir $datadir
  42 fi
  43
  44 # Delete any temp file:
  45 rm -f temp.log
  46
  47 # Read the bp.conf file and wget any url not already in the podcast.log file:
  48 while read podcast
  49         do # ==> Main action follows.
  50         file=$(wget -q $podcast -O - | tr '\r' '\n' | tr \' \" | sed -n 's/.*url="\([^"]*\)".*/\1/p')
  51         for url in $file
  52                 do
  53                 echo $url >> temp.log
  54                 if ! grep "$url" podcast.log > /dev/null
  55                         then
  56                         wget -q -P $datadir "$url"
  57                 fi
  58                 done
  59         done < bp.conf
  60
  61 # Move dynamically created log file to permanent log file:
  62 cat podcast.log >> temp.log
  63 sort temp.log | uniq > podcast.log
  64 rm temp.log
  65 # Create an m3u playlist:
  66 ls $datadir | grep -v m3u > $datadir/podcast.m3u
  67
  68
  69 exit 0

To end this section, a review of the basics . . . and more.

Example A-31. Basics Reviewed

https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-31.txt

Example A-32. An expanded cdcommand

https://www.xnip.cn/wp-content/uploads/2022/11/Example-A-32.txt