ReferenceIntroduction
A shell is a command line interpretor. It takes commands and executes
them. As such, it implements a programming language. The Bourne
shell is used to create shell scripts -- ie. programs that are
interpreted/executed by the shell. You can write shell scripts with
the C-shell; however, this is not covered here.
Creating a ScriptSuppose you often type the commandfind . -name file -printand you'd rather type a simple command, say sfind fileCreate a shell script % cd ~/bin % emacs sfind % page sfind find . -name $1 -print % chmod a+x sfind % rehash % cd /usr/local/bin % sfind tcsh ./shells/tcsh ObservationsThis quick example is far from adequate but some observations:
#!/bin/shAll Bourne Shell scripts should begin with the sequence#!/bin/shFrom the man page for exec(2): "On the first line of an interpreter script, following the "#!", is the name of a program which should be used to interpret the contents of the file. For instance, if the first line contains "#! /bin/sh", then the con- tents of the file are executed as a shell script." You can get away without this, but you shouldn't. All good scripts state the interpretor explicitly. Long ago there was just one (the Bourne Shell) but these days there are many interpretors -- Csh, Ksh, Bash, and others. CommentsComments are any text beginning with the pound (#) sign. A comment can start anywhere on a line and continue until the end of the line.Search PathAll shell scripts should include a search path specifica- tion:PATH=/usr/ucb:/usr/bin:/bin; export PATHA PATH specification is recommended -- often times a script will fail for some people because they have a different or incomplete search path. The Bourne Shell does not export environment variables to children unless explicitly instructed to do so by using the export command. Argument CheckingA good shell script should verify that the arguments sup- plied (if any) are correct.if [ $# -ne 3 ]; then echo 1>&2 Usage: $0 19 Oct 91 exit 127 fiThis script requires three arguments and gripes accordingly.
Exit statusAll Unix utilities should return an exit status.# is the year out of range for me? if [ $year -lt 1901 -o $year -gt 2099 ]; then echo 1>&2 Year \"$year\" out of range exit 127 fi etc... # All done, exit ok exit 0A non-zero exit status indicates an error condition of some sort while a zero exit status indicates things worked as expected. On BSD systems there's been an attempt to categorize some of the more common exit status codes. See /usr/include/sysexits.h. Using exit statusExit codes are important for those who use your code. Many constructs test on the exit status of a command.The conditional construct is: if command; then command fiFor example, if tty -s; then echo Enter text end with \^D fiYour code should be written with the expectation that others will use it. Making sure you return a meaningful exit status will help. Stdin, Stdout, StderrStandard input, output, and error are file descriptors 0, 1, and 2. Each has a particular role and should be used accordingly:# is the year out of range for me? if [ $year -lt 1901 -o $year -gt 2099 ]; then echo 1>&2 Year \"$year\" out of my range exit 127 fi etc... # ok, you have the number of days since Jan 1, ... case `expr $days % 7` in 0) echo Mon;; 1) echo Tue;; etc...Error messages should appear on stderr not on stdout! Output should appear on stdout. As for input/output dialogue: # give the fellow a chance to quit if tty -s ; then echo This will remove all files in $* since ... echo $n Ok to procede? $c; read ans case "$ans" in n*|N*) echo File purge abandoned; exit 0 ;; esac RM="rm -rfi" else RM="rm -rf" fiNote: this code behaves differently if there's a user to communicate with (ie. if the standard input is a tty rather than a pipe, or file, or etc. See tty(1)). Language Constructs
Substitute values for variable and perform task: for variable in word ... do command doneFor example: for i in `cat $LOGS` do mv $i $i.$TODAY cp /dev/null $i chmod 664 $i doneAlternatively you may see: for variable in word ...; do command; done Switch to statements depending on pattern match case word in [ pattern [ | pattern ... ] ) command ;; ] ... esacFor example: case "$year" in [0-9][0-9]) year=19${year} years=`expr $year - 1901` ;; [0-9][0-9][0-9][0-9]) years=`expr $year - 1901` ;; *) echo 1>&2 Year \"$year\" out of range ... exit 127 ;; esac Test exit status of command and branch if command then command [ else command ] fiFor example: if [ $# -ne 3 ]; then echo 1>&2 Usage: $0 19 Oct 91 exit 127 fiAlternatively you may see: if command; then command; [ else command; ] fi Repeat task while command returns good exit status. {while | until} command do command doneFor example: # for each argument mentioned, purge that directory while [ $# -ge 1 ]; do _purge $1 shift doneAlternatively you may see: while command; do command; done Variables are sequences of letters, digits, or underscores beginning with a letter or underscore. To get the contents of a variable you must prepend the name with a $. Numeric variables (eg. like $1, etc.) are positional vari- ables for argument communication.
Special characters to terminate words: ; & ( ) | ^ < > new-line space tabThese are for command sequences, background jobs, etc. To quote any of these use a backslash (\) or bracket with quote marks ("" or ''). Single Quotes Within single quotes all characters are quoted -- including the backslash. The result is one word. grep :${gid}: /etc/group | awk -F: '{print $1}'Double Quotes Within double quotes you have variable subsitution (ie. the dollar sign is interpreted) but no file name generation (ie. * and ? are quoted). The result is one word. if [ ! "${parent}" ]; then parent=${people}/${group}/${user} fiBack Quotes Back quotes mean run the command and substitute the output. if [ "`echo -n`" = "-n" ]; then n="" c="\c" else n="-n" c="" fiand TODAY=`(set \`date\`; echo $1)` Functions are a powerful feature that aren't used often enough. Syntax is name () { commands }For example: # Purge a directory _purge() { # there had better be a directory if [ ! -d $1 ]; then echo $1: No such directory 1>&2 return fi etc... }Within a function the positional parmeters $0, $1, etc. are the arguments to the function (not the arguments to the script). Within a function use return instead of exit. Functions are good for encapsulations. You can pipe, redi- rect input, etc. to functions. For example: # deal with a file, add people one at a time do_file() { while parse_one etc... } etc... # take standard input (or a specified file) and do it. if [ "$1" != "" ]; then cat $1 | do_file else do_file fi You can execute shell scripts from within shell scripts. A couple of choices: sh command This runs the shell script as a separate shell. For example, on Sun machines in /etc/rc: sh /etc/rc.local. command This runs the shell script from within the current shell script. For example: # Read in configuration information . /etc/hostconfigWhat are the virtues of each? What's the difference? The second form is useful for configuration files where environment variable are set for the script. For example: for HOST in $HOSTS; do # is there a config file for this host? if [ -r ${BACKUPHOME}/${HOST} ]; then . ${BACKUPHOME}/${HOST} fi etc...Using configuration files in this manner makes it possible to write scripts that are automatically tailored for differ- ent situations. Some Tricks
Based on An Introduction to Shell Programing by: Reg Quinton Computing and Communications Services The University of Western Ontario London, Ontario N6A 5B7 Canada |
KB >