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