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. |