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: A script for viewing formatted man pages
1 #!/bin/bash 2 3 # Formats the source of a man page for viewing in a user directory. 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 if [ -z $1 ] 8 then 9 echo "Usage: `basename $0` [filename]" 10 exit 1 11 fi 12 13 groff -Tascii -man $1 | less 14 # From the man page for groff. 15 16 exit 0 |
Example A-2. rn: A simple-minded file rename utility
This script is a modification of Example 3-58.
1 #! /bin/bash 2 # 3 # Very simpleminded filename "rename" utility. 4 # Based on "lowercase.sh". 5 6 7 if [ $# -ne 2 ] 8 then 9 echo "Usage: `basename $0` old-pattern new-pattern" 10 # As in "rn gif jpg", which renames all gif files in working directory to jpg. 11 exit 1 12 fi 13 14 number=0 # Keeps track of how many files actually renamed. 15 16 17 for filename in *$1* #Traverse all matching files in directory. 18 do 19 if [ -f $filename ] # If finds match... 20 then 21 fname=`basename $filename` # Strip off path. 22 n=`echo $fname | sed -e "s/$1/$2/"` # Substitute new for old in filename. 23 mv $fname $n # Rename. 24 let "number += 1" 25 fi 26 done 27 28 if [ $number -eq 1 ] # For correct grammar. 29 then 30 echo "$number file renamed." 31 else 32 echo "$number files renamed." 33 fi 34 35 exit 0 36 37 38 # Exercise for reader: 39 # What type of files will this not work on? 40 # How to fix this? |
Example A-3. encryptedpw: A script for uploading to an ftp site, using a locally encrypted password
1 #!/bin/bash 2 3 # Example 3-71 modified to use encrypted password. 4 5 if [ -z $1 ] 6 then 7 echo "Usage: `basename $0` filename" 8 exit 1 9 fi 10 11 Username=bozo 12 # Change to suit. 13 14 Filename=`basename $1` 15 # Strips pathname out of file name 16 17 Server="XXX" 18 Directory="YYY" 19 # Change above to actual server name & directory. 20 21 22 password=`cruft <pword` 23 # "pword" is the file containing encrypted password. 24 # Uses the author's own "cruft" file encryption package, 25 # based on onetime pad algorithm, 26 # and obtainable from: 27 # Primary-site: ftp://metalab.unc.edu /pub/Linux/utils/file 28 # cruft-0.2.tar.gz [16k] 29 30 31 ftp -n $Server <<End-Of-Session 32 # -n option disables auto-logon 33 34 user $Username $Password 35 binary 36 bell 37 # Ring 'bell' after each file transfer 38 cd $Directory 39 put $Filename 40 bye 41 End-Of-Session 42 43 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-4. behead: A script for 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 HOWTO. 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 for the reader: 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-5. ftpget: A script for 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 HOWTO.
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 1
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 $*
62 # --> Parse command line args.
63 do
64 case $i in
65 -v) verbflag=-v; echo hash >> ${TMPFILE}; shift;;
66 -h) remhost=$2; shift 2;;
67 -d) echo cd $2 >> ${TMPFILE};
68 if [ x${verbflag} != x ]; then
69 echo pwd >> ${TMPFILE};
70 fi;
71 shift 2;;
72 -c) echo lcd $2 >> ${TMPFILE}; shift 2;;
73 -m) echo mget "$2" >> ${TMPFILE}; shift 2;;
74 -f) f1=`expr "$2" : "\([^:]*\).*"`; f2=`expr "$2" : "[^:]*:\(.*\)"`;
75 echo get ${f1} ${f2} >> ${TMPFILE}; shift 2;;
76 --) shift; break;;
77 esac
78 done
79 if [ $# -ne 0 ]; then
80 echo $usage
81 exit 2
82 fi
83 if [ x${verbflag} != x ]; then
84 ftpflags="${ftpflags} -v"
85 fi
86 if [ x${remhost} = x ]; then
87 remhost=prep.ai.mit.edu
88 # --> Rewrite to match your favorite ftp site.
89 fi
90 echo quit >> ${TMPFILE}
91 # --> All commands saved in tempfile.
92
93 ftp ${ftpflags} ${remhost} < ${TMPFILE}
94 # --> Now, tempfile batch processed by ftp.
95
96 rm -f ${TMPFILE}
97 # --> Finally, tempfile deleted (you may wish to copy it to a logfile).
98
99
100 # --> Exercises for reader:
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 3.3.1.
Example A-6. password: A script for generating random 8-character passwords
1 #!/bin/bash
2 # May need to be invoked with #!/bin/bash2 on some machines.
3 #
4 # Random password generator for bash 2.x by Antek Sawicki <tenox@tenox.tc>,
5 # who generously gave permission to the HOWTO author to use it here.
6 #
7 # ==> Comments added by HOWTO 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 # ==> $RANDOM%${#MATRIX} returns random number between 1 and length of MATRIX.
25
26 # ==> ${MATRIX:$(($RANDOM%${#MATRIX})):1}
27 # ==> returns expansion of MATRIX at random position, by length 1.
28 # ==> See {var:pos:len} parameter substitution in Section 3.3.1 and following examples.
29
30 # ==> PASS=... simply pastes this result onto previous PASS (concatenation).
31
32 # ==> To visualize this more clearly, uncomment the following line
33 # ==> echo "$PASS"
34 # ==> to see PASS being built up, one character at a time, each iteration of the loop.
35
36 let n+=1
37 # ==> Increment 'n' for next pass.
38 done
39
40 echo "$PASS"
41 #== Or, redirect to file, as desired. |
+
James R. Van Zandt contributed this script, which uses named pipes and, in his words, "really exercises quoting and escaping".
Example A-7. fifo: A script for 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 HOWTO.
5
6
7 HERE=`uname -n`
8 # ==> hostname
9 THERE=bilbo
10 echo "starting remote backup to $THERE at `date +%r`"
11 # ==> `date +%r` returns time in 12-hour format, i.e. "08:08:34 PM".
12
13 # make sure /pipe really is a pipe and not a plain file
14 rm -rf /pipe
15 mkfifo /pipe
16 # ==> Create a "named pipe", named "/pipe".
17
18 # ==> 'su xyz' runs commands as user "xyz".
19 # ==> 'ssh' invokes secure shell (remote login client).
20 su xyz -c "ssh $THERE \"cat >/home/xyz/backup/${HERE}-daily.tar.gz\" < /pipe"&
21 cd /
22 tar -czf - bin boot dev etc home info lib man root sbin share usr var >/pipe
23 # ==> Uses named pipe, /pipe, to communicate between processes:
24 # ==> 'tar/gzip' writes to /pipe and 'ssh' reads from /pipe.
25
26 # ==> The end result is this backs up the main directories, from / on down.
27
28 # ==> What are the advantages of a "named pipe" in this situation,
29 # ==> as opposed to an "anonymous pipe", with |?
30 # ==> Will an anonymous pipe even work here?
31
32
33 exit 0 |
+
Jordi Sanfeliu gave permission to use his elegant tree script.
Example A-8. tree: A script for 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 HOWTO author.
15
16
17 search () {
18 for dir in `echo *`
19 # ==> `echo *` lists all the files in current working directory, without line breaks.
20 # ==> Same effect as for dir in *
21 do
22 if [ -d $dir ] ; then # ==> If it is a directory (-d)...
23 zz=0 # ==> Temp variable, keeping track of directory level.
24 while [ $zz != $deep ] # Keep track of inner nested loop.
25 do
26 echo -n "| " # ==> Display vertical connector symbol,
27 # ==> with 2 spaces & no line feed in order to indent.
28 zz=`expr $zz + 1` # ==> Increment zz.
29 done
30 if [ -L $dir ] ; then # ==> If directory is a symbolic link...
31 echo "+---$dir" `ls -l $dir | sed 's/^.*'$dir' //'`
32 # ==> Display horiz. connector and list directory name, but...
33 # ==> delete date/time part of long listing.
34 else
35 echo "+---$dir" # ==> Display horizontal connector symbol...
36 # ==> and print directory name.
37 if cd $dir ; then # ==> If can move to subdirectory...
38 deep=`expr $deep + 1` # ==> Increment depth.
39 search # with recursivity ;-)
40 # ==> Function calls itself.
41 numdirs=`expr $numdirs + 1` # ==> Increment directory count.
42 fi
43 fi
44 fi
45 done
46 cd .. # ==> Up one directory level.
47 if [ $deep ] ; then # ==> If depth = 0 (returns TRUE)...
48 swfi=1 # ==> set flag showing that search is done.
49 fi
50 deep=`expr $deep - 1` # ==> Decrement depth.
51 }
52
53 # - Main -
54 if [ $# = 0 ] ; then
55 cd `pwd` # ==> No args to script, then use current working directory.
56 else
57 cd $1 # ==> Otherwise, move to indicated directory.
58 fi
59 echo "Initial directory = `pwd`"
60 swfi=0 # ==> Search finished flag.
61 deep=0 # ==> Depth of listing.
62 numdirs=0
63 zz=0
64
65 while [ $swfi != 1 ] # While flag not set...
66 do
67 search # ==> Call function after initializing variables.
68 done
69 echo "Total directories = $numdirs"
70
71
72
73 # ==> Challenge to reader: try to figure out exactly how this script works. |