47.2 C Shell Programming Considered HarmfulResolved: 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 . 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 he finds that many simple things he wishes to do range from cumbersome to impossible in the csh . What's more, lack 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. 47.2.1 File DescriptorsThe 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. 47.2.1.1 Writing FilesIn the Bourne shell, you can open or dup random file descriptors. For example, exec 2>errs.out means that from then on, stderr goes into the errs.out file. Or what if you just want to throw away stderr and leave stdout alone? Pretty simple operation, eh?
That works in the Bourne shell. In the C shell, you can only make a pitiful attempt like this:
But who said that stdout was my terminal? So it's wrong. This simple operation cannot be done in the C shell. Along these same lines, you can't direct error messages in csh scripts on stderr , as is considered proper. In the Bourne shell, you might say: echo "$0: cannot find $file" 1>&2 but in the C shell, you can't redirect stdout onto stderr so you end up doing something silly like this: sh -c "echo '${0}: cannot find $file' 1>&2" 47.2.1.2 Reading Files
In the csh
, all you've got is exec 3< file1 exec 4< file2 Now you can read from file descriptor 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 47.2.1.3 Closing FDs
In the Bourne shell, you can close file descriptors you don't
want open, like 47.2.1.4 More Elaborate CombinationsMaybe you want to pipe stderr to a command and leave stdout alone. Not too hard an idea, right? As I mentioned above, you can't do this in the C shell. In a Bourne shell, you can do things like this: $
Normal output would be unaffected.
The fd closes ( Consider the pipeline:
You want to know the status of C
, well, that's easy: it's in 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; 47.2.2 Command Orthogonality47.2.2.1 Built-InsThe csh is a horrid botch with its built-ins. You can't put them together in any reasonable way. Even a simple little thing like this: % while nonsensical, shouldn't give me this message: Reset tty pgrp from 9341 to 26678 Others are more fun: % Some can even hang your shell. Try typing CTRL-z while you're source ing something, or redirecting a source command. Just make sure you have another window handy. 47.2.2.2 Flow ControlYou can't mix flow control and commands, like this: who | while read line; do echo "gotta $line" done You can't combine multiline pipes constructs in a csh using semicolons. There's no easy way to do this: alias cmd 'if (foo) then bar; else snark; endif' 47.2.2.3 Stupid Parsing BugsCertain reasonable things just don't work, like this: % But this is ok: % If you have a stopped job: [2] Stopped rlogin globhost You should be able to kill it with: % but: % works. if(expr) may fail on some versions of csh, while: if (expr) works! 47.2.3 SignalsIn the C shell, 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 temporary file 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 47.2.4 QuotingYou can't quote things reasonably in the csh : set foo = "Bill asked, \"How's tricks?\"" doesn't work. This makes it really hard (10.8 ) 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 ( set foo = "this is a \$dollar quoted and this is $HOME not quoted" dollar: Undefined variable. You have to use backslashes ( % 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 several newlines.' 47.2.5 Variable SyntaxThere's this big difference between global -vironment) 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
% It's really nice to be able to say
You can't get the process number of the last background command from the
C shell, 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 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: % And watch the fun! 47.2.6 Expression EvaluationConsider this statement in the csh : if ($?MANPAGER) setenv PAGER $MANPAGER Despite your attempts to set only PAGER when you want to, the csh aborts: 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 endif That's the same problem you have here: % This forces you to write a couple of nested if statements. This is highly undesirable because it renders short-circuit Booleans useless in situations like these. If the csh were 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. 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 but this is okay: @ a = 4 / 2 47.2.7 Error HandlingWouldn'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 are correct. Alas, the csh implementation of this doesn't work. Consider this statement: exit (i) Of course, they really meant: exit (1) or just: exit 1 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 the C shell tells you there's nothing wrong with this script. The equivalent construct in the Bourne shell, on the other hand, tells you this: #!/bin/sh -n if (1) then exit (i) endif /tmp/x: syntax error at line 3: `(' unexpected 47.2.8 Random BugsHere's one: !%s%x%s Core dump, or garbage. If you have an alias with backquotes ( Try this: % What??? While some vendors have fixed some of the csh 's bugs (the tcsh (8.3 ) 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 Bourne shell. - | ||||
|