Penguin
Blame: CshProgrammingConsideredHarmful
EditPageHistoryDiffInfoLikePages
Annotated edit history of CshProgrammingConsideredHarmful version 4, including all changes. View license author blame.
Rev Author # Line
3 AristotlePagaltzis 1 Originally found at http://www.faqs.org/faqs/unix-faq/shell/csh-whynot/.
4 JohnMcPherson 2
1 AristotlePagaltzis 3
3 AristotlePagaltzis 4 <pre>
5 From: Tom Christiansen <[mailto:tchrist@mox.perl.com]>
6 Newsgroups: [comp.unix.shell|news://comp.unix.shell], [comp.unix.questions|news://comp.unix.questions],
7 [comp.unix.programmer|news://comp.unix.programmer], [comp.infosystems.www.authoring.cgi|news://comp.infosystems.www.authoring.cgi]
8 Subject: Csh Programming Considered Harmful
9 Date: 6 Oct 1996 14:03:18 GMT
10 Message-ID: <538e76$8uq$1@csnews.cs.colorado.edu>
1 AristotlePagaltzis 11
3 AristotlePagaltzis 12 Archive-name: unix-faq/shell/csh-whynot
13 Version: $Id: csh-faq,v 1.7 95/09/28 12:52:17 tchrist Exp Locker: tchrist $
14 </pre>
1 AristotlePagaltzis 15
3 AristotlePagaltzis 16 The following periodic article answers in excruciating detail the frequently asked question “Why shouldn't I program in csh?”. It is available for anon [FTP] from perl.com in <tt>/pub/perl/versus/csh.whynot.gz</tt> ''~[This address is no longer valid. —Ed.]''
1 AristotlePagaltzis 17
3 AristotlePagaltzis 18 !!! ~*~** CSH PROGRAMMING CONSIDERED HARMFUL ~*~**
1 AristotlePagaltzis 19
3 AristotlePagaltzis 20 !! Resolved: The csh is a tool utterly inadequate for programming, and its use for such purposes should be strictly banned!
1 AristotlePagaltzis 21
3 AristotlePagaltzis 22 I am continually shocked and dismayed to see people write test cases, install scripts, and other random hackery using the csh. Lack of proficiency in the BourneShell has been known to cause errors in <tt>/etc/rc</tt> and <tt>.cronrc</tt> files, which is a problem, because you __must__ write these files in that language.
1 AristotlePagaltzis 23
24 The csh is seductive because the conditionals are more [C]-like, so the path of least resistance is 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 csh.
25
26 !!! 1. FILE DESCRIPTORS
27
28 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 possibilities.
29
30 !! 1a. Writing Files
31
32 In the BourneShell, you can open or dup arbitrary file descriptors. For example,
33
3 AristotlePagaltzis 34 <verbatim>
35 exec 2>errs.out
36 </verbatim>
1 AristotlePagaltzis 37
38 means that from then on, STDERR goes into errs file.
39
40 Or what if you just want to throw away STDERR and leave STDOUT alone? Pretty simple operation, eh?
41
3 AristotlePagaltzis 42 <verbatim>
43 cmd 2>/dev/null
44 </verbatim>
1 AristotlePagaltzis 45
46 Works in the BourneShell. In the csh, you can only make a pitiful attempt like this:
47
3 AristotlePagaltzis 48 <verbatim>
49 (cmd > /dev/tty) >& /dev/null
50 </verbatim>
1 AristotlePagaltzis 51
52 But who said that STDOUT was my tty? So it's wrong. This simple operation __''cannot be done''__ in the csh.
53
54 Along these same lines, you can't direct error messages in csh scripts out STDERR as is considered proper. In the BourneShell, you might say:
55
3 AristotlePagaltzis 56 <verbatim>
57 echo "$0: cannot find $file" 1>&2
58 </verbatim>
1 AristotlePagaltzis 59
60 but in the csh, you can't redirect STDOUT out STDERR, so you end up doing something silly like this:
61
3 AristotlePagaltzis 62 <verbatim>
63 sh -c 'echo "$0: cannot find $file" 1>&2'
64 </verbatim>
1 AristotlePagaltzis 65
66 !! 1b. Reading Files
67
68 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 read statement in the BourneShell allows you to read from STDIN, which catches redirection. It also means that you can do things like this:
69
3 AristotlePagaltzis 70 <verbatim>
71 exec 3<file1
72 exec 4<file2
73 </verbatim>
1 AristotlePagaltzis 74
75 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:
76
3 AristotlePagaltzis 77 <verbatim>
78 read some_var 0<&3
79 read another_var 0<&4
80 </verbatim>
1 AristotlePagaltzis 81
82 Although in older ones where read only goes from 0, you trick it:
83
3 AristotlePagaltzis 84 <verbatim>
85 exec 5<&0 # save old STDIN
86 exec 0<&3; read some_var
87 exec 0<&4; read another_var
88 exec 0<&5 # restore it
89 </verbatim>
1 AristotlePagaltzis 90
91 !! 1c. Closing FDs
92
3 AristotlePagaltzis 93 In the BourneShell, you can close file descriptors you don't want open, like 2>&-, which isn't the same as redirecting it to <tt>/dev/null</tt>.
1 AristotlePagaltzis 94
95 !! 1d. More Elaborate Combinations
96
97 Maybe you want to pipe STDERR to a command and leave STDOUT alone. Not too hard an idea, right? You can't do this in the csh as I mentioned in 1a. In a BourneShell, you can do things like this:
98
3 AristotlePagaltzis 99 <verbatim>
100 exec 3>&1; grep yyy xxx 2>&1 1>&3 3>&- | sed s/file/foobar/ 1>&2 3>&-
101 grep: xxx: No such foobar or directory
102 </verbatim>
1 AristotlePagaltzis 103
3 AristotlePagaltzis 104 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.
1 AristotlePagaltzis 105
106 Consider the pipeline:
107
3 AristotlePagaltzis 108 <verbatim>
109 A | B | C
110 </verbatim>
1 AristotlePagaltzis 111
3 AristotlePagaltzis 112 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, that is. In the BourneShell, you can get it, although doing so is a bit tricky. Here's something I had to do where I ran dd(1)'s STDERR into a <tt>grep -v</tt> pipe to get rid of the records in/out noise, but had to return the dd(1)'s exit status, not the grep's:
1 AristotlePagaltzis 113
3 AristotlePagaltzis 114 <verbatim>
115 device=/dev/rmt8
116 dd_noise='^[0-9]+\+[0-9]+ records (in|out)$'
117 exec 3>&1
118 status=`((dd if=$device ibs=64k 2>&1 1>&3 3>&- 4>&-; echo $? >&4) |
119 egrep -v "$dd_noise" 1>&2 3>&- 4>&-) 4>&1`
120 exit $status;
121 </verbatim>
1 AristotlePagaltzis 122
123 The csh has also been known to close all open file descriptors besides the ones it knows about, making it unsuitable for applications that intend to inherit open file descriptors.
124
125 !!! 2. COMMAND ORTHOGONALITY
126
127 !! 2a. Built-ins
128
129 The csh is a horrid botch with its built-ins. You can't put them together in many reasonable ways. Even simple little things like this:
130
3 AristotlePagaltzis 131 <verbatim>
132 % time | echo
133 </verbatim>
1 AristotlePagaltzis 134
135 which while nonsensical, shouldn't give me this message:
136
3 AristotlePagaltzis 137 <verbatim>
138 Reset tty pgrp from 9341 to 26678
139 </verbatim>
1 AristotlePagaltzis 140
141 Others are more fun:
142
3 AristotlePagaltzis 143 <verbatim>
144 % sleep 1 | while
145 while: Too few arguments.
146 [5] 9402
147 % jobs
148 [5] 9402 Done sleep |
149 </verbatim>
1 AristotlePagaltzis 150
151 Some can even hang your shell. Try typing ^Z while you're sourcing something, or redirecting a source command. Just make sure you have another window handy. Or try
152
3 AristotlePagaltzis 153 <verbatim>
154 % history | more
155 </verbatim>
1 AristotlePagaltzis 156
157 on some systems.
158
159 Aliases are not evaluated everywhere you would like them do be:
160
3 AristotlePagaltzis 161 <verbatim>
162 % alias lu 'ls -u'
163 % lu
164 HISTORY News bin fortran lib lyrics misc tex
165 Mail TEX dehnung hpview logs mbox netlib
166 % repeat 3 lu
167 lu: Command not found.
168 lu: Command not found.
169 lu: Command not found.
170 </verbatim>
1 AristotlePagaltzis 171
3 AristotlePagaltzis 172 <verbatim>
173 % time lu
174 lu: Command not found.
175 </verbatim>
1 AristotlePagaltzis 176
177 !! 2b. Flow control
178
179 You can't mix flow-control and commands, like this:
180
3 AristotlePagaltzis 181 <verbatim>
182 who | while read line; do
183 echo "gotta $line"
184 done
185 </verbatim>
1 AristotlePagaltzis 186
187 You can't combine multiline constructs in a csh using semicolons. There's no easy way to do this
188
3 AristotlePagaltzis 189 <verbatim>
190 alias cmd 'if (foo) then bar; else snark; endif'
191 </verbatim>
2 CraigBox 192
193 (See [Foo] and [Bar].)
1 AristotlePagaltzis 194
195 You can't perform redirections with if statements that are evaluated solely for their exit status:
196
3 AristotlePagaltzis 197 <verbatim>
198 if ( { grep vt100 /etc/termcap > /dev/null } ) echo ok
199 </verbatim>
1 AristotlePagaltzis 200
201 And even pipes don't work:
202
3 AristotlePagaltzis 203 <verbatim>
204 if ( { grep vt100 /etc/termcap | sed 's/$/###' } ) echo ok
205 </verbatim>
1 AristotlePagaltzis 206
207 But these work just fine in the BourneShell:
208
3 AristotlePagaltzis 209 <verbatim>
210 if grep vt100 /etc/termcap > /dev/null ; then echo ok; fi
211 </verbatim>
1 AristotlePagaltzis 212
3 AristotlePagaltzis 213 <verbatim>
214 if grep vt100 /etc/termcap | sed 's/$/###/' ; then echo ok; fi
215 </verbatim>
1 AristotlePagaltzis 216
217 Consider the following reasonable construct:
218
3 AristotlePagaltzis 219 <verbatim>
220 if ( { command1 | command2 } ) then
221 ...
222 endif
223 </verbatim>
1 AristotlePagaltzis 224
225 The output of command1 won't go into the input of command2. You will get the output of both commands on standard output. No error is raised. In the BourneShell or its clones, you would say
226
3 AristotlePagaltzis 227 <verbatim>
228 if command1 | command2 ; then
229 ...
230 fi
231 </verbatim>
1 AristotlePagaltzis 232
233 !! 2c. Stupid parsing bugs
234
235 Certain reasonable things just don't work, like this:
236
3 AristotlePagaltzis 237 <verbatim>
238 % kill -1 `cat foo`
239 `cat foo`: Ambiguous.
240 </verbatim>
1 AristotlePagaltzis 241
242 But this is ok:
243
3 AristotlePagaltzis 244 <verbatim>
245 % /bin/kill -1 `cat foo`
246 </verbatim>
1 AristotlePagaltzis 247
248 If you have a stopped job:
249
3 AristotlePagaltzis 250 <verbatim>
251 [2] Stopped rlogin globhost
252 </verbatim>
1 AristotlePagaltzis 253
254 You should be able to kill it with
255
3 AristotlePagaltzis 256 <verbatim>
257 % kill %?glob
258 kill: No match
259 </verbatim>
1 AristotlePagaltzis 260
261 but
262
3 AristotlePagaltzis 263 <verbatim>
264 % fg %?glob
265 </verbatim>
1 AristotlePagaltzis 266
267 works.
268
269 White space can matter:
270
3 AristotlePagaltzis 271 <verbatim>
272 if(expr)
273 </verbatim>
1 AristotlePagaltzis 274
275 may fail on some versions of csh, while
276
3 AristotlePagaltzis 277 <verbatim>
278 if (expr)
279 </verbatim>
1 AristotlePagaltzis 280
281 works! Your vendor may have attempted to fix this bug, but odds are good that their csh still won't be able to handle
282
3 AristotlePagaltzis 283 <verbatim>
284 if(0) then
285 !if(1) then
286 echo A: got here
287 else
288 echo B: got here
289 endif
290 echo We should never execute this statement
291 endif
292 </verbatim>
1 AristotlePagaltzis 293
294 !!! 3. SIGNALS
295
296 In the csh, all you can do with signals is trap SIGINT. In the BourneShell, 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:
297
3 AristotlePagaltzis 298 <verbatim>
299 $ trap 'rm -f /usr/adm/tmp/i$$ ;
300 echo "ERROR: abnormal exit";
301 exit' 1 2 3 15
302 </verbatim>
1 AristotlePagaltzis 303
3 AristotlePagaltzis 304 <verbatim>
305 $ trap 'rm tmp.$$' 0 # on program exit
306 </verbatim>
1 AristotlePagaltzis 307
308 !!! 4. QUOTING
309
310 You can't quote things reasonably in the csh:
311
3 AristotlePagaltzis 312 <verbatim>
313 set foo = "Bill asked, \"How's tricks?\""
314 </verbatim>
1 AristotlePagaltzis 315
316 doesn't work. This makes it really hard to construct strings with mixed quotes in them. In the BourneShell, this works just fine. In fact, so does this:
317
3 AristotlePagaltzis 318 <verbatim>
319 cd /mnt; /usr/ucb/finger -m -s `ls \`u\``
320 </verbatim>
1 AristotlePagaltzis 321
322 Dollar signs cannot be escaped in double quotes in the csh. Ug.
323
3 AristotlePagaltzis 324 <verbatim>
325 set foo = "this is a \$dollar quoted and this is $HOME not quoted"
326 dollar: Undefined variable.
327 </verbatim>
1 AristotlePagaltzis 328
329 You have to use backslashes for newlines, and it's just darn hard to get them into strings sometimes.
330
3 AristotlePagaltzis 331 <verbatim>
332 set foo = "this \
333 and that";
334 echo $foo
335 this and that
336 echo "$foo"
337 Unmatched ".
338 </verbatim>
1 AristotlePagaltzis 339
340 Say what? You don't have these problems in the BourneShell, where it's just fine to write things like this:
341
3 AristotlePagaltzis 342 <verbatim>
343 echo 'This is
344 some text that contains
345 several newlines.'
346 </verbatim>
1 AristotlePagaltzis 347
348 As distributed, quoting history references is a challenge. Consider:
349
3 AristotlePagaltzis 350 <verbatim>
351 % mail adec23!alberta!pixel.Convex.COM!tchrist
352 alberta!pixel.Convex.COM!tchri: Event not found.
353 </verbatim>
1 AristotlePagaltzis 354
355 !!! 5. VARIABLE SYNTAX
356
357 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.
358
359 In the BourneShell, this
3 AristotlePagaltzis 360
361 <verbatim>
362 VAR=foo cmds args
363 </verbatim>
364
1 AristotlePagaltzis 365 is the same as
3 AristotlePagaltzis 366
367 <verbatim>
368 (export VAR; VAR=foo; cmd args)
369 </verbatim>
370
1 AristotlePagaltzis 371 or csh's
3 AristotlePagaltzis 372
373 <verbatim>
374 (setenv VAR; cmd args)
375 </verbatim>
1 AristotlePagaltzis 376
377 You can't use :t, :h, etc on envariables. Watch:
3 AristotlePagaltzis 378
379 <verbatim>
380 echo Try testing with $SHELL:t
381 </verbatim>
1 AristotlePagaltzis 382
383 It's really nice to be able to say
384
3 AristotlePagaltzis 385 <verbatim>
386 ${PAGER-more}
387 </verbatim>
388
1 AristotlePagaltzis 389 or
3 AristotlePagaltzis 390
391 <verbatim>
392 FOO=${BAR:-${BAZ}}
393 </verbatim>
1 AristotlePagaltzis 394
395 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.
396
397 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 BourneShell, the pid of the last command put in the background is available in $!.
398
399 The csh is also flaky about what it does when it imports an environment variable into a local shell variable, as it does with HOME, USER, PATH, and TERM. Consider this:
400
3 AristotlePagaltzis 401 <verbatim>
402 % setenv TERM '`/bin/ls -l / > /dev/tty`'
403 % csh -f
404 </verbatim>
1 AristotlePagaltzis 405
406 And watch the fun!
407
408 !!! 6. EXPRESSION EVALUATION
409
410 Consider this statement in the csh:
411
3 AristotlePagaltzis 412 <verbatim>
413 if ($?MANPAGER) setenv PAGER $MANPAGER
414 </verbatim>
1 AristotlePagaltzis 415
416 Despite your attempts to only set PAGER when you want to, the csh aborts:
417
3 AristotlePagaltzis 418 <verbatim>
419 MANPAGER: Undefined variable.
420 </verbatim>
1 AristotlePagaltzis 421
422 That's because it parses the whole line anyway __and evaluates it__! You have to write this:
423
3 AristotlePagaltzis 424 <verbatim>
425 if ($?MANPAGER) then
426 setenv PAGER $MANPAGER
427 endif
428 </verbatim>
1 AristotlePagaltzis 429
430 That's the same problem you have here:
431
3 AristotlePagaltzis 432 <verbatim>
433 if ($?X && $X == 'foo') echo ok
434 X: Undefined variable
435 </verbatim>
1 AristotlePagaltzis 436
437 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:
438
3 AristotlePagaltzis 439 <verbatim>
440 if (p && p->member)
441 </verbatim>
1 AristotlePagaltzis 442
443 Undefined variables are not fatal errors in the BourneShell, so this issue does not arise there.
444
445 While the csh does have built-in expression handling, it's not what you might think. In fact, it's space sensitive. This is an error
446
3 AristotlePagaltzis 447 <verbatim>
448 @ a = 4/2
449 </verbatim>
1 AristotlePagaltzis 450
451 but this is ok
452
3 AristotlePagaltzis 453 <verbatim>
454 @ a = 4 / 2
455 </verbatim>
1 AristotlePagaltzis 456
457 The ad hoc parsing csh employs fouls you up in other places as well. Consider:
458
3 AristotlePagaltzis 459 <verbatim>
460 % alias foo 'echo hi' ; foo
461 foo: Command not found.
462 % foo
463 hi
464 </verbatim>
1 AristotlePagaltzis 465
466 !!! 7. ERROR HANDLING
467
468 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:
469
3 AristotlePagaltzis 470 <verbatim>
471 exit (i)
472 </verbatim>
1 AristotlePagaltzis 473
474 Of course, they really meant
475
3 AristotlePagaltzis 476 <verbatim>
477 exit (1)
478 </verbatim>
1 AristotlePagaltzis 479
480 or just
481
3 AristotlePagaltzis 482 <verbatim>
483 exit 1
484 </verbatim>
1 AristotlePagaltzis 485
486 Either shell will complain about this. But if you hide this in an if clause, like so:
487
3 AristotlePagaltzis 488 <verbatim>
489 #!/bin/csh -fn
490 if (1) then
491 exit (i)
492 endif
493 </verbatim>
1 AristotlePagaltzis 494
495 The csh tells you there's nothing wrong with this script. The equivalent construct in the BourneShell, on the other hand, tells you this:
496
3 AristotlePagaltzis 497 <verbatim>
498 #!/bin/sh -n
499 if (1) then
500 exit (i)
501 endif
502 </verbatim>
1 AristotlePagaltzis 503
3 AristotlePagaltzis 504 <verbatim>
505 /tmp/x: syntax error at line 3: `(' unexpected
506 </verbatim>
1 AristotlePagaltzis 507
508 !! RANDOM BUGS
509
510 Here's one:
511
3 AristotlePagaltzis 512 <verbatim>
513 fg %?string
514 ^Z
515 kill %?string
516 No match.
517 </verbatim>
1 AristotlePagaltzis 518
519 Huh? Here's another
520
3 AristotlePagaltzis 521 <verbatim>
522 !%s%x%s
523 </verbatim>
1 AristotlePagaltzis 524
525 Coredump, or garbage.
526
527 If you have an alias with backquotes, and use that in backquotes in another one, you get a coredump.
528
529 Try this:
3 AristotlePagaltzis 530
531 <verbatim>
532 % repeat 3 echo "/vmu*"
533 /vmu*
534 /vmunix
535 /vmunix
536 </verbatim>
537
1 AristotlePagaltzis 538 What???
539
540 Here's another one:
3 AristotlePagaltzis 541
542 <verbatim>
543 % mkdir tst
544 % cd tst
545 % touch 'foobar'
546 % foreach var ( * )
547 > echo "File named $var"
548 > end
549 foreach: No match.
550 </verbatim>
1 AristotlePagaltzis 551
552 !!! 8. SUMMARY
553
554 While some vendors have fixed some of the csh's bugs (the tcsh(1) also does much better here), many have added new ones. Most of its problems can never be solved because they're not actually bugs per se, but rather the direct consequences of braindead design decisions. It's inherently flawed.
555
3 AristotlePagaltzis 556 Do yourself a favor, and if you __have__ to write a shell script, do it in the BourneShell. It's on every [UNIX] system out there. However, behavior can vary.
1 AristotlePagaltzis 557
558 There are other possibilities.
559
560 The Korn shell is the preferred programming shell by many sh addicts, but it still suffers from inherent problems in the BourneShell's design, such as parsing and evaluation horrors. The Korn shell or its public-domain clones and supersets (like bash) aren't quite so ubiquitous as sh, so it probably wouldn't be wise to write a sharchive in them that you post to the net. When 1003.2 becomes a real standard that companies are forced to adhere to, then we'll be in much better shape. Until then, we'll be stuck with bug-incompatible versions of the sh lying about.
561
562 The Plan 9 shell, rc, is much cleaner in its parsing and evaluation; it is not widely available, so you'd be significantly sacrificing portability. No vendor is shipping it yet.
563
3 AristotlePagaltzis 564 If you don't have to use a shell, but just want an interpreted language, many other free possibilities present themselves, like [Perl], [REXX], [TCL], [Scheme], or [Python]. Of these, [Perl] is probably the most widely available on [UNIX] (and many other) systems and certainly comes with the most extensive [UNIX] interface. Increasing numbers vendors ship [Perl] with their standard systems. (See the [comp.lang.perl|news://comp.lang.perl] FAQ for a list.)
1 AristotlePagaltzis 565
3 AristotlePagaltzis 566 If you have a problem that would ordinarily use [SED] or [AWK] or sh, but it exceeds their capabilities or must run a little faster, and you don't want to write the silly thing in [C], then [Perl] may be for you. You can get at networking functions, binary data, and most of the [C] library. There are also translators to turn your [SED] and [AWK] scripts into [Perl] scripts, as well as a symbolic debugger. Tchrist's personal rule of thumb is that if it's the size that fits in a Makefile, it gets written in the BourneShell, but anything bigger gets written in [Perl].
1 AristotlePagaltzis 567
3 AristotlePagaltzis 568 See the <tt>comp.lang.{perl,rexx,tcl}</tt> newsgroups for details about these languages (including [FAQ]s), or David Muir Sharnoff's comparison of freely available languages and tools in comp.lang.misc and news.answers.
1 AristotlePagaltzis 569
3 AristotlePagaltzis 570 __NOTE__: [Doug Hamilton | mailto:hamilton@bix.com] has a program that he sells for profit for little toy non-[UNIX] systems. He calls it 'csh' or the 'hamilton csh', but it's not a csh as it's neither bug nor feature compatible with the real csh. Actually, he's fixed a great deal, but in doing so, has created a totally different shell.