Unix Support frequently advises people not to use
/bin/csh. Here is the classic document
by Tom Christiansen (
on why you shouldn't use it for scripts.
csh is a tool utterly inadequate for programming, and
its use for such purposes should be strictly banned.
I am continually shocked and dismayed to see people write test
cases, install scripts, and other random hackery using the
of proficiency in the Bourne shell has been known to cause errors in
/etc/rc and .cronrc files, which is a problem, because you
must write these files in that language.
csh is seductive because the conditionals are more C-like, so
the path of least resistance if chosen and a
csh script is written.
Sadly, this is a lost cause, and the programmer seldom even realizes
it, even when they find that many simple things they wish to do range
from cumbersome to impossible in the
The most common problem encountered in
csh programming is that you
can't do file-descriptor manipulation. All you are able to do is
stdout, or dup
Bourne-compatible shells offer you an abundance of more exotic
In the Bourne shell, you can open or dup random file descriptors.
means that from then on,
stderr goes into errs file.
Or what if you just want to throw away
stderr and leave
alone? Pretty simple operation, eh?
Works in the Bourne shell. In the
csh, you can only make a pitiful
attempt like this:
(cmd > /dev/tty) >& /dev/null
But who said that
stdout was my tty? So it's wrong. This simple
operation cannot be done in the
csh, all you've got is
$<, which reads a line
from your tty. What if you've redirected
stdin? Tough noogies, you
still get your tty, which you really can't redirect. Now, the
statement in the Bourne shell allows you to read from
catches redirection. It also means that you can do things like this:
Now you can read from fd 3 and get lines from file1, or from file2 through fd 4. In modern, Bourne-like shells, this suffices:
read some_var 0<&3
read another_var 0<&4
Although in older ones where
read only goes from 0, you trick it:
exec 5<&0 # save old stdin
exec 0<&3; read some_var
exec 0<&4; read another_var
exec 0<&5 # restore it
In the Bourne shell, you can close file descriptors you don't want
2>&-, which isn't the same as
redirecting it to
Maybe you want to pipe
stderr to a command and leave
Not too hard an idea, right? You can't do this in the
csh as I
mentioned in 1a. In a Bourne shell, you can do things like this:
exec 3>&1; grep yyy xxx 2>&1 1>&3 3>&- | sed s/file/foobar/ 1>&2 3>&-
grep: xxx: No such foobar or directory
Normal output would be unaffected. The closes there were in case something really cared about all its FDs. We send
stderr to sed,
and then put it back out 2.
Consider the pipeline:
A | B | C
You want to know the status of C, well, that's easy: it's in
csh. But if you want it
from A, you're out of luck -- if you're in the
csh, that is. In the
Bourne shell, you can get it, although doing so is a bit tricky.
Here's something I had to do where I ran dd's
stderr into a grep -v
pipe to get rid of the records in/out noise, but had to return the dd's
exit status, not the grep's:
dd_noise='^[0-9]+\+[0-9]+ records (in|out)$'
status=`((dd if=$device ibs=64k 2>&1 1>&3 3>&- 4>&-; echo $? >&4) |
egrep -v "$dd_noise" 1>&2 3>&- 4>&-) 4>&1`
csh is a horrid botch with its built-ins. You can't put them
together in many reasonable way. Even simple little things like
% time | echo
which while nonsensical, shouldn't give me this message:
Reset tty pgrp from 9341 to 26678
Others are more fun:
% sleep 1 | while
while: Too few arguments.
 9402 Done sleep |
Some can even hang your shell. Try typing
you're sourcing something, or redirecting a source command. Just make
sure you have another window handy.
You can't mix flow-control and commands, like this:
who | while read line; do
echo "gotta $line"
You can't combine multiline constructs in a
csh using semicolons.
There's no easy way to do this
alias cmd 'if (foo) then bar; else snark; endif'
Certain reasonable things just don't work, like this:
% kill -1 `cat foo`
`cat foo`: Ambiguous.
But this is ok:
% /bin/kill -1 `cat foo`
If you have a stopped job:
 Stopped rlogin globhost
You should be able to kill it with
% kill %?glob
kill: No match
% fg %?glob
csh, all you can do with signals is trap
SIGINT. In the Bourne
shell, you can trap any signal, or the end-of-program exit. For example,
to blow away a tempfile on any of a variety of signals:
$ trap 'rm -f /usr/adm/tmp/i$$ ;
echo "ERROR: abnormal exit";
exit' 1 2 3 15
$ trap 'rm tmp.$$' 0 # on program exit
You can't quote things reasonably in the
set foo = "Bill asked, \"How's tricks?\""
doesn't work. This makes it really hard to construct strings with mixed quotes in them. In the Bourne shell, this works just fine. In fact, so does this:
cd /mnt; /usr/ucb/finger -m -s `ls \`u\``
Dollar signs cannot be escaped in double quotes in the
set foo = "this is a \$dollar quoted and this is $HOME not quoted"
dollar: Undefined variable.
You have to use backslashes for newlines, and it's just darn hard to get them into strings sometimes.
set foo = "this \
this and that
Say what? You don't have these problems in the Bourne shell, where it's just fine to write things like this:
echo 'This is
some text that contains
There's this big difference between global (environment) and local
(shell) variables. In
csh, you use a totally different syntax
to set one from the other.
In Bourne shell, this
VAR=foo cmds args
is the same as
(export VAR; VAR=foo; cmd args)
(setenv VAR; cmd args)
You can't use :t, :h, etc on envariables. Watch:
echo Try testing with $SHELL:t
It's really nice to be able to say
to be able to run the user's
PAGER if set, and more otherwise.
You can't do this in the
csh. It takes more verbiage.
You can't get the process number of the last background command from the
csh, something you might like to do if you're starting up several jobs in
the background. In the Bourne shell, the pid of the last command put in
the background is available in
Consider this statement in the
if ($?MANPAGER) setenv PAGER $MANPAGER
Despite your attempts to only set
PAGER when you want
MANPAGER: Undefined variable.
That's because it parses the whole line anyway and evaluates it! You have to write this:
if ($?MANPAGER) then
setenv PAGER $MANPAGER
That's the same problem you have here:
if ($?X && $X == 'foo') echo ok
X: Undefined variable
This forces you to write a couple nested
if statements. This is highly
undesirable because it renders short-circuit booleans useless in
situations like these. If the
csh were the really C-like, you would
expect to be able to safely employ this kind of logic. Consider the
common C construct:
if (p && p->member)
Undefined variables are not fatal errors in the Bourne shell, so this issue does not arise there.
csh does have built-in expression handling, it's not
what you might think. In fact, it's space sensitive. This is an
@ a = 4/2
but this is ok
@ a = 4 / 2
Wouldn't it be nice to know you had an error in your script before
you ran it? That's what the
-n flag is for: just check the syntax.
This is especially good to make sure seldom taken segments of code
code are correct. Alas, the
csh implementation of this doesn't work.
Consider this statement:
Of course, they really meant
Either shell will complain about this. But if you hide this in an
clause, like so:
if (1) then
csh tells you there's nothing wrong with this script. The equivalent
construct in the Bourne shell, on the other hand, tells you this:
if (1) then
/tmp/x: syntax error at line 3: `(' unexpected
Huh? Here's another
Coredump, or garbage.
If you have an alias with backquotes, and use that in backquotes in another one, you get a coredump.
% repeat 3 echo "/vmu*"
While some vendors have fixed some of the
csh's bugs (the tcsh also
does much better here), most of its problems can never be solved
because they're a result of braindead design decisions. Do yourself
a favor, and if you have to write a shell script, do it in the