Appendix A. Contributed Scripts

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.