Why you shouldn't program in CSH

I am an avid supporter of any programming language that is not csh. Actually, in my opinion, csh isn't a programming language, so therefore I am in support of any programming language. :)

Personally, I prefer perl and sh/bash.

I append here an HTML-ized version of a posting from 1991 by Tom Christiansen (who, at that time, was tchrist@convex.com-- I don't know if this is still a valid address), on why one should not program in csh.

>From the keyboard of apl@world.std.com (Anthony P Lawrence):
:So, the question is: why is the csh so inappropriate for shell 
:programming and why don't those using it realize this?

I've been trying to hold back my "don't do programming in csh"
warnings to one a day, but since you asked, let me attempt to 
spell out some good reasons.  I preface this from the tail of the
csh man page on Suns.

    Although robust enough for general use, adventures
    into the esoteric periphery of the C shell may reveal
    unexpected quirks.

So here's a bunch of reasons why not to use the csh for programming.
I suspect that after reading them you'll agree.

--tom

1. FILE DESCRIPTORS

The most common problem encountered in csh programming is that you can't do file-descriptor manipulation. All you are able to do is redirect stdin, or stdout, or dup stderr into stdout. Bourne-compatible shells offer you an abundance of more exotic possibilies.

1a. Writing Files

In the Bourne shell, you can open or dup random file descriptors. For example,
	exec 2>errs.out
means that from then on, all of stderr goes into errs file. Or what if you just want to throw away stderr and leave stdout alone?
	cmd 2>/dev/null
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.

1b. Reading Files

In the csh, all you've got is $<, which reads a line from your tty. Read in the Bourne shell allows you to read from stdin. This allows you do do things like this:
	exec 3<file1
	exec 4<file2
Now you can read from fd 3 and get lines from file1, or from file2 through fd 4. In modern bournelike 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

1c. Closing FDs

In the Bourne shell, you can close file descriptors you don't want open, like:
2>&-
which isn't the same as redirecting it to /dev/null

1d. More Elaborate Combinations

Maybe you want to pipe stderr to a command and leave stdout alone. You can't do this in 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 it's FDs. We send stderr to the 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 $?, or $status in csh. But if you want it from A, you're out of luck. If you're in the csh. In the Bourne shell, you can get it. 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:
	device=/dev/rmt8
	dd_noise='^[0-9]+\+[0-9]+ records (in|out)$'
	exec 3>&1
	status=`((dd if=$device ibs=64k 2>&1 1>&3 3>&- 4>&-; echo $? >&4) |
		    egrep -v "$dd_noise" 1>&2 3>&- 4>&-) 4>&1`
	exit $status;

2. COMMAND ORTHOGONALITY

2a. Built-ins

The csh is a horrid botch with its built-ins. You can't put them together in many reasonable way. Even simple little things like this:
		% 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.
		[5] 9402
		% jobs
		[5]     9402 Done                 sleep |
Some can even hang your shell. Try typing ^Z while you're sourcing something. Or redirecting a source command.

2b. Flow control

You can't mix flow-control and commands, like this:
	
	who | while read line; do
	    echo "gotta $line"
	done
You can't combine multiline things in a csh using semicolons. There's no easy way to do this
	alias cmd 'if (foo) then bar; else snark; endif'

2c. Stupid non-orthogonal parsing bugs

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`

3. SIGNALS

In the 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

4. QUOTING

You can't quote things reasonably in the csh:
	set foo = 'isn\'t this so?'
doesn't work. This makes it really hard to construct strings with mixed quotes in them. Dollar signs cannot be escaped in doublequotes in the csh. Ug.
	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 \
	and that";
	echo $foo
	this  and that
	echo "$foo"
	Unmatched ".  # say what???
	echo $foo:q
You don't have these problems in the Bourne shell.

5. VARIABLES

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)
or csh's
	(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
	
	${PAGER-more}
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 verbage. You can't get the process number of the last background command from the csh. In the Bourne shell, it's $!.

6. RANDOM BUGS

Here's one:
	fg %?string
	^Z
	kill  %?string
	No match.
Huh? Here's another
	!%s%x%s
Coredump, or garbage. If you have an alias with backquotes, and use that in backquotes in another one, you get a coredump. Try this:
	% repeat 3 echo "/vmu*"
	/vmu*
	/vmunix
	/vmunix
What??? There are a lot, probably over 100, of these.