A shell script is a plain ASCII text file and can hence be created with any text editor (even vi!). It is a list of commands which you would normally have issued yourself on the commandline (quite similar in concept to command files under VMS or batch files under MSDOS). In addition, most UNIX shells have the capability of command flow logic ( goto, if/then/else/endif etc.), retrieving the command line parameters, defining and using (shell) variables etc.
Under the UNIX environment one can also choose which shell to use, although we shall only give examples in the most commonly used shell, the C-shell (csh). As an example, we will show a shell script which copies all files from one directory to a new one, also creating that new directory. The new directory must not exist yet, otherwise the script will fail with an error message. It’s usage would look like:
% csh -f scriptname dir1 dir2
or shorter:
% scriptname dir1 dir2
The second form, in which the script is not told through which shell it should process its commands, is the recommended practice. The first line of this script file must then contain the magic line
#! /bin/csh -f
to denote the script is to be run via the C-shell, which on standard UNIX systems is located in /bin/csh. In this case the script also needs to be made executable, i.e.
% chmod +x scriptname
In effect your operating system will then issue the first form of the command, The second form has the advantage that you don’t have to remember which shell to use, and in the end saves a few keystrokes, always considered a big issue in UNIX!
Now lets look at the full text of the script first:
#! /bin/csh -f
#
# Example of a shell script to copy all files from one directory
# to another. The input directory must not contain any subdirectories,
# and it will not copy any so-called (hidden) dot-files.
#
## check if called properly
if ($#argv != 2) then
echo "Usage: $0 dir1 dir2"
echo "copies all files from one directory to another"
goto done
endif
## save command line args in variables
set dir1=$1
set dir2=$2
## check if dir1 indeed is an existing dir
if (! -d $dir1) then
echo "$dir1 is not a directory" ; exit 1
endif
## check if dir2 does not exist
if (-e $dir2) then
echo "$dir2 already exists" ; exit 1
endif
## create new dir2
mkdir $dir2
if ($status != 0) goto error
## loop through all files in dir1
foreach file ($dir1/*)
if (-d $file) then
echo "Skipping $file (is a directory)"
else
echo "Copying $file"
cp $file $dir2
endif
end
## Labels to jump to exit OK (done) or not OK (error)
done:
exit 0
error:
exit 1
A few things can be noted:
Comments are lines that start with a #, but the first line of the script must contain this strange construction “#! /bin/csh -f” to tell it how to execute itself. By default, if the first line would not contain such a directive, the script would be executed by the (more primitive) Bourne shell (/bin/sh). Any options that would normally be supplied to the shell must then be given in the first-line shell directive, as with the -f option in the example above.
Shell variables can be created using the set name=value construct; they are henceforth referenced by prepending the shell variable name with a dollar: echo $name would simply display the value of the name shell variable (echo is a UNIX command).
Command line arguments are in special shell variables 0, 1, 2 etc. $0 is the name of the script, $1 the first argument (if present), etc. All command line arguments $1, $2,.. are also pre-defined in an shell variable argv, which is actually a list (like an array). It can be referenced as $argv[1], $argv[2], … etc. This form has the advantage that the construct $#argv (the same as $*) can be used to find the number of elements in the list. In our example we want exactly two, hence the first check. A shell variable list can be initialized using brackets, e.g.: set name=(val1 val2 val3). In this case $#name is 3, $name[2] has the value val2 etc.
The if (test) then/else/endif is used to test a condition and control command flow. The ! is used to negate a condition, and multiple conditions can be used together in the or (!!) and and (&&) boolean operators. The -e tests if the next argument is a file or directory that exists, and the -d if the next argument is a directory.
The foreach name (list) construct takes all elements from the list that follows, and executes all commands until a matching end is encountered. Inside the loop the current shell variable is defined in name.
Labels are defined by a name, followed by a colon, label: jumping to a label is be done by using goto label.
Scripts should by terminated properly by an exit command, optionally returning an error code (0=no error) to the caller. This gives the caller the opportunity to catch faulty behavior. After each command the status shell variable contains the exit value ($status) of that command.
After each shell command the special shell variable status contains the exit status of the previous command. We have used it once to check of the mkdir command completed successfully, if not, a direct exit by jumping to the label error was provided for.