These scripts, while not fitting into the text of this document, do illustrate some interesting shell programming techniques. They are useful, too. Have fun analyzing and running them.
Example A-1. manview: Viewing formatted manpages
1 #!/bin/bash 2 # manview.sh: Formats the source of a man page for viewing. 3 4 # This is useful when writing man page source and you want to 5 #+ look at the intermediate results on the fly while working on it. 6 7 E_WRONGARGS=65 8 9 if [ -z "$1" ] 10 then 11 echo "Usage: `basename $0` filename" 12 exit $E_WRONGARGS 13 fi 14 15 groff -Tascii -man $1 | less 16 # From the man page for groff. 17 18 # If the man page includes tables and/or equations, 19 # then the above code will barf. 20 # The following line can handle such cases. 21 # 22 # gtbl < "$1" | geqn -Tlatin1 | groff -Tlatin1 -mtty-char -man 23 # 24 # Thanks, S.C. 25 26 exit 0 |
Example A-2. mailformat: Formatting an e-mail message
1 #!/bin/bash 2 # mail-format.sh: Format e-mail messages. 3 4 # Gets rid of carets, tabs, also fold 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 long lines to. 28 29 # Delete carets and tabs at beginning of lines, 30 #+ then fold lines to $MAXWIDTH characters. 31 sed ' 32 s/^>// 33 s/^ *>// 34 s/^ *// 35 s/ *// 36 ' $1 | fold -s --width=$MAXWIDTH 37 # -s option to "fold" breaks lines at whitespace, if possible. 38 39 # This script was inspired by an article in a well-known trade journal 40 #+ extolling a 164K Windows utility with similar functionality. 41 # 42 # An nice set of text processing utilities and an efficient 43 #+ scripting language provide an alternative to bloated executables. 44 45 exit 0 |
Example A-3. rn: A simple-minded file rename utility
This script is a modification of Example 12-15.
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-4. blank-rename: renames filenames containing blanks
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-5. 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 somewhat 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://metalab.unc.edu /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 # "bell" rings 'bell' after each file transfer. 45 46 exit 0 |
Example A-6. 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 10 echo; echo "Insert source CD, but do *not* mount it." 11 echo "Press ENTER when ready. " 12 read ready # Wait for input, $ready not used. 13 14 echo; echo "Copying the source CD to $OF." 15 echo "This may take a while. Please be patient." 16 17 dd if=$CDROM of=$OF bs=$BLOCKSIZE # Raw device copy. 18 19 20 echo; echo "Remove data CD." 21 echo "Insert blank CDR." 22 echo "Press ENTER when ready. " 23 read ready # Wait for input, $ready not used. 24 25 echo "Copying $OF to CDR." 26 27 cdrecord -v -isosize speed=$SPEED dev=0,0 $OF 28 # Uses Joerg Schilling's "cdrecord" package (see its docs). 29 # http://www.fokus.gmd.de/nthp/employees/schilling/cdrecord.html 30 31 32 echo; echo "Done copying $OF to CDR on device $CDROM." 33 34 echo "Do you want to erase the image file (y/n)? " # Probably a huge file. 35 read answer 36 37 case "$answer" in 38 [yY]) rm -f $OF 39 echo "$OF erased." 40 ;; 41 *) echo "$OF not erased.";; 42 esac 43 44 echo 45 46 # Exercise: 47 # Change the above "case" statement to also accept "yes" and "Yes" as input. 48 49 exit 0 |
Example A-7. 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-8. days-between: Calculate number of days between 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 ARGS=2 # Two command line parameters expected. 6 E_PARAM_ERR=65 # Param error. 7 8 REFYR=1600 # Reference year. 9 CENTURY=100 10 DIY=365 11 ADJ_DIY=367 # Adjusted for leap year + fraction. 12 MIY=12 13 DIM=31 14 LEAPCYCLE=4 15 16 MAXRETVAL=256 # Largest permissable 17 # positive return value from a function. 18 19 diff= # Declare global variable for date difference. 20 value= # Declare global variable for absolute value. 21 day= # Declare globals for day, month, year. 22 month= 23 year= 24 25 26 Param_Error () # Command line parameters wrong. 27 { 28 echo "Usage: `basename $0` [M]M/[D]D/YYYY [M]M/[D]D/YYYY" 29 echo " (date must be after 1/3/1600)" 30 exit $E_PARAM_ERR 31 } 32 33 34 Parse_Date () # Parse date from command line params. 35 { 36 month=${1%%/**} 37 dm=${1%/**} # Day and month. 38 day=${dm#*/} 39 let "year = `basename $1`" # Not a filename, but works just the same. 40 } 41 42 43 check_date () # Checks for invalid date(s) passed. 44 { 45 [ "$day" -gt "$DIM" ] || [ "$month" -gt "$MIY" ] || [ "$year" -lt "$REFYR" ] && Param_Error 46 # Exit script on bad value(s). 47 # Uses "or-list / and-list". 48 # 49 # Exercise: Implement more rigorous date checking. 50 } 51 52 53 strip_leading_zero () # Better to strip possible leading zero(s) 54 { # from day and/or month 55 val=${1#0} # since otherwise Bash will interpret them 56 return $val # as octal values (POSIX.2, sect 2.9.2.1). 57 } 58 59 60 day_index () # Gauss' Formula: 61 { # Days from Jan. 3, 1600 to date passed as param. 62 63 day=$1 64 month=$2 65 year=$3 66 67 let "month = $month - 2" 68 if [ "$month" -le 0 ] 69 then 70 let "month += 12" 71 let "year -= 1" 72 fi 73 74 let "year -= $REFYR" 75 let "indexyr = $year / $CENTURY" 76 77 78 let "Days = $DIY*$year + $year/$LEAPCYCLE - $indexyr + $indexyr/$LEAPCYCLE + $ADJ_DIY*$month/$MIY + $day - $DIM" 79 # For an in-depth explanation of this algorithm, see 80 # http://home.t-online.de/home/berndt.schwerdtfeger/cal.htm 81 82 83 if [ "$Days" -gt "$MAXRETVAL" ] # If greater than 256, 84 then # then change to negative value 85 let "dindex = 0 - $Days" # which can be returned from function. 86 else let "dindex = $Days" 87 fi 88 89 return $dindex 90 91 } 92 93 94 calculate_difference () # Difference between to day indices. 95 { 96 let "diff = $1 - $2" # Global variable. 97 } 98 99 100 abs () # Absolute value 101 { # Uses global "value" variable. 102 if [ "$1" -lt 0 ] # If negative 103 then # then 104 let "value = 0 - $1" # change sign, 105 else # else 106 let "value = $1" # leave it alone. 107 fi 108 } 109 110 111 112 if [ $# -ne "$ARGS" ] # Require two command line params. 113 then 114 Param_Error 115 fi 116 117 Parse_Date $1 118 check_date $day $month $year # See if valid date. 119 120 strip_leading_zero $day # Remove any leading zeroes 121 day=$? # on day and/or month. 122 strip_leading_zero $month 123 month=$? 124 125 day_index $day $month $year 126 date1=$? 127 128 abs $date1 # Make sure it's positive 129 date1=$value # by getting absolute value. 130 131 Parse_Date $2 132 check_date $day $month $year 133 134 strip_leading_zero $day 135 day=$? 136 strip_leading_zero $month 137 month=$? 138 139 day_index $day $month $year 140 date2=$? 141 142 abs $date2 # Make sure it's positive. 143 date2=$value 144 145 calculate_difference $date1 $date2 146 147 abs $diff # Make sure it's positive. 148 diff=$value 149 150 echo $diff 151 152 exit 0 153 # Compare this script with the implementation of Gauss' Formula in C at 154 # http://buschencrew.hypermart.net/software/datedif |
Example A-9. 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 uppercase. 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-10. 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-11. "Game of Life"
1 #!/bin/bash 2 # life.sh: "Life in the Slow Lane" 3 4 # ##################################################################### # 5 # This is the Bash script version of John Conway's "Game of Life". # 6 # "Life" is a simple implementation of cellular automata. # 7 # --------------------------------------------------------------------- # 8 # On a rectangular grid, let each "cell" be either "living" or "dead". # 9 # Designate a living cell with a dot, and a dead one with a blank space.# 10 # Begin with an arbitrarily drawn dot-and-blank grid, # 11 #+ and let this be the starting generation, "generation 0". # 12 # Determine each successive generation by the following rules: # 13 # 1) Each cell has 8 neighbors, the adjoining cells # 14 #+ left, right, top, bottom, and the 4 diagonals. # 15 # 123 # 16 # 4*5 # 17 # 678 # 18 # # 19 # 2) A living cell with either 2 or 3 living neighbors remains alive. # 20 # 3) A dead cell with 3 living neighbors becomes alive (a "birth"). # 21 SURVIVE=2 # 22 BIRTH=3 # 23 # 4) All other cases result in dead cells. # 24 # ##################################################################### # 25 26 27 startfile=gen0 # Read the starting generation from the file "gen0". 28 # Default, if no other file specified when invoking script. 29 # 30 if [ -n "$1" ] # Specify another "generation 0" file. 31 then 32 if [ -e "$1" ] # Check for existence. 33 then 34 startfile="$1" 35 fi 36 fi 37 38 39 ALIVE1=. 40 DEAD1=_ 41 # Represent living and "dead" cells in the start-up file. 42 43 # This script uses a 10 x 10 grid (may be increased, 44 #+ but a large grid will will cause very slow execution). 45 ROWS=10 46 COLS=10 47 48 GENERATIONS=10 # How many generations to cycle through. 49 # Adjust this upwards, 50 #+ if you have time on your hands. 51 52 NONE_ALIVE=80 # Exit status on premature bailout, 53 #+ if no cells left alive. 54 TRUE=0 55 FALSE=1 56 ALIVE=0 57 DEAD=1 58 59 avar= # Global; holds current generation. 60 generation=0 # Initialize generation count. 61 62 # ================================================================= 63 64 65 let "cells = $ROWS * $COLS" 66 # How many cells. 67 68 declare -a initial # Arrays containing "cells". 69 declare -a current 70 71 display () 72 { 73 74 alive=0 # How many cells "alive". 75 # Initially zero. 76 77 declare -a arr 78 arr=( `echo "$1"` ) # Convert passed arg to array. 79 80 element_count=${#arr[*]} 81 82 local i 83 local rowcheck 84 85 for ((i=0; i<$element_count; i++)) 86 do 87 88 # Insert newline at end of each row. 89 let "rowcheck = $i % ROWS" 90 if [ "$rowcheck" -eq 0 ] 91 then 92 echo # Newline. 93 echo -n " " # Indent. 94 fi 95 96 cell=${arr[i]} 97 98 if [ "$cell" = . ] 99 then 100 let "alive += 1" 101 fi 102 103 echo -n "$cell" | sed -e 's/_/ /g' 104 # Print out array and change underscores to spaces. 105 done 106 107 return 108 109 } 110 111 IsValid () # Test whether cell coordinate valid. 112 { 113 114 if [ -z "$1" -o -z "$2" ] # Mandatory arguments missing? 115 then 116 return $FALSE 117 fi 118 119 local row 120 local lower_limit=0 # Disallow negative coordinate. 121 local upper_limit 122 local left 123 local right 124 125 let "upper_limit = $ROWS * $COLS - 1" # Total number of cells. 126 127 128 if [ "$1" -lt "$lower_limit" -o "$1" -gt "$upper_limit" ] 129 then 130 return $FALSE # Out of array bounds. 131 fi 132 133 row=$2 134 let "left = $row * $ROWS" # Left limit. 135 let "right = $left + $COLS - 1" # Right limit. 136 137 if [ "$1" -lt "$left" -o "$1" -gt "$right" ] 138 then 139 return $FALSE # Beyond row boundary. 140 fi 141 142 return $TRUE # Valid coordinate. 143 144 } 145 146 147 IsAlive () # Test whether cell is alive. 148 # Takes array, cell number, state of cell as arguments. 149 { 150 GetCount "$1" $2 # Get alive cell count in neighborhood. 151 local nhbd=$? 152 153 154 if [ "$nhbd" -eq "$BIRTH" ] # Alive in any case. 155 then 156 return $ALIVE 157 fi 158 159 if [ "$3" = "." -a "$nhbd" -eq "$SURVIVE" ] 160 then # Alive only if previously alive. 161 return $ALIVE 162 fi 163 164 return $DEAD # Default. 165 166 } 167 168 169 GetCount () # Count live cells in passed cell's neighborhood. 170 # Two arguments needed: 171 # $1) variable holding array 172 # $2) cell number 173 { 174 local cell_number=$2 175 local array 176 local top 177 local center 178 local bottom 179 local r 180 local row 181 local i 182 local t_top 183 local t_cen 184 local t_bot 185 local count=0 186 local ROW_NHBD=3 187 188 array=( `echo "$1"` ) 189 190 let "top = $cell_number - $COLS - 1" # Set up cell neighborhood. 191 let "center = $cell_number - 1" 192 let "bottom = $cell_number + $COLS - 1" 193 let "r = $cell_number / $ROWS" 194 195 for ((i=0; i<$ROW_NHBD; i++)) # Traverse from left to right. 196 do 197 let "t_top = $top + $i" 198 let "t_cen = $center + $i" 199 let "t_bot = $bottom + $i" 200 201 202 let "row = $r" # Count center row of neighborhood. 203 IsValid $t_cen $row # Valid cell position? 204 if [ $? -eq "$TRUE" ] 205 then 206 if [ ${array[$t_cen]} = "$ALIVE1" ] # Is it alive? 207 then # Yes? 208 let "count += 1" # Increment count. 209 fi 210 fi 211 212 let "row = $r - 1" # Count top row. 213 IsValid $t_top $row 214 if [ $? -eq "$TRUE" ] 215 then 216 if [ ${array[$t_top]} = "$ALIVE1" ] 217 then 218 let "count += 1" 219 fi 220 fi 221 222 let "row = $r + 1" # Count bottom row. 223 IsValid $t_bot $row 224 if [ $? -eq "$TRUE" ] 225 then 226 if [ ${array[$t_bot]} = "$ALIVE1" ] 227 then 228 let "count += 1" 229 fi 230 fi 231 232 done 233 234 235 if [ ${array[$cell_number]} = "$ALIVE1" ] 236 then 237 let "count -= 1" # Make sure value of tested cell itself 238 fi #+ is not counted. 239 240 241 return $count 242 243 } 244 245 next_gen () # Update generation array. 246 { 247 248 local array 249 local i=0 250 251 array=( `echo "$1"` ) # Convert passed arg to array. 252 253 while [ "$i" -lt "$cells" ] 254 do 255 IsAlive "$1" $i ${array[$i]} # Is cell alive? 256 if [ $? -eq "$ALIVE" ] 257 then # If alive, then 258 array[$i]=. #+ represent the cell as a period. 259 else 260 array[$i]="_" # Otherwise underscore 261 fi #+ (which will later be converted to space). 262 let "i += 1" 263 done 264 265 266 # let "generation += 1" # Increment generation count. 267 268 # Set variable to pass as parameter to "display" function. 269 avar=`echo ${array[@]}` # Convert array back to string variable. 270 display "$avar" # Display it. 271 echo; echo 272 echo "Generation $generation -- $alive alive" 273 274 if [ "$alive" -eq 0 ] 275 then 276 echo 277 echo "Premature exit: no more cells alive!" 278 exit $NONE_ALIVE # No point in continuing 279 fi #+ if no live cells. 280 281 } 282 283 284 # ========================================================= 285 286 # main () 287 288 # Load initial array with contents of startup file. 289 initial=( `cat "$startfile" | sed -e '/#/d' | tr -d '\n' |\ 290 sed -e 's/\./\. /g' -e 's/_/_ /g'` ) 291 # Delete lines containing '#' comment character. 292 # Remove linefeeds and insert space between elements. 293 294 clear # Clear screen. 295 296 echo # Title 297 echo "=======================" 298 echo " $GENERATIONS generations" 299 echo " of" 300 echo "\"Life in the Slow Lane\"" 301 echo "=======================" 302 303 304 # -------- Display first generation. -------- 305 Gen0=`echo ${initial[@]}` 306 display "$Gen0" # Display only. 307 echo; echo 308 echo "Generation $generation -- $alive alive" 309 # ------------------------------------------- 310 311 312 let "generation += 1" # Increment generation count. 313 echo 314 315 # ------- Display second generation. ------- 316 Cur=`echo ${initial[@]}` 317 next_gen "$Cur" # Update & display. 318 # ------------------------------------------ 319 320 let "generation += 1" # Increment generation count. 321 322 # ------ Main loop for displaying subsequent generations ------ 323 while [ "$generation" -le "$GENERATIONS" ] 324 do 325 Cur="$avar" 326 next_gen "$Cur" 327 let "generation += 1" 328 done 329 # ============================================================== 330 331 echo 332 333 exit 0 334 335 # -------------------------------------------------------------- 336 # The grid in this script has a "boundary problem". 337 # The the top, bottom, and sides border on a void of dead cells. 338 # Exercise: Change the script to have the grid wrap around, 339 # + so that the left and right sides will "touch", 340 # + as will the top and bottom. |
Example A-12. 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 University of Toronto. See the enclosed file "Moraes-COPYRIGHT" for permissions and restrictions.
Example A-13. 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-14. ftpget: Downloading files via 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 # Simple, and quick - written as a companion to ftplist 6 # -h specifies the remote host (default prep.ai.mit.edu) 7 # -d specifies the remote directory to cd to - you can provide a sequence 8 # of -d options - they will be cd'ed to in turn. If the paths are relative, 9 # make sure you get the sequence right. Be careful with relative paths - 10 # there are far too many symlinks nowadays. 11 # (default is the ftp login directory) 12 # -v turns on the verbose option of ftp, and shows all responses from the 13 # ftp server. 14 # -f remotefile[:localfile] gets the remote file into localfile 15 # -m pattern does an mget with the specified pattern. Remember to quote 16 # shell characters. 17 # -c does a local cd to the specified directory 18 # For example, 19 # ftpget -h expo.lcs.mit.edu -d contrib -f xplaces.shar:xplaces.sh \ 20 # -d ../pub/R3/fixes -c ~/fixes -m 'fix*' 21 # will get xplaces.shar from ~ftp/contrib on expo.lcs.mit.edu, and put it in 22 # xplaces.sh in the current working directory, and get all fixes from 23 # ~ftp/pub/R3/fixes and put them in the ~/fixes directory. 24 # Obviously, the sequence of the options is important, since the equivalent 25 # commands are executed by ftp in corresponding order 26 # 27 # Mark Moraes (moraes@csri.toronto.edu), Feb 1, 1989 28 # ==> Angle brackets changed to parens, so Docbook won't get indigestion. 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 TMPFILE=/tmp/ftp.$$ 39 # ==> Creates temp file, using process id of script ($$) 40 # ==> to construct filename. 41 42 SITE=`domainname`.toronto.edu 43 # ==> 'domainname' similar to 'hostname' 44 # ==> May rewrite this to parameterize this for general use. 45 46 usage="Usage: $0 [-h remotehost] [-d remotedirectory]... [-f remfile:localfile]... \ 47 [-c localdirectory] [-m filepattern] [-v]" 48 ftpflags="-i -n" 49 verbflag= 50 set -f # So we can use globbing in -m 51 set x `getopt vh:d:c:m:f: $*` 52 if [ $? != 0 ]; then 53 echo $usage 54 exit 65 55 fi 56 shift 57 trap 'rm -f ${TMPFILE} ; exit' 0 1 2 3 15 58 echo "user anonymous ${USER-gnu}@${SITE} > ${TMPFILE}" 59 # ==> Added quotes (recommended in complex echoes). 60 echo binary >> ${TMPFILE} 61 for i in $* # ==> Parse command line args. 62 do 63 case $i in 64 -v) verbflag=-v; echo hash >> ${TMPFILE}; shift;; 65 -h) remhost=$2; shift 2;; 66 -d) echo cd $2 >> ${TMPFILE}; 67 if [ x${verbflag} != x ]; then 68 echo pwd >> ${TMPFILE}; 69 fi; 70 shift 2;; 71 -c) echo lcd $2 >> ${TMPFILE}; shift 2;; 72 -m) echo mget "$2" >> ${TMPFILE}; shift 2;; 73 -f) f1=`expr "$2" : "\([^:]*\).*"`; f2=`expr "$2" : "[^:]*:\(.*\)"`; 74 echo get ${f1} ${f2} >> ${TMPFILE}; shift 2;; 75 --) shift; break;; 76 esac 77 done 78 if [ $# -ne 0 ]; then 79 echo $usage 80 exit 65 # ==> Changed from "exit 2" to conform with standard. 81 fi 82 if [ x${verbflag} != x ]; then 83 ftpflags="${ftpflags} -v" 84 fi 85 if [ x${remhost} = x ]; then 86 remhost=prep.ai.mit.edu 87 # ==> Rewrite to match your favorite ftp site. 88 fi 89 echo quit >> ${TMPFILE} 90 # ==> All commands saved in tempfile. 91 92 ftp ${ftpflags} ${remhost} < ${TMPFILE} 93 # ==> Now, tempfile batch processed by ftp. 94 95 rm -f ${TMPFILE} 96 # ==> Finally, tempfile deleted (you may wish to copy it to a logfile). 97 98 99 # ==> Exercises: 100 # ==> --------- 101 # ==> 1) Add error checking. 102 # ==> 2) Add bells & whistles. |
+
Antek Sawicki contributed the following script, which makes very clever use of the parameter substitution operators discussed in Section 9.3.
Example A-15. password: Generating random 8-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 LENGTH="8" 12 # ==> May change 'LENGTH' for longer password, of course. 13 14 15 while [ "${n:=1}" -le "$LENGTH" ] 16 # ==> Recall that := is "default substitution" operator. 17 # ==> So, if 'n' has not been initialized, set it to 1. 18 do 19 PASS="$PASS${MATRIX:$(($RANDOM%${#MATRIX})):1}" 20 # ==> Very clever, but tricky. 21 22 # ==> Starting from the innermost nesting... 23 # ==> ${#MATRIX} returns length of array MATRIX. 24 25 # ==> $RANDOM%${#MATRIX} returns random number between 1 26 # ==> and length of MATRIX - 1. 27 28 # ==> ${MATRIX:$(($RANDOM%${#MATRIX})):1} 29 # ==> returns expansion of MATRIX at random position, by length 1. 30 # ==> See {var:pos:len} parameter substitution in Section 3.3.1 31 # ==> and following examples. 32 33 # ==> PASS=... simply pastes this result onto previous PASS (concatenation). 34 35 # ==> To visualize this more clearly, uncomment the following line 36 # ==> echo "$PASS" 37 # ==> to see PASS being built up, 38 # ==> one character at a time, each iteration of the loop. 39 40 let n+=1 41 # ==> Increment 'n' for next pass. 42 done 43 44 echo "$PASS" # ==> Or, redirect to file, as desired. 45 46 exit 0 |
+
James R. Van Zandt contributed this script, which uses named pipes and, in his words, "really exercises quoting and escaping".
Example A-16. 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 |
+
Stephane Chazelas contributed the following script to demonstrate that generating prime numbers does not require arrays.
Example A-17. 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 17 and 25 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 gave permission to use his elegant tree script.
Example A-18. tree: Displaying a directory tree
1 #!/bin/sh 2 # @(#) tree 1.1 30/11/95 by Jordi Sanfeliu 3 # email: mikaku@arrakis.es 4 # 5 # Initial version: 1.0 30/11/95 6 # Next version : 1.1 24/02/97 Now, with symbolic links 7 # Patch by : Ian Kjos, to support unsearchable dirs 8 # email: beth13@mail.utexas.edu 9 # 10 # Tree is a tool for view the directory tree (obvious :-) ) 11 # 12 13 # ==> 'Tree' script used here with the permission of its author, Jordi Sanfeliu. 14 # ==> Comments added by the author of this document. 15 # ==> Argument quoting added. 16 17 18 search () { 19 for dir in `echo *` 20 # ==> `echo *` lists all the files in current working directory, without line breaks. 21 # ==> Similar effect to for dir in * 22 # ==> but "dir in `echo *`" will not handle filenames with blanks. 23 do 24 if [ -d "$dir" ] ; then # ==> If it is a directory (-d)... 25 zz=0 # ==> Temp variable, keeping track of directory level. 26 while [ $zz != $deep ] # Keep track of inner nested loop. 27 do 28 echo -n "| " # ==> Display vertical connector symbol, 29 # ==> with 2 spaces & no line feed in order to indent. 30 zz=`expr $zz + 1` # ==> Increment zz. 31 done 32 if [ -L "$dir" ] ; then # ==> If directory is a symbolic link... 33 echo "+---$dir" `ls -l $dir | sed 's/^.*'$dir' //'` 34 # ==> Display horiz. connector and list directory name, but... 35 # ==> delete date/time part of long listing. 36 else 37 echo "+---$dir" # ==> Display horizontal connector symbol... 38 # ==> and print directory name. 39 if cd "$dir" ; then # ==> If can move to subdirectory... 40 deep=`expr $deep + 1` # ==> Increment depth. 41 search # with recursivity ;-) 42 # ==> Function calls itself. 43 numdirs=`expr $numdirs + 1` # ==> Increment directory count. 44 fi 45 fi 46 fi 47 done 48 cd .. # ==> Up one directory level. 49 if [ "$deep" ] ; then # ==> If depth = 0 (returns TRUE)... 50 swfi=1 # ==> set flag showing that search is done. 51 fi 52 deep=`expr $deep - 1` # ==> Decrement depth. 53 } 54 55 # - Main - 56 if [ $# = 0 ] ; then 57 cd `pwd` # ==> No args to script, then use current working directory. 58 else 59 cd $1 # ==> Otherwise, move to indicated directory. 60 fi 61 echo "Initial directory = `pwd`" 62 swfi=0 # ==> Search finished flag. 63 deep=0 # ==> Depth of listing. 64 numdirs=0 65 zz=0 66 67 while [ "$swfi" != 1 ] # While flag not set... 68 do 69 search # ==> Call function after initializing variables. 70 done 71 echo "Total directories = $numdirs" 72 73 exit 0 74 # ==> Challenge: try to figure out exactly how this script works. |
Noah Friedman gave permission to use his string function script, which essentially reproduces some of the C-library string manipulation functions.
Example A-19. string functions: C-like string functions
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 |
Stephane Chazelas demonstrates object-oriented programming in a Bash 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 6 person.new() # Looks almost like a class declaration in C++. 7 { 8 local obj_name=$1 name=$2 firstname=$3 birthdate=$4 9 10 eval "$obj_name.set_name() { 11 eval \"$obj_name.get_name() { 12 echo \$1 13 }\" 14 }" 15 16 eval "$obj_name.set_firstname() { 17 eval \"$obj_name.get_firstname() { 18 echo \$1 19 }\" 20 }" 21 22 eval "$obj_name.set_birthdate() { 23 eval \"$obj_name.get_birthdate() { 24 echo \$1 25 }\" 26 eval \"$obj_name.show_birthdate() { 27 echo \$(date -d \"1/1/1970 0:0:\$1 GMT\") 28 }\" 29 eval \"$obj_name.get_age() { 30 echo \$(( (\$(date +%s) - \$1) / 3600 / 24 / 365 )) 31 }\" 32 }" 33 34 $obj_name.set_name $name 35 $obj_name.set_firstname $firstname 36 $obj_name.set_birthdate $birthdate 37 } 38 39 echo 40 41 person.new self Bozeman Bozo 101272413 42 # Create an instance of "person.new" (actually passing args to the function). 43 44 self.get_firstname # Bozo 45 self.get_name # Bozeman 46 self.get_age # 28 47 self.get_birthdate # 101272413 48 self.show_birthdate # Sat Mar 17 20:13:33 MST 1973 49 50 echo 51 52 # typeset -f 53 # to see the created functions (careful, it scrolls off the page). 54 55 exit 0 |