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 |