Memra

onintr, and why C shell scripting lost

◈ 10 cards

Handle a keyboard interrupt in a C shell script with onintr, re-arm the handler correctly, and give the technical reasons — not the aesthetic ones — that C shell scripting is discouraged.

Catching the interrupt

A script that creates a temporary file, or that deliberately locks a terminal, needs to know when the user pressed Ctrl-C. The Bourne shell answers with trap. The C shell answers with onintr, and the comparison is short because onintr does much less.

There are three forms and no more:

onintr            # default: the interrupt kills the script
onintr -          # ignore the interrupt entirely
onintr label:     # on interrupt, transfer control to label:

Here is the terminal locker, rewritten. It refuses to be interrupted while it holds the terminal, and it cleans up when it is asked to stop properly:

#!/bin/csh
onintr -
stty -echo
echo -n "Locked. Password: "
set guess = $<
stty echo

onintr - is the C shell spelling of trap '' INT. With echo turned off, an interrupt would otherwise leave the terminal in an unusable state — which is precisely the hazard the ignore form exists for.

Re-arming, which is easy to lose

The handler form does not re-arm itself. Once control transfers to the label, the disposition is back to the default, so the second Ctrl-C kills the script. The idiom is to jump back past the onintr statement so it runs again:

#!/bin/csh
loop:
onintr handler:
echo "working ..."
sleep 5
goto loop
handler:
echo "interrupted - still working"
goto loop

Note what that costs: goto and a label, in both directions, because the C shell has no functions to hold the handler. This is a structural weakness showing through as a style problem.

What trap can do that onintr cannot

onintr handles the keyboard interrupt and nothing else. There is no signal list, no way to name a signal, no way to catch a hangup when the terminal goes away, no way to catch the termination signal a system shutdown sends, and no equivalent of the Bourne shell's EXIT pseudo-signal for guaranteed cleanup. A C shell script that must remove its temporary files if it is killed simply cannot promise that.

Always name signals rather than numbers, incidentally: the numbers differ between systems, and a script that hardcodes them is wrong on some machine you have not tested on.

The exclamation-mark detonation

One more C shell hazard, because it costs people whole afternoons. History substitution is active in scripts, and it is active inside double quotes:

% set job = backup
% echo "$job finished!!"

The shell expands the double exclamation mark to the previous command line before echo ever sees it, so the output is garbled and often accompanied by an unrelated Undefined variable. error from whatever the previous command contained. Escape it as a backslash-exclamation, or use one exclamation mark, or use single quotes. The Bourne shell has no history substitution in scripts at all, so this class of bug does not exist there.

Why the argument ended the way it did

Put honestly, and without the folklore. Against the C shell as a scripting language:

  • No functions. A script that wants to reuse a block repeats it, gotos to it, or becomes several files.
  • Signal handling is one signal wide. No trap equivalent, no cleanup on termination.
  • No until, and no elif. Minor, but they add up.
  • History substitution fires in scripts, so ! is a live hazard inside double quotes.
  • Portability. /bin/sh is required to exist on every conformant system. /bin/csh is not.

And in the C shell's favour, genuinely:

  • Built-in integer arithmetic with @, where the Bourne shell forks expr.
  • Real arrays, which the Bourne shell simply does not have.
  • Declaration before use, which turns a misspelled variable name into an error message instead of an empty string.

The honest conclusion is the one the community reached: the C shell is a pleasant interactive shell and a poor scripting language, so use it at the prompt if you like it and write scripts in the Bourne family. That is a defensible position with a reason attached, which is what an exam question on this asks for.

Capabilitysh: trapcsh: onintrname an arbitrary signallistyes - trap cmd INT HUP TERMno - the keyboard interruptonlyignore a signalyes - trap '' INTyes - onintr -run a handleryes - trap cleanup INTyes - onintr label:restore the defaultyes - trap - INTyes - onintr with noargumentre-arm after the handlerrunsyes - automaticno - goto back past theonintrguaranteed cleanup on exityes - trap cleanup EXITno equivalent at allName signals, never numbers: SIGCHLD is 17 on Linux and 20 on BSD and Solaris.
Every row that says no is a script that cannot be written in the C shell without leaving something behind.
NORMAL ~/memra/learn/comp-325/onintr-and-why-csh-scripting-lost utf-8 LF