Penguin
Diff: CshProgrammingConsideredHarmful
EditPageHistoryDiffInfoLikePages

Differences between version 3 and revision by previous author of CshProgrammingConsideredHarmful.

Other diffs: Previous Major Revision, Previous Revision, or view the Annotated Edit History

Newer page: version 3 Last edited on Tuesday, December 28, 2004 2:03:12 am by AristotlePagaltzis Revert
Older page: version 2 Last edited on Monday, October 13, 2003 7:16:54 pm by CraigBox Revert
@@ -1,23 +1,25 @@
-From http://www.faqs.org/faqs/unix-faq/shell/csh-whynot/ 
+Originally found at http://www.faqs.org/faqs/unix-faq/shell/csh-whynot/.  
  
- From: Tom Christiansen <[mailto:tchrist@mox.perl.com]>  
- Newsgroups: [comp.unix.shell|news://comp.unix.shell], [comp.unix.questions|news://comp.unix.questions],  
- [comp.unix.programmer|news://comp.unix.programmer], [comp.infosystems.www.authoring.cgi|news://comp.infosystems.www.authoring.cgi]  
- Subject: Csh Programming Considered Harmful  
- Date: 6 Oct 1996 14:03:18 GMT  
- Message-ID: <538e76$8uq$1@csnews.cs.colorado.edu> 
+<pre>  
+ From: Tom Christiansen <[mailto:tchrist@mox.perl.com]>  
+Newsgroups: [comp.unix.shell|news://comp.unix.shell], [comp.unix.questions|news://comp.unix.questions],  
+ [comp.unix.programmer|news://comp.unix.programmer], [comp.infosystems.www.authoring.cgi|news://comp.infosystems.www.authoring.cgi]  
+Subject: Csh Programming Considered Harmful  
+Date: 6 Oct 1996 14:03:18 GMT  
+Message-ID: <538e76$8uq$1@csnews.cs.colorado.edu> 
  
- Archive-name: unix-faq/shell/csh-whynot  
- Version: $Id: csh-faq,v 1.7 95/09/28 12:52:17 tchrist Exp Locker: tchrist $ 
+Archive-name: unix-faq/shell/csh-whynot  
+Version: $Id: csh-faq,v 1.7 95/09/28 12:52:17 tchrist Exp Locker: tchrist $  
+</pre>  
  
-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 /pub/perl/versus/csh.whynot.gz ''Ed: this address is no longer valid.'' 
+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.] '' 
  
-!!! *** CSH PROGRAMMING CONSIDERED HARMFUL *** 
+!!! ~ *~ ** CSH PROGRAMMING CONSIDERED HARMFUL ~ *~ ** 
  
-!!Resolved: The csh is a tool utterly inadequate for programming, and its use for such purposes should be strictly banned! 
+!! Resolved: The 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 csh. Lack of proficiency in the BourneShell 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. 
+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. 
  
 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. 
  
 !!! 1. FILE DESCRIPTORS 
@@ -27,74 +29,96 @@
 !! 1a. Writing Files 
  
 In the BourneShell, you can open or dup arbitrary file descriptors. For example, 
  
- exec 2>errs.out 
+ <verbatim>  
+ exec 2>errs.out  
+ </verbatim>  
  
 means that from then on, STDERR goes into errs file. 
  
 Or what if you just want to throw away STDERR and leave STDOUT alone? Pretty simple operation, eh? 
  
- cmd 2>/dev/null 
+ <verbatim>  
+ cmd 2>/dev/null  
+ </verbatim>  
  
 Works in the BourneShell. In the csh, you can only make a pitiful attempt like this: 
  
- (cmd > /dev/tty) >& /dev/null 
+ <verbatim>  
+ (cmd > /dev/tty) >& /dev/null  
+ </verbatim>  
  
 But who said that STDOUT was my tty? So it's wrong. This simple operation __''cannot be done''__ in the csh. 
  
 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: 
  
- echo "$: cannot find $file" 1>&2 
+ <verbatim>  
+ echo "$: cannot find $file" 1>&2  
+ </verbatim>  
  
 but in the csh, you can't redirect STDOUT out STDERR, so you end up doing something silly like this: 
  
- sh -c 'echo "$: cannot find $file" 1>&2' 
+ <verbatim>  
+ sh -c 'echo "$: cannot find $file" 1>&2'  
+ </verbatim>  
  
 !! 1b. Reading Files 
  
 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: 
  
- exec 3<file1  
- exec 4<file2 
+ <verbatim>  
+ exec 3<file1  
+ exec 4<file2  
+ </verbatim>  
  
 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 <&3  
- read another_var <&4 
+ <verbatim>  
+ read some_var <&3  
+ read another_var <&4  
+ </verbatim>  
  
 Although in older ones where read only goes from 0, you trick it: 
  
- exec 5<&0 # save old STDIN  
- exec <&3; read some_var  
- exec <&4; read another_var  
- exec <&5 # restore it 
+ <verbatim>  
+ exec 5<&0 # save old STDIN  
+ exec <&3; read some_var  
+ exec <&4; read another_var  
+ exec <&5 # restore it  
+ </verbatim>  
  
 !! 1c. Closing FDs 
  
-In the BourneShell, you can close file descriptors you don't want open, like 2>&-, which isn't the same as redirecting it to __ /dev/null__
+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>
  
 !! 1d. More Elaborate Combinations 
  
 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: 
  
- 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 
+ <verbatim>  
+ 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  
+ </verbatim>  
  
-Normal output would be unaffected. The closes there were in case something really cared about all its FDs. We send STDERR to sed(1) , and then put it back out 2. 
+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 
+ <verbatim>  
+ A | B | C  
+ </verbatim>  
  
-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 __ grep -v__ pipe to get rid of the records in/out noise, but had to return the dd(1)'s exit status, not the grep's: 
+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: 
  
- device=/dev/rmt8  
- dd_noise='^[ [-9]+\+[ [-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; 
+ <verbatim>  
+ device=/dev/rmt8  
+ dd_noise='^[-9]+\+[-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;  
+ </verbatim>  
  
 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. 
  
 !!! 2. COMMAND ORTHOGONALITY 
@@ -102,323 +126,444 @@
 !! 2a. Built-ins 
  
 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: 
  
- % time | echo 
+ <verbatim>  
+ % time | echo  
+ </verbatim>  
  
 which while nonsensical, shouldn't give me this message: 
  
- Reset tty pgrp from 9341 to 26678 
+ <verbatim>  
+ Reset tty pgrp from 9341 to 26678  
+ </verbatim>  
  
 Others are more fun: 
  
- % sleep 1 | while  
- while: Too few arguments.  
- [ [5] 9402  
- % jobs  
- [ [5] 9402 Done sleep | 
+ <verbatim>  
+ % sleep 1 | while  
+ while: Too few arguments.  
+ [5] 9402  
+ % jobs  
+ [5] 9402 Done sleep |  
+ </verbatim>  
  
 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 
  
- % history | more 
+ <verbatim>  
+ % history | more  
+ </verbatim>  
  
 on some systems. 
  
 Aliases are not evaluated everywhere you would like them do be: 
  
- % alias lu 'ls -u'  
- % lu  
- HISTORY News bin fortran lib lyrics misc tex  
- Mail TEX dehnung hpview logs mbox netlib  
- % repeat 3 lu  
- lu: Command not found.  
- lu: Command not found.  
- lu: Command not found. 
+ <verbatim>  
+ % alias lu 'ls -u'  
+ % lu  
+ HISTORY News bin fortran lib lyrics misc tex  
+ Mail TEX dehnung hpview logs mbox netlib  
+ % repeat 3 lu  
+ lu: Command not found.  
+ lu: Command not found.  
+ lu: Command not found.  
+ </verbatim>  
  
- % time lu  
- lu: Command not found. 
+ <verbatim>  
+ % time lu  
+ lu: Command not found.  
+ </verbatim>  
  
 !! 2b. Flow control 
  
 You can't mix flow-control and commands, like this: 
  
- who | while read line; do  
- echo "gotta $line"  
- done 
+ <verbatim>  
+ who | while read line; do  
+ echo "gotta $line"  
+ done  
+ </verbatim>  
  
 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' 
+ <verbatim>  
+ alias cmd 'if (foo) then bar; else snark; endif'  
+ </verbatim>  
  
 (See [Foo] and [Bar].) 
  
 You can't perform redirections with if statements that are evaluated solely for their exit status: 
  
- if ( { grep vt100 /etc/termcap > /dev/null } ) echo ok 
+ <verbatim>  
+ if ( { grep vt100 /etc/termcap > /dev/null } ) echo ok  
+ </verbatim>  
  
 And even pipes don't work: 
  
- if ( { grep vt100 /etc/termcap | sed 's/$/###' } ) echo ok 
+ <verbatim>  
+ if ( { grep vt100 /etc/termcap | sed 's/$/###' } ) echo ok  
+ </verbatim>  
  
 But these work just fine in the BourneShell: 
  
- if grep vt100 /etc/termcap > /dev/null ; then echo ok; fi 
+ <verbatim>  
+ if grep vt100 /etc/termcap > /dev/null ; then echo ok; fi  
+ </verbatim>  
  
- if grep vt100 /etc/termcap | sed 's/$/###/' ; then echo ok; fi 
+ <verbatim>  
+ if grep vt100 /etc/termcap | sed 's/$/###/' ; then echo ok; fi  
+ </verbatim>  
  
 Consider the following reasonable construct: 
  
- if ( { command1 | command2 } ) then  
- ...  
- endif 
+ <verbatim>  
+ if ( { command1 | command2 } ) then  
+ ...  
+ endif  
+ </verbatim>  
  
 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 
  
- if command1 | command2 ; then  
- ...  
- fi 
+ <verbatim>  
+ if command1 | command2 ; then  
+ ...  
+ fi  
+ </verbatim>  
  
 !! 2c. Stupid parsing bugs 
  
 Certain reasonable things just don't work, like this: 
  
- % kill -1 `cat foo`  
- `cat foo`: Ambiguous. 
+ <verbatim>  
+ % kill -1 `cat foo`  
+ `cat foo`: Ambiguous.  
+ </verbatim>  
  
 But this is ok: 
  
- % /bin/kill -1 `cat foo` 
+ <verbatim>  
+ % /bin/kill -1 `cat foo`  
+ </verbatim>  
  
 If you have a stopped job: 
  
- [ [2] Stopped rlogin globhost 
+ <verbatim>  
+ [2] Stopped rlogin globhost  
+ </verbatim>  
  
 You should be able to kill it with 
  
- % kill %?glob  
- kill: No match 
+ <verbatim>  
+ % kill %?glob  
+ kill: No match  
+ </verbatim>  
  
 but 
  
- % fg %?glob 
+ <verbatim>  
+ % fg %?glob  
+ </verbatim>  
  
 works. 
  
 White space can matter: 
  
- if(expr) 
+ <verbatim>  
+ if(expr)  
+ </verbatim>  
  
 may fail on some versions of csh, while 
  
- if (expr) 
+ <verbatim>  
+ if (expr)  
+ </verbatim>  
  
 works! Your vendor may have attempted to fix this bug, but odds are good that their csh still won't be able to handle 
  
- if() then  
- !if(1) then  
- echo A: got here  
- else  
- echo B: got here  
- endif  
- echo We should never execute this statement  
- endif 
+ <verbatim>  
+ if() then  
+ !if(1) then  
+ echo A: got here  
+ else  
+ echo B: got here  
+ endif  
+ echo We should never execute this statement  
+ endif  
+ </verbatim>  
  
 !!! 3. SIGNALS 
  
 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: 
  
- $ trap 'rm -f /usr/adm/tmp/i$$ ;  
- echo "ERROR: abnormal exit";  
- exit' 1 2 3 15 
+ <verbatim>  
+ $ trap 'rm -f /usr/adm/tmp/i$$ ;  
+ echo "ERROR: abnormal exit";  
+ exit' 1 2 3 15  
+ </verbatim>  
  
- $ trap 'rm tmp.$$' 0 # on program exit 
+ <verbatim>  
+ $ trap 'rm tmp.$$' 0 # on program exit  
+ </verbatim>  
  
 !!! 4. QUOTING 
  
 You can't quote things reasonably in the csh: 
  
- set foo = "Bill asked, \"How's tricks?\"" 
+ <verbatim>  
+ set foo = "Bill asked, \"How's tricks?\""  
+ </verbatim>  
  
 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: 
  
- cd /mnt; /usr/ucb/finger -m -s `ls \`u\`` 
+ <verbatim>  
+ cd /mnt; /usr/ucb/finger -m -s `ls \`u\``  
+ </verbatim>  
  
 Dollar signs cannot be escaped in double quotes in the csh. Ug. 
  
- set foo = "this is a \$dollar quoted and this is $HOME not quoted"  
- dollar: Undefined variable. 
+ <verbatim>  
+ set foo = "this is a \$dollar quoted and this is $HOME not quoted"  
+ dollar: Undefined variable.  
+ </verbatim>  
  
 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 ". 
+ <verbatim>  
+ set foo = "this \  
+ and that";  
+ echo $foo  
+ this and that  
+ echo "$foo"  
+ Unmatched ".  
+ </verbatim>  
  
 Say what? You don't have these problems in the BourneShell, where it's just fine to write things like this: 
  
- echo 'This is  
- some text that contains  
- several newlines.' 
+ <verbatim>  
+ echo 'This is  
+ some text that contains  
+ several newlines.'  
+ </verbatim>  
  
 As distributed, quoting history references is a challenge. Consider: 
  
- % mail adec23!alberta!pixel.Convex.COM!tchrist  
- alberta!pixel.Convex.COM!tchri: Event not found. 
+ <verbatim>  
+ % mail adec23!alberta!pixel.Convex.COM!tchrist  
+ alberta!pixel.Convex.COM!tchri: Event not found.  
+ </verbatim>  
  
 !!! 5. VARIABLE SYNTAX 
  
 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 the BourneShell, this 
- VAR=foo cmds args 
+  
+ <verbatim>  
+ VAR=foo cmds args  
+ </verbatim>  
+  
 is the same as 
- (export VAR; VAR=foo; cmd args) 
+  
+ <verbatim>  
+ (export VAR; VAR=foo; cmd args)  
+ </verbatim>  
+  
 or csh's 
- (setenv VAR; cmd args) 
+  
+ <verbatim>  
+ (setenv VAR; cmd args)  
+ </verbatim>  
  
 You can't use :t, :h, etc on envariables. Watch: 
- echo Try testing with $SHELL:t 
+  
+ <verbatim>  
+ echo Try testing with $SHELL:t  
+ </verbatim>  
  
 It's really nice to be able to say 
  
- ${PAGER-more} 
+ <verbatim>  
+ ${PAGER-more}  
+ </verbatim>  
+  
 or 
- FOO=${BAR:-${BAZ}} 
+  
+ <verbatim>  
+ FOO=${BAR:-${BAZ}}  
+ </verbatim>  
  
 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 BourneShell, the pid of the last command put in the background is available in $!. 
  
 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: 
  
- % setenv TERM '`/bin/ls -l / > /dev/tty`'  
- % csh -f 
+ <verbatim>  
+ % setenv TERM '`/bin/ls -l / > /dev/tty`'  
+ % csh -f  
+ </verbatim>  
  
 And watch the fun! 
  
 !!! 6. EXPRESSION EVALUATION 
  
 Consider this statement in the csh: 
  
- if ($?MANPAGER) setenv PAGER $MANPAGER 
+ <verbatim>  
+ if ($?MANPAGER) setenv PAGER $MANPAGER  
+ </verbatim>  
  
 Despite your attempts to only set PAGER when you want to, the csh aborts: 
  
- MANPAGER: Undefined variable. 
+ <verbatim>  
+ MANPAGER: Undefined variable.  
+ </verbatim>  
  
 That's because it parses the whole line anyway __and evaluates it__! You have to write this: 
  
- if ($?MANPAGER) then  
- setenv PAGER $MANPAGER  
- endif 
+ <verbatim>  
+ if ($?MANPAGER) then  
+ setenv PAGER $MANPAGER  
+ endif  
+ </verbatim>  
  
 That's the same problem you have here: 
  
- if ($?X && $X == 'foo') echo ok  
- X: Undefined variable 
+ <verbatim>  
+ if ($?X && $X == 'foo') echo ok  
+ X: Undefined variable  
+ </verbatim>  
  
 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) 
+ <verbatim>  
+ if (p && p->member)  
+ </verbatim>  
  
 Undefined variables are not fatal errors in the BourneShell, so this issue does not arise there. 
  
 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 
  
- @ a = 4/2 
+ <verbatim>  
+ @ a = 4/2  
+ </verbatim>  
  
 but this is ok 
  
- @ a = 4 / 2 
+ <verbatim>  
+ @ a = 4 / 2  
+ </verbatim>  
  
 The ad hoc parsing csh employs fouls you up in other places as well. Consider: 
  
- % alias foo 'echo hi' ; foo  
- foo: Command not found.  
- % foo  
- hi 
+ <verbatim>  
+ % alias foo 'echo hi' ; foo  
+ foo: Command not found.  
+ % foo  
+ hi  
+ </verbatim>  
  
 !!! 7. ERROR HANDLING 
  
 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: 
  
- exit (i) 
+ <verbatim>  
+ exit (i)  
+ </verbatim>  
  
 Of course, they really meant 
  
- exit (1) 
+ <verbatim>  
+ exit (1)  
+ </verbatim>  
  
 or just 
  
- exit 1 
+ <verbatim>  
+ exit 1  
+ </verbatim>  
  
 Either shell will complain about this. But if you hide this in an if clause, like so: 
  
- #!/bin/csh -fn  
- if (1) then  
- exit (i)  
- endif 
+ <verbatim>  
+ #!/bin/csh -fn  
+ if (1) then  
+ exit (i)  
+ endif  
+ </verbatim>  
  
 The csh tells you there's nothing wrong with this script. The equivalent construct in the BourneShell, on the other hand, tells you this: 
  
- #!/bin/sh -n  
- if (1) then  
- exit (i)  
- endif 
+ <verbatim>  
+ #!/bin/sh -n  
+ if (1) then  
+ exit (i)  
+ endif  
+ </verbatim>  
  
- /tmp/x: syntax error at line 3: `(' unexpected 
+ <verbatim>  
+ /tmp/x: syntax error at line 3: `(' unexpected  
+ </verbatim>  
  
 !! RANDOM BUGS 
  
 Here's one: 
  
- fg %?string  
- ^Z  
- kill %?string  
- No match. 
+ <verbatim>  
+ fg %?string  
+ ^Z  
+ kill %?string  
+ No match.  
+ </verbatim>  
  
 Huh? Here's another 
  
- !%s%x%s 
+ <verbatim>  
+ !%s%x%s  
+ </verbatim>  
  
 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 
+  
+ <verbatim>  
+ % repeat 3 echo "/vmu*"  
+ /vmu*  
+ /vmunix  
+ /vmunix  
+ </verbatim>  
+  
 What??? 
  
 Here's another one: 
- % mkdir tst  
- % cd tst  
- % touch 'foobar'  
- % foreach var ( * )  
- > echo "File named $var"  
- > end  
- foreach: No match. 
+  
+ <verbatim>  
+ % mkdir tst  
+ % cd tst  
+ % touch 'foobar'  
+ % foreach var ( * )  
+ > echo "File named $var"  
+ > end  
+ foreach: No match.  
+ </verbatim>  
  
 !!! 8. SUMMARY 
  
 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. 
  
-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. 
+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. 
  
 There are other possibilities. 
  
 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. 
  
 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. 
  
-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.) 
+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.) 
  
-If you have a problem that would ordinarily use sed(1) or awk(1) 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(1) and awk(1) 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]. 
+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]. 
  
-See the comp.lang.{perl,rexx,tcl} 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. 
+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. 
  
-__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. 
+__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.