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 sed ' 30 s/^>// 31 s/^ *>// 32 s/^ *// 33 s/ *// 34 ' $1 | fold -s --width=$MAXWIDTH 35 # -s option to "fold" breaks lines at whitespace, if possible. 36 37 # This script was inspired by an article in a well-known trade journal 38 # extolling a 164K Windows utility with similar functionality. 39 40 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 # Exercise: 45 # -------- 46 # What type of files will this not work on? 47 # How can this be fixed? |
Example A-4. encryptedpw: Uploading to an ftp site, using a locally encrypted password
1 #!/bin/bash 2 3 # Example "ex72.sh" modified to use encrypted password. 4 5 # Note that this is still 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-5. copy-cd: Copying a data CD
1 #!/bin/bash 2 # copy-cd.sh: copying a data CD 3 4 CDROM=/dev/cdrom # CD ROM device 5 OF=/home/bozo/projects/cdimage.iso # output file 6 # /xxxx/xxxxxxx/ Change to suit your system. 7 BLOCKSIZE=2048 8 SPEED=2 # May use higher speed if supported. 9 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-6. 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-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 ARGS=1 24 E_BADARGS=65 25 26 if [ $# -ne $ARGS ] # Need a seed number. 27 then 28 echo "Usage: `basename $0` NUMBER" 29 exit $E_BADARGS 30 fi 31 32 MAX_ITERATIONS=200 33 # For large seed numbers (>32000), increase MAX_ITERATIONS. 34 35 h=$1 # Seed 36 37 echo 38 echo "C($1) --- $MAX_ITERATIONS Iterations" 39 echo 40 41 for ((i=1; i<=MAX_ITERATIONS; i++)) 42 do 43 44 echo -n "$h " 45 # ^^^^^ 46 # tab 47 48 let "remainder = h % 2" 49 if [ "$remainder" -eq 0 ] # Even? 50 then 51 let "h /= 2" # Divide by 2. 52 else 53 let "h = h*3 + 1" # Multiply by 3 and add 1. 54 fi 55 56 57 COLUMNS=10 # Output 10 values per line. 58 let "line_break = i % $COLUMNS" 59 if [ "$line_break" -eq 0 ] 60 then 61 echo 62 fi 63 64 done 65 66 echo 67 68 # For more information on this mathematical function, 69 #+ see "Computers, Pattern, Chaos, and Beauty", by Pickover, p. 185 ff., 70 #+ as listed in the bibliography. 71 72 exit 0 |
+
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-8. 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-9. 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-10. 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-11. 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-12. Generating prime numbers using the modulo operator
1 #!/bin/bash
2 # primes.sh: Generate prime numbers, without using arrays.
3
4 # This does *not* use the classic "Sieve of Erastosthenes" algorithm,
5 #+ but instead uses the more intuitive method of testing each candidate number
6 #+ for factors (divisors), using the "%" modulo operator.
7 #
8 # Script contributed by Stephane Chazelas,
9
10
11 LIMIT=1000 # Primes 2 - 1000
12
13 Primes()
14 {
15 (( n = $1 + 1 )) # Bump to next integer.
16 shift # Next parameter in list.
17 # echo "_n=$n i=$i_"
18
19 if (( n == LIMIT ))
20 then echo $*
21 return
22 fi
23
24 for i; do # "i" gets set to "@", previous values of $n.
25 # echo "-n=$n i=$i-"
26 (( i * i > n )) && break # Optimization.
27 (( n % i )) && continue # Sift out non-primes using modulo operator.
28 Primes $n $@ # Recursion inside loop.
29 return
30 done
31
32 Primes $n $@ $n # Recursion outside loop.
33 # Successively accumulate positional parameters.
34 # "$@" is the accumulating list of primes.
35 }
36
37 Primes 1
38
39 exit 0
40
41 # Uncomment lines 17 and 25 to help figure out what is going on.
42
43 # Compare the speed of this algorithm for generating primes
44 # with the Sieve of Erastosthenes (ex68.sh).
45
46 # Exercise: Rewrite this script without recursion, for faster execution. |
+
Jordi Sanfeliu gave permission to use his elegant tree script.
Example A-13. 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-14. 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 a Bash script.
Example A-15. 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 |