Programming Shell Scripts
Programming Shell Scripts
Introduction
One of the nice things about the C-shell is that it provides access to the individual UNIX utilities (i.e., UNIX commands) that can
be run from the command prompt. For example, a complex command using pipes, such as
provides a compact list of all users who are currently logged on to Hercules. If the command is entered properly, a formatted list of
users should be reported that looks similar to the following:
While this is one form of shell programming, the command would be more useful if it did not have to be entered at the command
prompt each time it was needed. That is, the command would be more useful if it was contained in a shell script (i.e., a file that
contains shell commands). For example, the following could be entered into an executable file called showUsers:
#! /usr/bin/tcsh -f
who | cut -c-8 | sort -u | pr -l1 -8 -w78 -t
exit 0
It would then merely require that the showUsers command be entered at the command prompt to obtain a compact list of current
users.
In this section, we focus on the creation and execution of shell script files.
Shell Variables
1. Shell variables hold values which are actually lists containing zero or more elements (called words).
- If a word contains only digits, without a leading zero and with no embedded spaces, it can be used as
an operand in an arithmetic operation.
- Any shell variable can be used in a manner similar to a Boolean variable. If the variable is set (i.e., it
has been given a value), it represents the value true. If the variable is unset (i.e., it has not been given a value), it
represents the value false.
2. While there are numerous built-in shell variables (enter set at the command prompt to see them all or echo
$<variable_name> to see the value of <variable_name>), a few representative examples are:
- prompt: Contains the string which prints as the command prompt when you log on to Hercules.
The default value is %. You can change it by using the set command.
- Example
This command will change the command prompt to your name followed by >. If you entered the name jimbo, the
resulting prompt would be
jimbo>
- Example
jimbo{23}>
- history: Controls the number of previously executed commands maintained in the history list.
- Example
set history = 50
- path: Contains a list of directories that tcsh will search for the commands (i.e., programs) named
in the command line.
- Example
Sets the first three elements of path to system directories where standard UNIX commands are usually located. The fourth
element (i.e., the period) represents the current directory.
- status: Contains the completion status of the last command executed (i.e., usually zero and non-
zero mean successful and unsuccessful completion, respectively.)
3. In a UNIX command, we can use characters called magic characters. Specifically, whenever the characters *, ?, [,
and { are encountered, expansions are performed. The * character is a wild card character. For example, assume the command
to be executed is
CC *.c
The shell will use *.c as a pattern, attempt to match it against all of the file names in your directory, and compile all those
files ending in .c (known as file name expansion). Similarly, the ? character is a wild card character for matching a single
character. For example, assume the command to be executed is
CC file?.c
The shell will attempt to match file?.c against all of the file names in your directory, and compile all those whose names
begin with file, followed by any single character, followed by .c. An alternative to the ? character uses square brackets to
enumerate particular single characters to match. For example, assume the command to be executed is
CC file[134].c
The shell will compile file1.c, file3.c, and file4.c. Similarly, curly braces can be used to form a string product. For
example, assume the command to be executed is
CC file{1,3,4}.c
4. Example
Given what we now know from above in item 3, let’s consider the use of single quotes versus double quotes in UNIX
commands. Consider the command
This command has serious unintended problems because the shell will perform both history substitution (!! re-executes the
most recent command) and file name substitution (for every asterisk encountered). The command could be written as
echo \*\*\* WAKEUP\!\! \*\*\*
Single quotes protect the enclosed text from being split into words, suppress all forms of expansion, prevent the substitution of
words that begin with $, and prevent the substitution of the history characters (i.e., !9, !! and !$). Double quotes protect the
enclosed text from being split into words and suppress all forms of expansion. However, double quotes allow substitution. The
bottom line: you have to be careful in using single and double quotes when magic characters and substitution characters are
involved.
5. The set command is used to assign values to variables. It has several forms:
- Example
set arguments
- Example
Sets the variable arguments to the string “abc”. More precisely, the variable arguments contains a list with one
element whose value is the string “abc”.
- set <variable_name> = ( <value_1> <value_2> … <value_n> ): Sets the variable
<variable_name> to a list containing n elements whose values are <value_1>, <value_2>, …, <value_n>, respectively.
- Example
Sets the variable arguments to a four-element list containing the values shown above.
- Example
Sets (changes) the value of the second word in the variable arguments (note that the indexes start at 1). That is, the
variable arguments now contains the value
- @: An alternative form of the set command used exclusively for integer arithmetic and assignment.
- Example
@ i = 10
@ j = $i * 2 + 5
@ i++
@ j = ( $i – 5 ) / 2
Note: The first assignment is equivalent to set i = 10. However, none of the other assignment statements can be
expressed using the set command.
6. The value of a shell variable is accessed by placing $ (i.e., the dollar symbol) before the name of the variable. It
also has several forms:
- Example
echo $arguments
- ${<variable_name>}: Has the same effect as $<variable_name>. The braces are used only when a
string of alphanumeric characters is to be concatenated to the contents of a variable.
- Example
The command
echo $arguments
The command
The command
echo $argumentsxyz
The command
echo ${arguments}xyz
The command
echo $arguments[2]
789
- Example
The command
echo $arguments[2-4]
789f 456
- Example
The command
echo $arguments[2-]
outputs the value
789f 456
- Example
Shell Expressions
Shell expressions are formed by combining variables and constants with operators that are similar to those in the C and C++
programming languages.
1. Arithmetic operators.
- *: Multiplication.
- /: Division.
- %: Modulo division.
- +: Addition.
- -: Subtraction.
Note: Operator precedence is the same as in C/C++. Enclose expressions within parentheses to alter the precedence.
2. Assignment operators.
- =: Assign value.
3. Logical operators.
- !: Logical negation.
4. Comparision operators.
- -o <file_name>: The user (i.e., whoever is trying to access the file) owns the file.
Shell control structures are similar to those found in the C and C++ programming languages.
1. if Command
- One-line form: The <simple_command> must be a command that does not use pipes or I/O
redirection and the whole command must be contained on one line.
if ( <expression> ) <simple_command>
- Example
- Example
if ( $?file ) rm $file
- Multi-line form: The if and endif lines must be contained on one line (however, the if line can
be split into multiple lines using a backslash (i.e., \) character).
if ( <expression> ) then
zero or more lines containing shell commands
endif
- Example
if ( <expression> ) then
zero or more lines containing shell commands
else
zero or more lines containing shell commands
endif
if ( <expression> ) then
zero or more lines containing shell commands
else if ( <expression> ) then
zero or more lines containing shell commands
else
zero or more lines containing shell commands
endif
8. while Command
- Within the loop body, break and continue commands may be used to terminate and short-
circuit execution, respectively (i.e., they work the same as the like-named statements found in C and C++).
9. foreach Command
- The loop index variable <variable_name> takes on successive values from the <list_of_words>
parameter.
- Example
Note: The expression *.c is expanded to the list of words containing all file names in the current directory whose
extension is .c. Any command enclosed within a pair of single back quotes (i.e., `wc –l $file`) will be executed and
its output treated as a list of words. However, in this case, the result is a list containing a single numeric value representing
the number of lines in the file whose name is stored in the variable file.
switch ( <expression> )
case <first_case>:
zero or more lines containing shell commands
breaksw
case <second_case>:
zero or more lines containing shell commands
breaksw
.
.
.
case <last_case>:
zero or more lines containing shell commands
breaksw
default:
zero or more lines containing shell commands
endsw
- Example
switch ( $#arguments )
case 0:
zero or more lines containing shell commands
breaksw
case 1:
zero or more lines containing shell commands
breaksw
case 2:
zero or more lines containing shell commands
breaksw
default:
zero or more lines containing shell commands
endsw
- The value between the parentheses in the switch command does not need to be an integer, it can be a
word.
- Example
switch ( $argv[$i] )
case end:
breaksw
case list:
@ k = $i + 1
ls $argv[$k]
breaksw
case delete:
case erase:
@ k = $i + 1
rm $argv[$k]
breaksw
endsw
<label>:
zero or more lines containing shell commands
goto <label>
- A label can be placed anywhere in a shell script file, but it must be on a line by itself.
- The goto command can be placed anywhere before or after the label.
The following are a few special shell variables you might find useful.
1. $<: Indicates that the value of a variable should be read from standard input.
12. $0 … $9: These are special variable names for up to 10 arguments in a command line.
13. $argv and $*: Contain a list of command line arguments.
14. The shift command can be used as a convenient way to loop over all command line arguments.
- shift: Discards argv[1] and moves the remaining arguments one position to the left.
- It is an error for argv not to be set or to have less than one argument.
15. $#argv and $#: Contain the number of command line arguments.
Examples
You can copy and paste each shell script in this example and the following examples into a file and run it. They demonstrate most
of the useful features and characteristics of shell script programming.
1. SCRIPT = argDemo
#! /usr/bin/tcsh -f
if ( $#argv == 0 ) then
echo “argDemo: usage is argDemo <yes_response> or argDemo <no_response>”
exit 1
endif
switch ( $argv[1] )
case [yY][eE][sS]:
echo “argv[1] is ‘yes’”
breaksw
case [nN][oO]:
echo “argv[1] is ‘no’”
breaksw
default:
echo “Enter yes or no in any sequence of upper- and lower-case”
exit 1
endsw
exit 0
Note: Comments in a script file are lines that start with a #. However, due to a historical artifact in the development of the C-
shell, it was decided that lines beginning with the directive #! (which looks like it should be a comment) would make a script
file directly executable. That is, the kernel can start this program, even though it is not machine code. By default, if the first
line does not contain such a directive, the script file would be executed by the (more primitive) Bourne shell (i.e., /bin/sh).
Any options that would normally be supplied to the shell must then be given in the first-line shell directive. For example, the
-f option in the example above, ignores the ~.tcshrc file resulting in faster execution time.
Background on ~.tcshrc: The ~.tcshrc file is a file in your home directory that is read and its contents executed every
time tcsh is invoked. In addition, if a particular invocation of tcsh is the shell created at the time you login, a file named
~.login will be read and its contents executed immediately following the ~.tcshrc file. These files are typically used to
set variables and aliases that you would like to use (basically, they “customize” your shell session). The ~.tcshrc file
should be kept as short as possible because new shell instances are created fairly often. So, put the stuff that needs to be done
once in the ~.login file (e.g., most shell variables like path and prompt, environment variables, and most aliases).
Note: Scripts should be terminated by an exit command. Optionally, but typically, exit should return an error code to the
caller (e.g., exit 0 meaning no error and exit <n> meaning an error has occurred). This gives the caller an opportunity to
catch faulty behavior. After each command executes, the status shell variable contains the exit value (i.e., $status) of
that command.
16. SCRIPT = ifElseDemo
#! /usr/bin/tcsh -f
set class
set number = $argv[1]
exit 0
#! /usr/bin/tcsh -f
exit 0
18. SCRIPT = foreachDemo2
#! /usr/bin/tcsh -f
foreach i ( 10 15 20 40 )
echo $i
end
foreach i ( a b c 17 )
echo $i
end
exit 0
#! /usr/bin/tcsh -f
if ( $#argv != 1 ) then
echo "zap: usage is zap <user_id>"
exit 1
endif
set ps_test
while ( $i <= $#ps_output )
set line = ( $ps_output[$i] )
if ( $line[4] != "ps" && $line[4] != "tcsh" && $line[4] != "zap" ) then
set ps_test = ( $ps_test $i )
endif
@ i ++
end
foreach i ( $ps_test )
set line = ( $ps_output[$i] )
set process_no = $line[1]
RETRY:
echo
echo -n "zap: Kill CMD = ${line[4]} (PID = ${line[1]})? "
set response = $<
if ( $response =~ n* || $response =~ N* ) then
continue
else if ( $response =~ q* || $response =~ Q* ) then
break
else if ( $response =~ y* || $response =~ Y* ) then
kill -9 $process_no
continue
else
echo "zap: Invalid response: Enter y (yes), n (no), or q (quit)"
goto RETRY
endif
end
exit 0
#! /usr/bin/tcsh -f
if ( $#argv != 2 ) then
echo “saveFiles: usage $0 dir1 dir2”
exit 1
endif
if ( ! -e $dir1 ) then
echo “saveFiles: $dir1 does not exist”
exit 1
endif
if ( ! -d $dir1 ) then
echo “saveFiles: $dir1 is not a directory”
exit 1
endif
if ( -e $dir2 ) then
echo “saveFiles: $dir2 already exists”
exit 1
endif
mkdir $dir2
if ( $status != 0 ) exit 1
exit 0
In what follows, assume the name of the script file being debugged is scriptFile.
$ tcsh –n scriptFile
21. To output each line of the script file as it is checked for syntax errors:
23. To output each line of the script file after variable and command substitutions have occurred and before the line is
executed:
- Output statements or flags that indicate where the script is currently executing (like a trace).
- Example
Assuming that fileName contains junkola and trash contains /home/venus/hilder, then
mv junkola /home/venus/hilder/fileName
to the screen. That is, it does any expansions required, but does not actually execute the instruction. Note that what was
probably intended was to have a $ sign at the beginning of the second fileName.
25. Use an exit command to prevent a shell script from executing passed a particular point in the script.