Unix Shell Scripting With Ksh/Bash
Unix Shell Scripting With Ksh/Bash
Comments
and questions, contact Richard.Brittain @ dartmouth.edu
These notes are intended for use in a 2-part class, total duration 3 hours.
Assumptions:
It is assumed that you already know how to:
Example commands are shown like this. Many commands are shown with links to their
full man pages (sh)
Output from commands is shown like this; optional items are [ in brackets ].
Some descriptions in these notes have more detail available, and are denoted like this:
More details of this item would appear here. The printed notes include all of the additional information
Permission is granted to download and use these notes and example scripts, as long as all copyright notices are
kept intact. Some of the examples are taken from texts or online resources which have granted permission to
redistribute.
These notes are updated from time to time. The "development" set of notes are http://northstar-www.dartmouth.edu/~richard/classes/ksh (Dartmouth
only)
1
Table of Contents
The goals of this class are to enable you to: ................................................................................ 1
1. What is a Shell Script ........................................................................................................................... 5
2. Why use Shell Scripts........................................................................................................................... 5
2.1. Typical uses................................................................................................................................... 6
3. History of Shells ................................................................................................................................... 6
4. Comparison of shell features .............................................................................................................. 7
4.1. Core Similarities (and recap of basic command line usage)......................................................... 7
4.2. Principal Differences..................................................................................................................... 8
5. Other Scripting Languages................................................................................................................... 8
6. ksh/bash vs sh ..................................................................................................................................... 9
7. Basic sh script syntax ......................................................................................................................... 10
7.1. Exit status ................................................................................................................................... 10
8. Filename Wildcards ........................................................................................................................... 11
9. Shell Variables ................................................................................................................................... 11
9.1. Setting and exporting variables.................................................................................................. 11
9.2. Using variables............................................................................................................................ 12
9.3. Conditional modifiers ................................................................................................................. 12
9.4. Variable assignment command prefix........................................................................................ 12
10. Preset Shell Variables ...................................................................................................................... 13
10.1. Login environment ................................................................................................................... 13
10.2. Shell internal settings ............................................................................................................... 13
10.3. Process ID variables.................................................................................................................. 14
10.4. ksh/bash additional features.................................................................................................... 14
11. Command Line (positional) arguments ........................................................................................... 14
11.1. Setting new positional arguments............................................................................................ 15
12. Shell options .................................................................................................................................... 16
13. Command Substitution.................................................................................................................... 16
13.1. sh syntax................................................................................................................................... 16
13.2. ksh/bash syntax........................................................................................................................ 17
14. I/O redirection and pipelines........................................................................................................... 17
14.1. Output redirection.................................................................................................................... 17
2
14.2. Input redirection ...................................................................................................................... 17
14.3. Command pipelines.................................................................................................................. 18
15. Input and Output............................................................................................................................. 18
15.1. Script output............................................................................................................................. 18
15.2. Script input ............................................................................................................................... 18
16. Conditional tests for [...] and [[...]] commands ............................................................................... 19
16.1. File tests.................................................................................................................................... 19
16.2. Character string tests ............................................................................................................... 19
17. More conditional tests for [...] and [[...]] commands...................................................................... 20
17.1. Arithmetic tests ........................................................................................................................ 20
17.2. Additional tests for [[...]] (ksh and bash).................................................................................. 20
17.3. Negating and Combining tests ................................................................................................. 20
18. Flow Control and Compound Commands ....................................................................................... 21
18.1. Conditional execution: if/else .................................................................................................. 21
18.2. Looping: 'while' and 'for' loops................................................................................................. 21
19. Flow Control and Compound Commands (contd.).......................................................................... 23
19.1. Case statement: pattern matching........................................................................................... 23
19.2. Miscellaneous flow control and subshells................................................................................ 23
20. Conditional Test Examples .............................................................................................................. 24
21. Miscellaneous internal commands.................................................................................................. 24
22. Manipulating Variables (ksh/bash only).......................................................................................... 25
22.1. Text variables............................................................................................................................ 25
22.2. Numeric variables..................................................................................................................... 26
23. Shell Functions................................................................................................................................. 26
23.1. Defining functions .................................................................................................................... 26
23.2. Calling functions. ...................................................................................................................... 27
23.3. Reuseable functions ................................................................................................................. 28
24. Advanced I/O................................................................................................................................... 28
24.1. Redirecting for the whole script............................................................................................... 28
24.2. Explicitly opening or duplicating file descriptors ..................................................................... 28
24.3. Input and output to open file descriptors (ksh) ....................................................................... 29
24.4. Closing file handles................................................................................................................... 29
24.5. "Here" documents.................................................................................................................... 29
25. Wizard Level I/O .............................................................................................................................. 30
3
25.1. Pass stderr of a command into a pipeline for further processing............................................ 31
25.2. Capture the exit status of a command in the middle of a pipeline.......................................... 31
26. Coprocesses and Background jobs .................................................................................................. 33
26.1. ksh coprocesses........................................................................................................................ 33
27. Delivering and Trapping Signals ...................................................................................................... 35
28. Security issues in shell scripts.......................................................................................................... 36
29. Style ................................................................................................................................................. 38
29.1. When not to use shell scripts ................................................................................................... 38
30. Some longer examples .................................................................................................................... 38
31. A Toolkit of commonly used external commands........................................................................... 39
31.1. Listing, copying and moving files and directories .................................................................... 39
31.2. Displaying text, files or parts of files ........................................................................................ 40
31.3. Compression and archiving ...................................................................................................... 40
31.4. Sorting and searching for patterns........................................................................................... 40
31.5. System information (users, processes, time) ........................................................................... 41
31.6. Conditional tests....................................................................................................................... 41
31.7. Stream Editing .......................................................................................................................... 41
31.8. Finding and comparing files ..................................................................................................... 42
31.9. Arithmetic and String Manipulation......................................................................................... 42
31.10. Merging files........................................................................................................................... 42
32. References, Resources, Man pages etc. .......................................................................................... 43
32.1. Books ........................................................................................................................................ 43
32.2. Online Resources...................................................................................................................... 43
32.3. Unix-like shells and utilities for Microsoft Windows................................................................ 44
4
(1)
There is no difference in syntax between interactive command line use and placing the commands in a
file. Some commands are only useful when used interactively (e.g. command line history recall) and
other commands are too complex to use interactively.
The shell itself has limited capabilities -- the power comes from using it as a "glue"
language to combine the standard Unix utilities, and custom software, to produce a
tool more useful than the component parts alone.
Any shell can be used for writing a shell script. To allow for this, the first line of every
script is:
#!/path/to/shell (e.g. #!/bin/ksh).
The #! characters tell the system to locate the following pathname, start it up and feed it the rest of the
file as input. Any program which can read commands from a file can be started up this way, as long as it
recognizes the # comment convention. The program is started, and then the script file is given to it as an
argument. Because of this, the script must be readable as well as executable. Examples are perl, awk, tcl
and python.
A shell script can be as simple as a sequence of commands that you type regularly. By putting
them into a script, you reduce them to a single command.
1: #!/bin/sh
2: date
3: pwd
4: du -k
(2)
(e.g. apply the same analysis to every data file on a CD, without needing to repeat the commands)
5
Create new commands using combinations of utilities in ways the original authors
never thought of.
Simple shell scripts might be written as shell aliases, but the script can be made
available to all users and all processes. Shell aliases apply only to the current shell.
Wrap programs over which you have no control inside an environment that you can
control.
e.g. set environment variables, switch to a special directory, create or select a configuration file, redirect
output, log usage, and then run the program.
Create customized datasets on the fly, and call applications (e.g. matlab, sas, idl,
gnuplot) to work on them, or create customized application commands/procedures.
Rapid prototyping (but avoid letting prototypes become production)
Other tools may create fancier installers (e.g. tcl/tk), but can not be assumed to be installed already.
Shell scripts are used because they are very portable. Some software comes with a complete installation
of the tool it wants to use (tcl/tk/python) in order to be self contained, but this leads to software bloat.
Application startup scripts, especially unattended applications (e.g. started from cron
or at)
Any user needing to automate the process of setting up and running commercial
applications, or their own code.
(3)
3. History of Shells
sh
aka "Bourne" shell, written by Steve Bourne at AT&T Bell Labs for Unix V7 (1979).
Small, simple, and (originally) very few internal commands, so it called external
programs for even the simplest of tasks. It is always available on everything that looks
vaguely like Unix.
csh
The "C" shell. (Bill Joy, at Berkeley). Many things in common with the Bourne shell,
but many enhancements to improve interactive use. The internal commands used only
in scripts are very different from "sh", and similar (by design) to the "C" language
syntax.
tcsh
The "TC" shell. Freely available and based on "csh". It has many additional features to
make interactive use more convenient.
6
We use it as the default interactive shell for new accounts on all of our public systems.
Not many people write scripts in [t]csh. See Csh Programming Considered Harmful by Tom
Christiansen for a discussion of problems with programming csh scripts.
ksh
The "Korn" shell, written by David Korn of AT&T Bell Labs (now AT&T Research).
Written as a major upgrade to "sh" and backwards compatible with it, but has many
internal commands for the most frequently used functions. It also incorporates many
of the features from tcsh which enhance interactive use (command line history recall
etc.).
It was slow to gain acceptance because earlier versions were encumbered by AT&T licensing. This
shell is now freely available on all systems, but sometimes not installed by default on "free" Unix.
There are two major versions. ksh88 was the version incorporated into AT&T SVR4 Unix, and may
still be installed by some of the commercial Unix vendors. ksh93 added more features, primarily for
programming, and better POSIX compliance.
POSIX 1003.2 Shell Standard.
Standards committees worked over the Bourne shell and added many features of the
Korn shell (ksh88) and C shell to define a standard set of features which all compliant
shells must have.
On most systems, /bin/sh is now a POSIX compliant shell. Korn shell and Bash are POSIX compliant,
but have many features which go beyond the standard. On Solaris, the POSIX/XPG4 commands which
differ slightly in behaviour from traditional SunOS commands are located in /usr/xpg4/bin
bash
The "Bourne again" shell. Written as part of the GNU/Linux Open Source effort, and
the default shell for Linux and Mac OS-X. It is a functional clone of sh, with
additional features to enhance interactive use, add POSIX compliance, and partial ksh
compatability.
zsh
A freeware functional clone of sh, with parts of ksh, bash and full POSIX compliance,
and many new interactive command-line editing features. It was installed as the
default shell on early MacOSX systems.
(4)
7
Backgrounding commands with &
Quoting rules: "double quotes" protect most things, but allow $var interpretation;
'single quotes' protect all metacharacters from interpretation.
Home directory expansion using ~user (except for sh)
# comments
Command substitution using `command` (backtics)
Expand variables using $varname syntax
Conditional execution using && and ||
Line continuation with "\"
(5)
what is installed already - many other scripting languages are not available by default
what similar code already exists
what you are most familiar with and can use most efficiently. Your time is always
more expensive than computer cycles.
Some major players (all of these are freely available) in the general purpose scripting
languages are:
awk
A pattern matching and data (text and numeric) manipulation tool. Predates perl. Installed on all Unix
systems. Often used in combination with shell scripts.
perl
8
The most used scripting language for Web CGI applications and system administration tasks. Perl is
harder to learn, and is uusually installed by default now. It is more efficient and has an enormous library
of functions available. You could use Perl for almost all scripting tasks, but the syntax is very different
to the shell command line
python
tcl/tk
Tool Command Language. Another general purpose scripting language. The "tk" component is a
scripted interface to standard X-windows graphical components, so the combination is often used to
create graphical user interfaces.
Ksh93 can be extended by linking to shared libraries providing additional internal commands. One
example of an extended shell is tksh which incorporates Tcl/Tk with ksh and allows generation of
scripts using both languages. It can be used for prototyping GUI applications.
(6)
6. ksh/bash vs sh
Ksh and bash are both supersets of sh. For maximum portability, even to very old computers,
you should stick to the commands found in sh. Where possible, ksh or bash-specific features
will be noted in the following pages. In general, the newer shells run a little faster and scripts
are often more readable because logic can be expressed more cleanly user the newer syntax.
Many commands and conditional tests are now internal.
The philosophy of separate Unix tools each performing a single operation was followed closely by the designers
of the original shell, so it had very few internal commands and used external tools for very trivial operations
(like echo and [). Ksh and bash internally performs many of the basic string and numeric manipulations and
conditional tests. Occasional problems arise because the internal versions of some commands like echo are not
fully compatible with the external utility they replaced.
The action taken every time a shell needs to run an external program is to locate the program (via $PATH),
fork(), which creates a second copy of the shell, adjust the standard input/output for the external program, and
exec(), which replaces the second shell with the external program. This process is computationally expensive
(relatively), so when the script does something trivial many times over in a loop, it saves a lot of time if the
function is handled internally.
If you follow textbooks on Bourne shell programming, all of the advice should apply no
matter which of the Bourne-derived shells you use. Unfortunately, many vendors have added
features over the years and achieving complete portability can be a challenge. Explicitly
writing for ksh (or bash) and insisting on that shell being installed, can often be simpler.
The sh and ksh man pages use the term special command for the internal commands -
handled by the shell itself.
(7)
9
7. Basic sh script syntax
The most basic shell script is a list of commands exactly as could be typed interactively,
prefaced by the #! magic header. All the parsing rules, filename wildcards, $PATH searches
etc., which were summarized above, apply.
In addition:
# as the first non-whitespace character on a line
flags the line as a comment, and the rest of the line is completely ignored. Use
comments liberally in your scripts, as in all other forms of programming.
\ as the last character on a line
causes the following line to be logically joined before interpretation. This allows
single very long commands to be entered in the script in a more readable fashion. You
can continue the line as many times as needed.
This is actually just a particular instance of \ being to escape, or remove the special meaning from, the
following character.
;as a separator between words on a line
is interpreted as a newline. It allows you to put multiple commands on a single line.
There are few occasions when you must do this, but often it is used to improve the
layout of compound commands.
Example: ex1 display, text
1: #!/bin/ksh
2: # For the purposes of display, parts of the script have
3: # been rendered in glorious technicolor.
4: ## Some comments are bold to flag special sections
5:
6: # Line numbers on the left are not part of the script.
7: # They are just added to the HTML for reference.
8:
9: # Built-in commands and keywords (e.g. print) are in blue
10: # Command substitutions are purple. Variables are black
11: print "Disk usage summary for $USER on `date`"
12:
13: # Everything else is red - mostly that is external
14: # commands, and the arguments to all of the commands.
15: print These are my files # end of line comment for print
16: # List the files in columns
17: ls -C
18: # Summarize the disk usage
19: print
20: print Disk space usage
21: du -k
22: exit 0
(8)
10
8. Filename Wildcards
The following characters are interpreted by the shell as filename wildcards, and any word
containing them is replaced by a sorted list of all the matching files.
Wildcards may be used in the directory parts of a pathname as well as the filename part. If no files match the
wildcard, it is left unchanged. Wildcards are not full regular expressions. Sed, grep, awk etc. work with more
flexible (and more complex) string matching operators.
*
Match zero or more characters.
?
Match any single character
[...]
Match any single character from the bracketed set. A range of characters can be
specified with [ - ]
[!...]
Match any single character NOT in the bracketed set.
An initial "." in a filename does not match a wildcard unless explicitly given in the
pattern. In this sense filenames starting with "." are hidden. A "." elsewhere in the
filename is not special.
Pattern operators can be combined
Example:
chapter[1-5].* could match chapter1.tex, chapter4.tex, chapter5.tex.old. It would
not match chapter10.tex or chapter1
(9)
9. Shell Variables
Scripts are not very useful if all the commands and options and filenames are explicitly coded.
By using variables, you can make a script generic and apply it to different situations. Variable
names consist of letters, numbers and underscores ([a-zA-Z0-9_], cannot start with a number,
and are case sensitive. Several special variables (always uppercase names) are used by the
system -- resetting these may cause unexpected behaviour. Some special variables may be
read-only. Using lowercase names for your own variables is safest.
11
Added srcfile to the list of variables which will be made available to external program
through the environment. If you don't do this, the variable is local to this shell instance.
export
List all the variables currently being exported - this is the environment which will be
passed to external programs.
Example:
datafile=census2000
# Tries to find $datafile_part1, which doesn't exist
echo $datafile_part1.sas
# This is what we intended
echo ${datafile}_part1.sas
12
(10)
13
Internal Field Separators: the set of characters (normally space and tab) which are used
to parse a command line into separate arguments. This may be set by the user for
special purposes, but things get very confusing if it isn't changed back.
(11)
14
$*
contains all of the arguments in a single string, with one space separating them.
$@
similar to $*, but if used in quotes, it effectively quotes each argument and keeps them
separate. If any argument contains whitespace, the distinction is important.
e.g. if the argument list is: a1 a2 "a3 which contains spaces" a4
then: $1=a1, $2=a2, $3=a3 which contains spaces, $4=a4
and: $*=a1 a2 a3 which contains spaces a4
and: "$@"="a1" "a2" "a3 which contains spaces" "a4"
Only using the form "$@" preserves quoted arguments. If the arguments are being passed
from the script directly to some other program, it may make a big difference to the meaning.
1: #!/bin/sh
2: #
3: # Check positional argument handling
4: echo "Number of arguments: $#"
5: echo "\$0 = $0"
6:
7: echo "Loop over \$*"
8: for a in $*; do
9: echo \"$a\"
10: done
11:
12: echo "Loop over \"\$@\""
13: for a in "$@"; do
14: echo \"$a\"
15: done
1: #!/bin/sh
2: # Find an entry in the password file
3: pwent=`grep '^root:' /etc/passwd`
4: # Turn off globbing - passwd lines often contain '*'
5: set -o noglob
6: # The "full name" and other comments are in
7: # field 5, colon delimited. Get this field using shell word
splitting
8: OIFS=$IFS; IFS=: ; set $pwent; IFS=$OIFS
9: echo $5
15
1: #!/bin/ksh
2:
3: # Select a random image from the background logo collection
4: # This could be used to configure a screen saver, for example.
5: #
6: # This works even if the filenames contain spaces.
7:
8: # switch to the logos directory to avoid long paths
9: logos=/afs/northstar/common/usr/lib/X11/logos/backgrounds
10: cd $logos
11:
12: # '*' is a filename wildcard to match all files in the current
directory
13: set *
14:
15: # Use the syntax for arithmetic expressions. "%" is the modulo
operator
16: # Shift arguments by a random number between 0 and the number of
files
17: shift $(($RANDOM % $#))
18:
19: # Output the resulting first argument
20: echo "$logos/$1"
(12)
(13)
16
output, the newlines are retained. If the resultant string is displayed, unquoted, using
echo, newlines and multiple spaces will be removed.
1: #!/bin/ksh
2:
3: echo Today is `date`
4:
5: file=/etc/hosts
6: echo The file $file has $(wc -l < $file) lines
7:
8: hostname -s > myhostname
9: echo This system has host name $(<myhostname)
(14)
17
14.3. Command pipelines
command | command [ | command ...]
Pipe multiple commands together. The standard output of the first command becomes
the standard input of the second command. All commands run simultaneously, and
data transfer happens via memory buffers. This is one of the most powerful constructs
in Unix. Compound commands may also be used with pipes. Pipes play very nicely
with multiprocessor systems.
No more than one command in a pipeline should be interactive (attempt to read from the terminal). This
construct is much more efficient than using temporary files, and most standard Unix utilities are
designed such that they work well in pipelines.
The exit status of a pipeline is the exit status of the last command. In compound commands, a pipeline
can be used anywhere a simple command could be used.
(15)
18
read a line from stdin, parsing by $IFS, and placing the words into the named variables.
Any left over words all go into the last variable. A '\' as the last character on a line
removes significance of the newline, and input continues with the following line.
-r
raw mode - ignore \-escape conventions
1: #!/bin/sh
2: echo "Testing interactive user input: enter some keystrokes and press
return"
3: read x more
4: echo "First word was \"$x\""
5: echo "Rest of the line (if any) was \"$more\""
(16)
19
an external program, the only way to test for a null string is:
if [ "X$var" = "X" ]
This is rarely needed now, but is still often found.
$variable = text
True if $variable matches text.
$variable < text
True if $variable comes before (lexically) text
Similarly, > = comes after
(17)
Examples:
if [[ -x /usr/local/bin/lserve && \
-w /var/logs/lserve.log ]]; then
/usr/local/bin/lserve >> /var/logs/lserve.log &
20
fi
(18)
Example:
Example:
if [ -r $myfile ]
then
cat $myfile
else
echo $myfile not readable
fi
21
Example: ex4 display, text
1: #!/bin/ksh
2: count=0
3: max=10
4: while [[ $count -lt $max ]]
5: do
6: echo $count
7: count=$((count + 1))
8: done
9: echo "Value of count after loop is: $count"
for identifier [ in words ]; do; list; done
Set identifier in turn to each word in words and execute the list. Omitting the "in
words" clause implies using $@, i.e. the identifier is set in turn to each positional
argument.
Example:
Compound commands can be thought of as running in an implicit subshell. They can have I/O
redirection independant of the rest of the script. Setting of variables in a real subshell does not
leave them set in the parent script. Setting variables in implicit subshells varies in behaviour
among shells. Older sh could not set variables in an implicit subshell and then use them later,
but current ksh can do this (mostly).
1: #!/bin/sh
2:
3: # Demonstrate reading a file line-by-line, using I/O
4: # redirection in a compound command
5: # Also test variable setting inside an implicit subshell.
6: # Test this under sh and ksh and compare the output.
7:
8: line="TEST"
9: save=
10:
11: if [ -z "$1" ]; then
12: echo "Usage: $0 filename"
13: else
14: if [ -r $1 ]; then
15: while read line; do
16: echo "$line"
17: save=$line
22
18: done < $1
19: fi
20: fi
21: echo "End value of \$line is $line"
22: echo "End value of \$save is $save"
(19)
Example:
case $filename in
*.dat)
echo Processing a .dat file
;;
*.sas)
echo Processing a .sas file
;;
*)
# catch anything else that doesn't match patterns
echo "Don't know how to deal with $filename"
;;
esac
23
Read the contents of the named file into the current shell and execute as if in line.
Uses $PATH to locate the file, and can be passed positional parameters. This is often
used to read in shell functions that are common to multiple scripts. There are security
implications if the pathname is not fully specified.
( ... ) Command grouping
Commands grouped in "( )" are executed in a subshell, with a separate environment
(can not affect the variables in the rest of the script).
(20)
(21)
24
The args are read as input to the shell and the resulting command executed. Allows
"double" expansion of some constructs. For example, constructing a variable name out
of pieces, and then obtaining the value of that variable.
netdev=NETDEV_
NETDEV_1=hme0 # As part of an initialization step defining
multiple devices
(22)
25
removes the shortest suffix of $var patching pattern
${var%%pattern}
removes the longest suffix of $var patching pattern
${var#pattern}
removes the shortest prefix of $var patching pattern
${var##pattern}
removes the longest prefix of $var patching pattern
(23)
A function may read or modify any shell variable that exists in the calling script. Such
variables are global.
26
(ksh and bash only) Functions may also declare local variables in the function using
typeset or declare. Local variables are visible to the current function and any
functions called by it.
Example:
die()
{
# Print an error message and exit with given status
# call as: die status "message" ["message" ...]
exitstat=$1; shift
for i in "$@"; do
print -R "$i"
done
exit $exitstat
}
Example:
[ -w $filename ] || \
die 1 "$file not writeable" "check permissions"
1: #!/bin/sh
2:
3: background()
4: {
5: sleep 10
6: echo "Background"
7: sleep 10
8: # Function will return here - if backgrounded, the
subprocess will exit.
9: }
10:
11: echo "ps before background function"
12: ps
13: background &
14: echo "My PID=$$"
15: echo "Background function PID=$!"
27
16: echo "ps after background function"
17: ps
18: exit 0
Example:
vprint()
{
# Print or not depending on global "$verbosity"
# Change the verbosity with a single variable.
# Arg. 1 is the level for this message.
level=$1; shift
if [[ $level -le $verbosity ]]; then
print -R $*
fi
}
verbosity=2
vprint 1 This message will appear
vprint 3 This only appears if verbosity is 3 or higher
Functions may generate output to stdout, stderr, or any other file or filehandle.
Messages to stdout may be captured by command substitution (`myfunction`, which
provides another way for a function to return information to the calling script. Beware
of side-effects (and reducing reusability) in functions which perform I/O.
(24)
29
1: #!/bin/sh
2: echo "Example of unquoted here document, with variable and
command substitution"
3:
4: cat <<EOF
5: This text will be fed to the "cat" program as
6: standard input. It will also have variable
7: and command substitutions performed.
8: I am logged in as $USER and today is `date`
9: EOF
10: echo
11: echo "Example of quoted here document, with no variable or
command substitution"
12: # The terminating string must be at the start of a line.
13: cat <<"EndOfInput"
14: This text will be fed to the "cat" program as standard
15: input. Since the text string marking the end was quoted, it
does not get
16: variable and command subsitutions.
17: I am logged in as $USER and today is `date`
18: EndOfInput
1: #!/bin/sh
2: # Add in the magic postscript preface to perform
3: # duplex printer control for Xerox docuprint.
4:
5: # To have this script send the files directly to the
printer, use
6: # a subshell to collect the output of the two 'cat'
commands.
7:
8: ## (
9: cat << EOP
10: %!PS
11: %%BeginFeature: *Duplex DuplexTumble
12: <</Duplex true /Tumble false>> setpagedevice
13: %%EndFeature
14: EOP
15: cat "$@"
16: ## ) | lpr
(25)
30
25.1. Pass stderr of a command into a pipeline for further
processing
Example: ex14 display, text
exec 3>&1
./ex13.sh 2>&1 1>&3 3>&- | sed 's/stderr/STDERR/' 1>&2
We duplicate stdout to another file descriptor (3), then run the first command with
stderr redirected to stdout and stdout redirected to the saved descriptor (3). The result
is piped into other commands as needed. The output of the pipeline is redirected back
to stderr, so that stdout and stderr of the script as a whole are what we expect.
1: #!/bin/sh
2: # Example 14
3: # Take stderr from a command and pass it into a pipe
4: # for further processing.
5:
6: # Uses ex13.sh to generate some output to stderr
7: # stdout of ex13 is processed normally
8:
9: # Save a copy of original stdout
10: exec 3>&1
11:
12: # stdout from ex13.sh is directed to the original stdout
(3)
13: # stderr is passed into the pipe for further processing.
14: # stdout from the pipe is redirected back to stderr
15: ./ex13.sh 2>&1 1>&3 3>&- | sed 's/stderr/STDERR/' 1>&2
16:
17: # 3 is closed before running the command, just in case it
cares
18: # about inheriting open file descriptors.
exec 3>&1
ex13stat=`((./ex13.sh; echo $? >&4) | grep 'foo' 1>&3) 4>&1`
This script uses nested subshells captured in backtics. Again we first duplicate stdout
to another file descriptor (3). The inner subshell runs the first command, then writes
the exit status to fd 4. The outer subshell redirects 4 to stdout so that it is captured by
the backtics. Standard output from the first command (inner subshell) is passed into
the pipeline as normal, but the final output of the pipeline is redirected to 3 so that it
appears on the original stdout and is not captured by the backtics.
If any of the commands really care about inheriting open file descriptors that they
don't need then a more correct command line closes the descriptors before running the
commands.
31
1: #!/bin/sh
2: # Example 15
3:
4: # Uses ex13.sh to generate some output and give us an
5: # exit status to capture.
6:
7: # Get the exit status of ex13 into $ex13stat.
8: # stdout of ex13 is processed normally
9:
10: # Save a copy of stdout
11: exec 3>&1
12: # Run a subshell, with 4 duplicated to 1 so we get it in
stdout.
13: # Capture the output in ``
14: # ex13stat=`( ... ) 4>&1`
15: # Inside the subshell, run another subshell to execute
ex13,
16: # and echo the status code to 4
17: # (./ex13.sh; echo $? >&4)
18: # stdout from the inner subshell is processed normally,
but the
19: # subsequent output must be directed to 3 so it goes to
the
20: # original stdout and not be captured by the ``
21: ex13stat=`((./ex13.sh; echo $? >&4) | grep 'foo' 1>&3)
4>&1`
22:
23: echo Last command status=$?
24: echo ex13stat=$ex13stat
25:
26: # If any of the commands really care about inheriting
open file
27: # descriptors that they don't need then a more correct
command line
28: # closes the descriptors before running the commands
29: exec 3>&1
30: ex13stat=`((./ex13.sh 3>&- 4>&- ; echo $? >&4) | \
31: grep 'foo' 1>&3 3>&- 4>&- ) 4>&1`
32: echo Last command status=$?
33: echo ex13stat=$ex13stat
exec 3>&1
ex13stat=`((./ex13.sh 2>&1 1>&3 3>&- 4>&- ; echo $? >&4) | \
sed s/err/ERR/ 1>&2 3>&- 4>&- ) 4>&1`
1: #!/bin/sh
2: # Example 16
3:
4: # Uses ex13.sh to generate some output and give us an
5: # exit status to capture.
6:
7: # Get the exit status of ex13 into ex13stat.
8: # stderr of ex13 is processed by the pipe, stdout
9: # is left alone.
32
10:
11: # Save a copy of stdout
12: exec 3>&1
13:
14: # Run a subshell, with 4 copied to 1 so we get it in
stdout.
15: # Capture the output in backtics`
16: # ex13stat=`( ) 4>&1`
17:
18: # In the subshell, run another subshell to execute ex13,
and
19: # echo the status code to 4
20: # (./ex13.sh; echo $? >&4)
21:
22: # stdout from the inner subshell is directed to the
original stdout (3)
23: # stderr is passed into the pipe for further processing.
24: # stdout from the pipe is redirected back to stderr
25:
26: # Close the extra descriptors before running the commands
27: exec 3>&1
28: ex13stat=`((./ex13.sh 2>&1 1>&3 3>&- 4>&- ; echo $? >&4)
| \
29: sed s/err/ERR/ 1>&2 3>&- 4>&- ) 4>&1`
30:
31: echo Last command status=$?
32: echo ex13stat=$ex13stat
33:
A practical application of this would be running a utility such as dd where the exit
status is important to capture, but the error output is overly chatty and may need to be
filtered before delivering to other parts of a script.
(26)
33
Read from the pipe to the coprocess, instead of standard input
print -p args
Write to the pipe connected to the coprocess, instead of standard output
Multiple coprocesses can be handled by moving the special file descriptors connected
to the pipes onto standard input and output, and or to explicitly specified file
descriptors.
exec <&p
The input from the coprocess is moved to standard input
exec >&p
The output from the coprocess is moved to standard output
1: #!/bin/ksh
2:
3: # If we have not redirected standard output, save a copy
of
4: # the output of this script into a file, but still send a
5: # copy to the screen.
6:
7: if [[ -t 1 ]] ; then
8: # Only do this if fd 1 (stdout) is still connected
9: # to a terminal
10:
11: # We want the standard output of the "tee" process
12: # to go explicitly to the screen (/dev/tty)
13: # and the second copy goes into a logfile named $0.out
14:
15: tee $0.out >/dev/tty |&
16:
17: # Our stdout all goes into this coprocess
18: exec 1>&p
19: fi
20:
21: # Now generate some output
22: print "User activity snapshot on $(hostname) at $(date)"
23: print
24: who
1: #!/bin/ksh
2: # This example uses a locally written tool for Dartmouth
Name Directory lookups
3:
4: # Start the dndlookup program as a coprocess
5: # Tell it to output only the canonical full name, and to
not print multiple matches
6: dndlookup -fname -u |&
7:
8: # move the input/output streams so we
34
9: # can use other coprocesses too
10: exec 4>&p
11: exec 5<&p
12:
13: echo "Name file contents:"
14: cat namefile
15: echo
16:
17: # read the names from a file "namefile"
18: while read uname; do
19: print -u4 $uname
20: read -u5 dndname
21: case $dndname in
22: *many\ matches*)
23: # handle case where the name wasn't unique
24: print "Multiple matches to \"$uname\" in DND"
25: ;;
26: *no\ match*)
27: # handle case where the name wasn't found
28: print "No matches to \"$uname\" in DND"
29: ;;
30: *)
31: # we seem to have a hit - process the
32: # canonical named retrieved from dndlookup
33: print "Unique DND match: full name for \"$uname\" is
\"$dndname\""
34: ;;
35: esac
36: sleep 2
37: done < namefile
38:
39: # We've read all the names, but the coprocess
40: # is still running. Close the pipe to tell it
41: # we have finished.
42: exec 4>&-
(27)
35
EXIT
the handler is called when the function exits, or when the whole script exits. The exit
signal has value 0.
ERR (ksh)
the handler is called when any command has a non-zero exit status
DEBUG (ksh)
the handler is called after each command.
1: #!/bin/bash
2: # Try this under bash, ksh and sh
3:
4: trap huphandler HUP
5: trap '' QUIT
6: trap exithandler TERM INT
7:
8: huphandler()
9: {
10: echo 'Received SIGHUP'
11: echo "continuing"
12: }
13:
14: exithandler()
15: {
16: echo 'Received SIGTERM or SIGINT'
17: exit 1
18: }
19: ## Execution starts here - infinite loop until
interrupted
20: # Use ":" or "true" for infinite loop
21: # SECONDS is built-in to bash and ksh. It is number of
seconds since script started
22: : is like a comment, but it is evaluated for side effects
and evaluates to true
23: seconds=0
24: while : ; do
25: # while true; do
26: sleep 5
27: seconds=$((seconds + 5))
28: echo -n "$SECONDS $seconds - "
29: done
Exit handlers can be defined to clean up temporary files or reset the state of devices.
This can be useful if the script has multiple possible exit points.
(28)
36
Most systems don't even allow a script to be made set-UID. It is impossible (due to inherent
race conditions) to ensure that a set-uid script cannot be compromised. Use wrapper programs
like sudo instead.
Always explicitly set $PATH at the start of a script, so that you know exactly
which external programs will be used.
If possible, don't use temporary files. If they cannot be avoided, use $TMPDIR,
and create files safely (e.g. mktemp).
Often scripts will write to a fixed, or trivially generated temporary filename in /tmp. If the file
already exists and you don't have permission to overwrite it, the script will fail. If you do have
permission to overwrite it, you will delete the previous contents. Since /tmp is public write,
another user may create files in it, or possibly fill it completely.
Example:
Environment variable $TMPDIR is often used to indicate a preferred location for temporary
files (e.g., a per-user directory). Some systems may use $TMP or $TEMP. Safe scratch files can
be made by creating a new directory, owned and writeable only by you, then creating files in
there.
Example:
or (deluxe version)
tmp=${TMPDIR:-/tmp}
tmp=$tmp/tempdir.$RANDOM.$RANDOM.$RANDOM.$$
(umask 077 && mkdir $tmp) || {
echo "Could not create temporary directory" 1>&2
exit 1
}
Alternatively, many systems have mktemp to safely create a temporary file and return the
filename, which can be used by the script and then deleted.
Example:
Consider the effects of a file named "myfile;cd /;rm *" if processed, unquoted, by
your script.
37
# We are backslash-protecting the characters \'" ?*;
protect_filenames()
{
sed -es/\\\\/\\\\\\\\/g \
-es/\\\'/\\\\\'/g \
-es/\\\"/\\\\\"/g \
-es/\\\;/\\\\\;/g \
-es/\\\?/\\\\\?/g \
-es/\\\*/\\\\\*/g \
-es/\\\ /\\\\\ /g
}
If using GNU find and xargs, there is a much cleaner option to null-terminate generated
pathnames.
(29)
29. Style
Shell scripts are very frequently written quickly for a single purpose, used once and
discarded. They are also as frequently kept and used many times, and migrate into
other uses, but often do not receive the same level of testing and debugging that other
software would be given in the same situation. It is possible to apply general principles
of good software engineering to shell scripts.
Preface scripts with a statement of purpose, author, date and revision notes
Use a revision control system for complex scripts with a long lifetime
Assume your script will have a long lifetime unless you are certain it won't
Document any non-standard external utilities which your script needs
Document your scripts with inline comments - you'll need them in a few
months when you edit it.
Treat standard input and output in the normal way, so that your script can be
used in combination with other programs (the Unix toolkit philosophy)
Be consistent in the format of your output, so that other programs can rely on it
Use options to control behaviour such as verbosity of output. Overly chatty
programs are very hard to combine with other utilities
Use interactive features (prompting the user for information) very sparingly.
Doing so renders the script unuseable in pipeline combinations with other
programs, or in unattended operations.
Test (a lot)
(30)
Download a compressed tar file of all example scripts used in these notes.
This entire tutorial was created from individual HTML pages using a content
management system written as ksh scripts (heavily using sed to edit the pages),
coordinated by make.
You can even write an entire web server as a shell script. This one is part of the LEAF
(Linux Embedded Appliance Firewall) project. This wouldn't be suitable for much
load, but handles occasional queries on static HTML and CGI scripts.
(www.nisi.ab.ca/lrp/Packages/weblet.htm)
(31)
Most of these commands will operate on a one or more named files, or will operate on
a stream of data from standard input if no files are named.
39
list contents of a directory, or list details of files and directories.
mkdir; rmdir *
Make and Remove directories.
rm; cp; mv *
Remove (delete), Copy and Move (rename) files and directories
touch *
Update the last modifed timestamp on a file, to make it appear to have just been
written.
If the file does not exist, a new zero-byte file is created, which is often useful to signify that an event
has occurred.
tee
Make a duplicate copy of a data stream - used in pipelines to send one copy to a log
file and a second copy on to another program. (Think plumbing).
40
31.5. System information (users, processes, time)
date *
Display the current date and time (flexible format). Useful for conditional execution
based on time, and for timestamping output.
ps *
List the to a running processes.
kill *
Send a signal (interrupt) to a running process.
id
Print the user name and UID and group of the current user (e.g. to distinguish
priviledged users before attempting to run programs which may fail with permission
errors)
who
Display who is logged on the system, and from where they logged in.
uname *
Display information about the system, OS version, hardware architecture etc.
mail *
Send mail, from a file or standard input, to named recipients. Since scripts are often
used to automate long-running background jobs, sending notification of completion by
mail is a common trick.
logger
Place a message in the central system logging facility. Scripts can submit messages
with all the facilities available to compiled programs.
hostname
Display the hostname of the current host - usful to keep track of where your programs
are running
41
Since it makes a single pass through the file, keeping only a few lines in memory at once, it can be used
with infinitely large data sets. It is mostly used for global search and replace operations. It is a superset
of 'tr', 'grep', and 'cut', but is more complicated to use.
tr
Transliterate - perform very simple single-character edits on a file.
(32)
42
32. References, Resources, Man pages etc.
The standard man pages for sh and ksh are quite complete, but not easy to learn from.
The following is a sampling of the many available books on the subject. The Bolsky
and Korn book might be viewed as the standard "reference". The Blinn book is Bourne
shell, but everything in it should work for either shell.
The links are to publisher's web sites, or Amazon.com. Some links are also given to
the example scripts provided with the books.
32.1. Books
The New KornShell Command And Programming Language, by Morris I.
Bolsky, David G. Korn (Contributor). More info
Learning the Korn Shell, 2nd Edn. by Bill Rosenblatt and Arnold Robbins.
More info
The Korn Shell Linux and Unix Programming Manual (2nd Edn) by Anatole
Olczak. More info
Unix Shell Programming by Stephen Kochan and Patrick Wood (third Edition).
More info
Mastering Unix Shell Scripting by Randal K. Michael, Wiley (2003) More info
Light on basics, but develops scripting through examples. Ksh only. Examples can be
downloaded from the Wiley site (www.wiley.com/legacy/compbooks/michael/).
Wicked Cool Shell Scripts by Dave Taylor, No Starch Press (2004) More info
Develops scripting entirely through examples, drawn from Linux and OSX in addition to
traditional Unix. Recommended, but not for beginners. Examples can be downloaded from the
Intuitive site (www.intuitive.com/wicked/wicked-cool-shell-script-library.shtml).
43
Kornshell (http://www.kornshell.com)
The official Korn shell home page, with download links.
Mac OSX Unix tutorial (http://www.osxfaq.com/Tutorials/LearningCenter/)
Good resource on advanced use of OSX and Unix shell scripting in general
44