Chapter 7

C-Shell Scripting Language

0. Table of Contents

        1. Working With Variables
        2. Invoking Shell Scripts
        3. Conditional Statements
        4. Iteration Using Loops
        5. Command Substitution
        6. Advanced Topics


1. Working With Variables

% set var = one
% echo $var
one
% set n = 1
% echo $n
1

What can be done can also be undone.  Use 'unset' to remove any
values (bindings) associated with a variable name.

% unset n
% echo $n
csh: n: Undefined variable.

Lists (sometimes referred to as arrays) have special syntax.

% set lst = ( one two three four )
% echo $lst
one two three four
% echo $lst[1]
one
% echo $lst[2]
two
% echo $lst[4]
four

You can also reference subsequences of a list.

% echo $lst[2-3]
two three

But you can't reference elements that don't exist.

% echo $lst[5]
lst: Subscript out of range.

The syntax $#name refers to the number of items in a list.

% echo $#lst
4
% echo $lst[$#lst]
four

You can use 'set' to change the elements in a list. 

% set lst[4] = five
% echo lst[4]
five

You can use 'shift' to shift all the items in a list to left 
thereby eliminating what was the first item in the list and 
reducing the number of items in the list by one.

% shift lst
% echo $lst[1]
two
% echo $lst[4] 
lst: Subscript out of range.
% echo $#lst
3

Use '@' instead of 'set' if you want to perform some math on
the right hand side of the '=' sign.  All math calculations 
operate only on integers and return only integers. 

% @ n = 3 * 4
% echo $n
12

You can use parentheses to group operations but leave space between
operators and operands.  If in doubt add spaces; in most cases it 
won't hurt. 

% @ n = ( 3 * ( 4 + 5 ) )
% echo $n
27

Note that a variable name appearing on the left hand side of an
assignment ('=') doesn't have a preceding '$' but variables 
appearing on the right hand side must be preceded by a '$'.

% @ n = 2 + 3
% @ n = $n + 1
% echo $n
6
% @ m = 2 * $n
% echo $m
12

Referencing a variable is one place where adding space can cause
problems.  The following is probably not what you intended.

% echo $ n
$ n

Note that integer division always rounds down.

% @ n = 5 / 3
% echo $n
1

You can use the modulus operator '%' to determine the remainder
after division. 

% @ n = 5 % 3
% echo $n
2

There are several convenient shorthand operators. 

% @ n = 1
% echo $n
1
% @ n ++ 
% echo $n
2
% @ n += 5
% echo $n
7
% @ n *= 5
% echo $n
35

Here are some special variables worth knowing about: 'cwd' is the
current working directory, 'home' is the user's home directory, 'path'
is the sequence of directories that the shell traverses when searching
for a program, and 'shell' is the currently executing shell program.

% echo $cwd
/u/tld/tmp
% echo $home
/u/tld
% echo $path
/bin /usr/bin /usr/local/bin /u/tld/bin .
% echo $shell
/bin/csh

The variable 'argv' is available only inside a shell script where it
is bound to the list of words (arguments) that appear on the command
line to the right of the name of file containing the shell script. 
The following assignments should be self explanatory.

set list_of_args = ( $argv )
set first_arg = $argv[1]
set second_argument = $argv[2]
set number_of_args = $#argv

There are also short variable names that you can use to refer to
the arguments appearing on the command line: $1 refers to the first
argument, $2 to the second argument, and so on.  In addition, $0
refers to the file of commands (script) to be executed.

set command = $0
set first_argument = $1
set second_argument = $2


2. Invoking Shell Scripts

When writing a shell script it is useful to imagine yourself 
typing each statement in turn, for this is essentially what
the shell does in executing a shell script.  You can execute a 
shell script written in the C-shell scripting language by 
submitting the script to a shell program as in:

% csh script

You can also add a directive as the first line of the script
indicating the shell program to use for execution.

% cat script
#!/bin/csh
...

To invoke this script without explicitly submitting it to a 
shell you must use 'chmod' to make the script executable.

% chmod +x script

Now you should be able to execute the shell by simply typing 
its name to the prompt.

% script


3. Conditional Statements

'if' - do something on the condition that ...

The syntax of conditional statements varies depending on the form of
the antecedent (the 'then' part of an conditional statement).  If the
antecedent is a simple command and the condition and antecedent can
both fit on the same line, then you don't need to use the 'then'
keyword, indeed it will cause an error if you do include it.

if ( condition ) something

% set x = 0
% if ( $x == 0 ) echo "Big fat zero"
Big fat zero
% if ( ! ( $x > 0 ) && ! ( $x < 0 ) ) echo "Big fat zero"
Big fat zero
% set x = 1
% if ( $x != 0 ) echo "It ain't zero"
It ain't zero
% if ( ! ( $x == 0 ) ) echo "It ain't zero"
Ain't zero
% if ( ! $x ) echo "Big fat zero"
% set x = 0
% if ( ! $x ) echo "Big fat zero"
Big fat zero

In the last two conditionals, we're exploiting the fact that 0 
is interpreted as false and hence '! 0' is interpreted as true.

For more complex antecedents (e.g., multiple commands) or statements
requiring additional conditions (e.g., an if-then-else statement) you
must use the 'then' keyword and it must appear exactly as shown below.

if ( condition ) then
  something
  something
endif

or

if ( condition ) then
  something
  something
else if ( condition ) then
  something
  something
endif 

if ( $x > 0 ) then
  echo "$x is greater than zero"
else if ( $x < 0 ) then
  echo "$x is less than zero"
else if ( $x = 0 ) then
  echo "$x is equal to zero"
endif

Note that using a variable bound to something other than 
a number in a condition expecting a number is an error. 

% set x = "not a number"
% if ( $x > 0 ) echo "Oops"
tcsh: if: Expression Syntax.
% set x = 1
% if ( $x > 0 ) echo "OK"
OK


4. Iteration Using Loops

'foreach' - do something for each item in a list of items

foreach variable ( list )
 something
 something
end

A script that sums the numbers appearing on the command line:

% echo sumnums
#!/bin/csh
set r = 0
foreach n ( $argv )
@ r += $n
end
echo $r
% sumnums 4 6 7 8
25

Note that 'n' in 'foreach n ( $argv )' isn't preceded by a '$' but
'argv' is preceded by a '$'.  Think of the 'foreach' loop as setting
the variable 'n' to each item in the argument list.  From this
perspective, 'foreach n ( $argv )' is analogous to 'set n = $argv[$i]'
for 'set i = 1' to 'set i = $#argv'.

'while' - do something as long as some condition is met

while ( condition )
  something
  something
end

The condition can be a flag set to 1 or 0, an inequality involving '<'
or '>', a string comparison such as '=~' or '!~' involving a pattern
on the right hand side, or a complex expression involving conjunction
('&&'), disjunction ('||'), and negation ('!').  Unless the loop is to
cycle indefinitely, there must be some aspect of the program that
changes so that eventually the exit condition is met (evaluates to
true) and the loop terminates.  In the following example, the exit
condition '$i <= $n' is met when the variable 'i' which is incremented
as part of each iteration of the loop becomes greater than 'n'.

% cat sequence
#!/bin/csh
set n = $argv
set i = 1
set r = 0
while ( $i <= $n )
   @ r += $i
   @ i ++
end
echo $r
% sequence 5
45

Here's how you'd translate a 'foreach' loop into a 'while' loop.

                             set i = 1
                             while ( $i <= $#argv )
foreach n ( $argv )            @ n = $argv[$i] 
  ... $n ...                   ... $n ...
  ... $n ...                   ... $n ...
end                            @ i ++
                             end

You can 'nest' loops; you can have 'foreach' loops inside of 'while'
loops or visa versa.  The 'break' command allows you to terminate the
inner-most loop in which the 'break' appears.  Here's a simple example
illustrating the use of the 'break' statement in checking a list of
numbers for primes:

% cat primes
#!/bin/csh
foreach n ( $argv ) 
  @ d = $n / 2
  while ( $d > 1 )
    @ r = $n % $d
    if ( $r == 0 ) break
    @ d --
  end 
  if ( $d == 1 ) echo $n
end


5. Command Substitution

The backtick or backquote enables you to assign a string or variable
the output of a command.

% echo "`date +%h` is the `date +%m`th month."
Sep is the 09th month.

When assigned to a variable the output of a command results in the 
variable being assigned a list (or array) of words

% ls
09-15-04.txt    09-20-04.txt    09-22-04.txt    09-24-04.txt    09-27-04.txt
% set exercises = ( `ls` )
% echo $exercises
09-15-04.txt 09-20-04.txt 09-22-04.txt 09-24-04.txt 09-27-04.txt

As with any list you can determine how many items it contains, refer to
any particular item in the list by its index, or refer to a subsequence
of the items in a list.

% echo $#exercises
5
% echo $exercises[1]
09-15-04.txt
% echo $exercises[2]
09-20-04.txt
% echo $exercises[2-4]
09-20-04.txt 09-22-04.txt 09-24-04.txt


6. Advanced Topics

Using subroutines in shell scripts - advanced topic #1

You've already been using subroutines in shells; every time you invoke
a command inside of a script you're executing a subroutine.  In some
cases, we execute a command for its 'side effects'.  For example, you
might execute 'mkdir images' to create a directory.  The creation of
the new directory is called a 'side effect'.  In other cases, e.g.,
command substitution (above), we execute a command for the output 
returned to the shell.

Unless you take pains to avoid it, the output of commands is directed
to the standard output and you see the output scrolling across your
screen.  It doesn't matter whether you execute a command directly in
the shell or indirectly by executing a script which then executes
other commands:

% cat testout
#!/bin/csh
echo "Starting 'testout'"
subtest
echo "Finishing 'testout'"
% cat subtest
#!/bin/csh
echo "Executing 'subtest'"
% testout
Starting 'testout'
Executing 'subtest'
Finishing 'testout'

You can, of course, redirect output to files using '>' or '>>' and you
can pipe the output from one command into another command as in 'ls |
wc -l' (which counts the number of files and directories in the
current working directory).  You can also use the backtick operator as
just mentioned to set a variable as in 'set var = `ls`' or specify the
list in a 'foreach' loop as in 'foreach var ( `ls ) ... end'. In the
following, we illustrate how use the output generated by one command
can determine the flow of control of another command by using
conditional statements.  First we define a command that checks to see
if its only argument is prime.

% cat isprime
#!/bin/csh
set n = $argv
set p = 1
@ d = $n / 2
while ( $d > 1 && $p )
  @ r = $n % $d
  if ( $r == 0 ) set p = 0
  @ d --
end 
if ( $p ) then
  echo 1
else 
  echo 0
endif

The echo commands are placed so that 'isprime' outputs a one (1) if its
sole argument is prime and a zero (0) otherwise.  The 'isprime' command is
now a general utility that we can use in any number of contexts.  We
can use the 'isprime' command in a conditional statement:

% if ( `isprime 3` ) echo "Prime"
Prime
% if ( ! `isprime 4` ) echo "Composite"
Composite

We can also use 'isprime' to write a more compact version of the
'primes' command:

% cat primes.sub
#!/bin/csh
foreach n ( $argv ) 
  if ( `isprime $n` ) echo $n
end


Exiting from a shell command using 'exit' - advanced topic #2

The 'exit' command allows you to exit from a script at any point.


Getting user input in a shell script - advanced topic #3

The pseudo variable '$<' substitutes a line read from the standard
input, with no further interpretation.  It can be used to read from
the keyboard in a shell script.  See if you can figure out what the
following script does:

% cat guess 
#!/bin/csh
set n = `date +"%M"`
while ( 1 )
  set m = $<
  if ( $m > $n ) then
    echo "Too high"
  else if ( $m < $n ) then
    echo "Too low"
  else 
    echo "You got it"
    exit
  endif
end


Using the exit status of a command - advanced topic #4

Every Unix command returns an exit status.  By convention, if the
command is successful, it returns an exit status of zero.  If the
command fails, then it returns an exit status greater than zero with
the exact value depending on how it failed; for example, grep returns
one (1) if it can't find the pattern it's looking for and two (2) if
it can't find the file supplied as its second argument.

In the C-shell the 'status' variable, is set to the exit status of
the last command executed:

% grep while isprime 
while ( $d > 1 )
% echo $status
0
% grep foreach isprime 
% echo $status
1

In writing a C-shell script you can use the keyword 'exit' with an
argument to make the script return a particular exit status based on
whatever you define as success or failure. 

Here is another version of 'isprime' that returns an exit status of
zero (success) if its only argument is prime and one (failure)
otherwise.

% cat isprime
#!/bin/csh
set n = $argv
@ d = $n / 2
while ( $d > 1 )
  @ r = $n % $d
  if ( $r == 0 ) exit 1
  @ d --
end 
exit 0
% isprime 3
% echo $status
0
% isprime 4
% echo $status
1

There is a special syntax that allows you to use the exit status of 
a command in an 'if' statement; you use curly braces instead of 
the usual parentheses.  The logic of using commands as conditional
is a little twisted however.  Zero is false in a conditional while 
one is true. 

% if ( 0 ) echo "No"
% if ( ! 0 ) echo "No Way"
No Way
% if ( 1 ) echo "Way"
Way

In evaluating a command, if the command is successful (that is to say
it returns an exit status of zero), then the curly braces tell the
shell to return a one.  If the command fails (it returns an exit
status greater than zero), then the curly braces tell the shell to
return a zero.

% if { isprime 3 } echo "Prime"
Prime

Now we can rewrite the 'primes' command as follows:

% cat primes
#!/bin/csh
foreach n ( $argv ) 
  if { isprime $n } echo $n
end