The C shell dialect: same ideas, different language
◈ 9 cardsTranslate a Bourne script into C shell line by line, and name the three places the C shell differs in kind rather than in spelling: declaration before use, the exit-status variable, and the total absence of functions.
The delta, not a second course
Modules 9 and 10 built the Bourne shell script from the ground up. The C shell asks a script exactly the same questions — name a value, read an argument, branch, loop, report success — and answers almost all of them with different words. In three places it answers with different behaviour, and those three are what an exam question is really testing. Everything else is a lookup.
So this lesson translates rather than re-teaches. Here is a five-line Bourne script:
#!/bin/sh
count=0
export EDITOR=vi
count=`expr $count + 1`
echo "got $# arguments; count is $count"
### Line by line into the C shell
Line 1 — the interpreter. #!/bin/sh becomes #!/bin/csh. The kernel reads that line when the file is exec'd, so the interpreter travels with the file and it no longer matters which shell you launched it from. On a modern Linux box /bin/csh is usually tcsh wearing the older name; everything in this module is the common core that both accept.
Line 2 — assignment. count=0 becomes set count = 0. Two things changed. set is a command, so the variable name is an argument to it, and that means the spaces around the equals sign are allowed — in fact they read better. In the Bourne shell the opposite is true: count = 0 there is an attempt to run a program called count, because sh recognises an assignment only when the equals sign is welded to the name with no whitespace at all.
Line 3 — export. The Bourne shell splits the job in two: assign, then mark the name for export. The C shell fuses them into one verb. export EDITOR=vi becomes setenv EDITOR vi — note that there is no equals sign at all. setenv takes the name and the value as two separate arguments, and it both creates the variable and puts it in the environment that every child process inherits.
That gives the C shell three assignment verbs where the Bourne shell has one. set makes a shell-local variable. setenv makes an exported environment variable. @ makes a shell-local variable holding the result of integer arithmetic.
Line 4 — arithmetic. The Bourne shell has no integer arithmetic of its own, so it forks expr and reads the answer back through command substitution. The C shell has @ built in, and this is the one place it is genuinely the better tool: @ count++, or the long form @ count = $count + 1. No fork, no quoting of the multiplication sign, no reading a number back out of a subprocess.
Line 5 — the argument count. $# becomes $#argv. The C shell keeps the script's arguments in an ordinary array named argv, so the count is just that array's length. The Bourne short forms 2 and $# are also accepted, which makes the array spelling look optional; it is not, once you want to slice.
The finished translation:
#!/bin/csh
set count = 0
setenv EDITOR vi
@ count++
echo "got $#argv arguments; count is $count"
The three real differences
One: declaration before use is mandatory. In the Bourne shell, reading a name that was never set gives you the null string and the script keeps going. That silence is the cause of a whole family of bugs, and the C shell refuses to participate: reading an undeclared variable is a hard error that stops the script.
% echo "value is $total"
total: Undefined variable.
Every C shell variable has to be brought into existence first, by set, setenv or @. That is stricter, and on balance it is a good thing — a typo in a variable name is a diagnostic instead of an empty string. It also means you cannot write the Bourne idiom of letting an unset variable stand in for a default.
Two: the exit status has a different name. In the C shell the exit status of the last command is in the variable status, read as $status. The Bourne spelling is not a synonym here. In the C shell the question mark is a different operator entirely: written $?name, it asks whether the variable called name exists, and answers 1 or 0. The warning below is the single most important correction in this module.
Three: there are no functions. The Bourne shell grew functions in its second scripting chapter. The C shell has none, at all, in any version. The substitutes are a second script file invoked as a command, or goto with a label — and goto cannot take arguments or return a value, so it is not really a substitute at all. This absence is the strongest single technical argument against writing anything substantial in the C shell.
The multi-word trap
set takes a list of assignments, which produces a result nobody expects the first time:
% set name = John Doe II
% echo $name
John
The command did not fail and it did not bind three words to one name. It bound name to John, and then declared two brand-new variables called Doe and II, each holding the null string. To bind all three words to name you must say which you want: set name = "John Doe II" makes one string, and set name = (John Doe II) makes a three-element array. The parentheses are the array constructor, and they matter again in the next lesson.