LevSelector.com |
sh (Bourne Shell)
• intro
• simple sh tutorial
- part 1
• simple sh tutorial
- part 2
• simple sh tutorial
- part 3
• simple sh tutorial
- part 4
• simple sh tutorial
- part 5
• examples 1
intro | home - top of the page - |
sh - Bourne Shell
• www.columbia.edu/acis/tutor/Unixhelp/scrpt_.html
- simple shell tutorial
• theory.uwinnipeg.ca/UNIXhelp/scrpt/
- simple shell tutorial
• steve-parker.org/sh/sh.shtml
- tutorial
• www.ooblick.com/text/sh/
- tutorial (by Andrew Arensburger)
• www.geocities.com/~gregl/htm/bourne_shell_tutorial.htm
- shell programming tutorial
• www.ling.helsinki.fi/users/reriksso/unix/shell.html
- An Introduction to the Unix Shell - by S. R. Bourne
• nim.cit.cornell.edu/usr/share/man/info/en_US/a_doc_lib/aixuser/usrosdev/bourne_shell.htm
- yet another one
• www.torget.se/users/d/Devlin/shell/
- UNIX Bourne Shell Programming
• www.i-cram.com/cgi-bin/page.cgi?g=Unix%2FShell%2FScripting%2Findex.html
- many tutorials
Books:
"Portable Shell Scripting" by Bruce Blinn |
simple sh tutorial - part 1 | home - top of the page - |
How to write a shell script
Introduction
A shell is a command line interpretor. It takes commands and executes them. You can put those commands in a file - thus you get a program (shell script) written in shell as a programming language.
Creating a Script
Suppose you often type the command
find . -name file -print
and you'd rather type a simple command, say
sfind file
Let's create a shell script file "sfind'
in your ~/bin directory (make sure that it is in your path), put there
2 lines:
#!/bin/sh
# this is a comment find . -name $1 -print # this is a comment # this is a comment |
Save this file and make it executable:
chmod a+x sfind
rehash
That's it.
Path Specification
All shell scripts should include a search path specification:
PATH=/usr/ucb:/usr/bin:/bin;
export PATH
A PATH specification is recommended -- often times a script will fail
for some people because they have a different or incomplete search path.
Note the export PATH command - the Bourne Shell does not export environment
variables to children unless explicitly instructed to do so by using the
export command.
Argument Checking
A good shell script should verify that the arguments supplied (if any)
are correct.
#!/bin/sh
if [ $# -ne 3 ]; then
|
This script requires three arguments and gripes accordingly.
Exit status
All Unix utilities should return an exit status.
#!/bin/sh
# is the year out of range for me? if [ $year -lt 1901 -o $year -gt
2099 ]; then
# etc. exit 0 |
A 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 status
Exit codes are important for those who use your code. Many constructs
test on the exit status of a command.
The conditional construct is:
#!/bin/sh
if command; then
# For example: if tty -s; then
|
Stdin, Stdout, Stderr
Standard input, output, and error are file descriptors 0, 1, and 2.
Each has a particular role and should be used accordingly:
#!/bin/sh
# is the year out of range for me?
# etc...
case `expr $days % 7` in
# etc...
if tty -s ; then
|
simple sh tutorial - part 2 | home - top of the page - |
For loop iteration:
#!/bin/sh
for variable in word ...
# For example: for i in `cat $LOGS`
# Alternatively you may see: for variable in word ...;
do command; done
|
Case:
#!/bin/sh
# Switch to statements depending on pattern match case word in
# For example: case "$year" in [0-9][0-9])
|
Conditional Execution:
#!/bin/sh
# Test exit status of command and branch if command
# For example: if [ $# -ne 3 ]; then
# Alternatively you may see: if command; then command; [ else command; ] fi |
While/Until Iteration:
#!/bin/sh
# Repeat task while command returns good exit
status.
# For example:
while [ $# -ge 1 ]; do
# Alternatively you may see: while command; do command; done |
simple sh tutorial - part 3 | home - top of the page - |
Variables:
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.
Variable Assignment:
Assign a value to a variable by variable=value.
For example:
PATH=/usr/ucb:/usr/bin:/bin;
export PATH
or
TODAY=`(set \`date\`; echo
$1)`
Exporting Variables:
Variables are not exported to children unless explicitly marked.
#!/bin/sh
# We MUST have a DISPLAY environment variable if [ "$DISPLAY" = "" ];
then
# Likewise, for variables like the PRINTER
which you want honored by lpr(1).
PRINTER=PostScript; export PRINTER # Note: that the Cshell exports all environment variables. |
Referencing Variables:
Use $variable (or, if necessary,
${variable})
to reference the value.
#!/bin/sh
# Most user's have a /bin of their own if [ "$USER" != "root" ];
then
# The braces are required for concatenation constructs. $p_01
# returns the value of the variable called "p_01".
|
Conditional Reference:
${variable-word} - If the variable
has been set, use it's value, else use word.
POSTSCRIPT=${POSTSCRIPT-PostScript};
export POSTSCRIPT
${variable:-word} - If the variable has been set and is not null, use it's value, else use word.
These are useful constructions for honoring the user environment. Ie. the user of the script can override variable assignments. Cf. programs like lpr(1) honor the PRINTER environment variable, you can do the same trick with your shell scripts.
${variable:?word} - If variable
is set use it's value, else print out word and exit. Useful for bailing
out.
Arguments:
Command line arguments to shell scripts are positional variables:
$0, $1, ...
Where $0 is the command, and the rest the arguments.
$# - The number of arguments.
$*, $@ - All the arguments as a blank separated string. Watch out for "$*" vs. "$@".
And, some commands:
shift - Shift the postional variables
down one and decrement number of arguments.
set arg arg ... - Set the
positional variables to the argument list.
Command line parsing uses shift:
#!/bin/sh
# parse argument list while [ $# -ge 1 ]; do
# A use of the set command:
TODAY=`(set \`date\`; echo $1)` cd $SPOOL for i in `cat $LOGS`
|
Special Variables:
$$ - Current
process id. This is very useful for constructing temporary files.
tmp=/tmp/cal0$$
trap "rm -f $tmp /tmp/cal1$$ /tmp/cal2$$"
trap exit 1 2 13 15
/usr/lib/calprog >$tmp
$? - The exit
status of the last command.
$command
# Run target
file if no errors and ...
if [ $? -eq 0 ]
then
etc...
fi
simple sh tutorial - part 4 | home - top of the page - |
Quotes/Special Characters: - Special characters to terminate
words:
; & ( )
| ^ < > new-line space tab
These 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}
fi
Back Quotes: - Back quotes mean run the command
and substitute the output.
if [ "`echo -n`" = "-n" ]; then
n=""
c="\c"
else
n="-n"
c=""
fi
and
TODAY=`(set \`date\`; echo $1)`
Functions:
Functions are a powerful feature that aren't used often enough.
Syntax is
name ()
{
commands
}
For example:
#!/bin/sh
# Purge a directory _purge()
if [ ! -d $1 ]; then
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, redirect input, etc. to functions.
For example:
# deal with a file, add people one at a time
do_file()
etc...
etc... # take standard input (or a specified file) and do it. if [ "$1" != "" ]; then
|
Sourcing commands:
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/hostconfig
What 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:
#!/bin/sh
for HOST in $HOSTS; do # is there a config file for this host? if [ -r ${BACKUPHOME}/${HOST}
]; then
|
Using configuration files in this manner makes it possible to write scripts that are automatically tailored for different situations.
Some Tricks
Test:
The most powerful command is test(1).
if test expression; then
etc...
and (note the matching bracket argument)
if [ expression ]; then
etc...
On System V machines this is a builtin (check out the command /bin/test).
On BSD systems (like the Suns) compare the command /usr/bin/test with
/usr/bin/[.
Useful expressions are:
test { -w, -r, -x, -s, ... } filename
- is file writeable, readable, executeable, empty, etc?
test n1 { -eq, -ne, -gt, ... } n2
- are numbers equal, not equal, greater than, etc.?
test s1 { =, != } s2
- Are strings the same or different?
test cond1 { -o, -a } cond2 - Binary
or; binary and; use ! for unary negation.
For example:
if [ $year -lt 1901
-o $year -gt 2099 ]; then
echo 1>&2 Year \"$year\" out of range exit 127 fi |
Learn this command inside out! It does a lot for you.
String matching:
The test command provides limited string matching tests. A more
powerful trick is to match strings with the case switch.
# parse argument list
while [ $# -ge 1 ]; do
shift done |
Of course getopt would work much better.
simple sh tutorial - part 5 | home - top of the page - |
SysV vs BSD echo:
On BSD systems to get a prompt you'd say:
echo -n Ok to procede?;
read ans
On SysV systems you'd say:
echo Ok to procede? \c;
read ans
In an effort to produce portable code we've been using:
#!/bin/sh
# figure out what kind of echo to use if [ "`echo -n`" = "-n"
]; then
etc... echo $n Ok to procede? $c; read ans |
Is there a person?
The Unix tradition is that programs should execute as quietly as possible.
Especially for pipelines, cron jobs, etc. User prompts aren't required
if there's no user.
# If there's a person out
there, prod him a bit.
if tty -s; then
echo Enter text end with \^D
fi
The tradition also extends to output.
# If the output is to a
terminal, be verbose
if tty -s <&1; then
verbose=true
else
verbose=false
fi
Beware: just because stdin is a tty that doesn't mean that stdout is too. User prompts should be directed to the user terminal.
# If there's a person out
there, prod him a bit.
if tty -s; then
echo Enter text end with \^D >&0
fi
Have you ever had a program stop waiting for keyboard input when the output is directed elsewhere?
Creating Input:
#!/bin/sh
# Exampel of redirecting input: take standard input (or a specified file) and do it. if [ "$1" != "" ]; then
# alternatively, redirection from a file - take standard input (or a specified file) and do it. if [ "$1" != "" ]; then
# You can also construct files on the fly. rmail bsmtp <<EOF
subscribe $2 Usenet Feeder at UWO
# Note: that variables are expanded in the input. |
String Manipulations:
#!/bin/sh
# Some tricks parsing strings TIME=`date | cut -c12-19`
|
With some care, redefining the input field separators can help:
#!/bin/sh
# convert IP number to in-addr.arpa name name()
if [ $# -ne 1 ]; then
add=`name $1` nslookup < < EOF |
grep "$add" | sed 's/.*= //'
|
Debugging:
The shell has a number of flags that make debugging easier:
sh -n command
Read the shell script but don't execute the commands. IE. check syntax.
sh -x command
Display commands and arguments as they're executed. In a lot of my shell
scripts you'll see
# Uncomment the next line
for testing
# set -x
----------------------------------------------------
This tutorial is based on An Introduction to Shell Programing
by Reg Quinton
examples 1 | home - top of the page - |
You can use here-doc constructs:
#!/bin/sh
sort <<EOF
|
You can use here-doc constructs:
#!/bin/sh
while [ 1 ]; do
|