[comp.sources.misc] v20i010: rc - A Plan 9 shell reimplementation, Part01/04

byron@archone.tamu.edu (Byron Rakitzis) (05/22/91)

Submitted-by: Byron Rakitzis <byron@archone.tamu.edu>
Posting-number: Volume 20, Issue 10
Archive-name: rc/part01

rc is a command interpreter and programming language similar
to  sh(1).  It is based on the AT&T plan 9 shell of the same
name.  The shell offers a C-like syntax (much more  so  than
the  C  shell),  and  a  powerful mechanism for manipulating
variables.  It is  reasonably  small  and  reasonably  fast,
especially when compared to contemporary shells.  Its use is
intended to be interactive, but the  language  lends  itself
well to scripts.

This shell was written by me, Byron Rakitzis, but kudos go to Paul
Haahr for letting me know what a shell should do and for contributing
certain bits and pieces to rc (notably the limits code, most of which.c
and the backquote redirection code), and to Hugh Redelmeier for running
rc through his fussy ANSI compiler and thereby provoking interesting
discussions about portability, and also for providing many valuable
suggestions for improving rc's code in general. Finally, many thanks
go to David Sanderson, for reworking the man page to format well with
troff, and for providing many suggestions both for rc and its man page.

Of course, without Tom Duff's design of the original rc, I could not
have written this shell (though I probably would have written *a*
shell). Almost of all of the features, with minor exceptions, have been
implemented as described in the Unix v10 manuals. Hats off to td for
designing a C-like, minimal but very useful shell.

Byron Rakitzis
--------------
#! /bin/sh
# This is a shell archive.  Remove anything before this line, then feed it
# into a shell via "sh file" or similar.  To overwrite existing files,
# type "sh file -c".
# The tool that generated this appeared in the comp.sources.unix newsgroup;
# send mail to comp-sources-unix@uunet.uu.net if you want that tool.
# Contents:  README builtins.c hash.h rc.1
# Wrapped by kent@sparky on Wed May 22 01:21:49 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo If this archive is complete, you will see the following message:
echo '          "shar: End of archive 1 (of 4)."'
if test -f 'README' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README'\"
else
  echo shar: Extracting \"'README'\" \(3564 characters\)
  sed "s/^X//" >'README' <<'END_OF_FILE'
XThis is release 1.0 of rc.
X
XRead COPYRIGHT for copying information. All files are
X
XCopyright 1991, Byron Rakitzis.
X
X------ Compiling rc:
X
XTo compile rc, you need an ANSI compiler like gcc. Some compilers which
Xgrok prototypes can also compile rc. In particular, I have successfully
Xcompiled rc using "cc" on the sgi running IRIX-3.3.1 (version 3.2 has
Xan older compiler---rc won't compile under that OS). I avoid using
Xstandard header files as much as possible.  In most cases, this is a
Xwin, but you should pay attention to stddef.h and to rc.h to make
Xsure the values there are suitable for your system.
X
XNote: if you have an IBM PC-RT, you *can* compile rc with IBM's
Xcompiler but you have to tweak the sources a little. I decided not to
Xsupport the RT because /usr/include does not have a stdarg.h, but
Xdesperate RT users can contact me for porting info.
X
XIf you are on a pure system V machine, you may have define certain
Xmacros to omit certain pieces of code. In particular, defining the
XNOLIMITS macro will #ifdef out the Berkeley limits code.
XAlso, the NOJOB macro if defined will omit certain calls to signal()
Xused to make rc work on Berkeley systems that assume csh. (note that rc
Xdoes *not* support csh-style job control in either case)
XSimilarly, the NONMPIPES macro omits support for <{} redirection on
Xsystems which do not support named pipes.
X
XFinally, on older systems without "<dirent.h>", the macro
X"NODIRENT" must be defined. This substitutes "<sys/dir.h>" for
X"<dirent.h>" and uses struct direct instead of struct dirent.
X
X------ Bugs:
X
XSend bug reports to byron@archone.tamu.edu. If a core dump is
Xgenerated, sending me a backtrace will help me out a great deal. You
Xcan get a backtrace like this:
X
X	; gdb rc core
X	(gdb) where
X	<<<BACKTRACE INFO>>>
X	(gdb)
X
XAlso, always report the machine, compiler and os used to make rc.  It's
Xpossible I may have access to a machine of that type, in which case it
Xbecomes much easier for me to track the bug down.
X
X------ Man page:
XThe man page works with nroff on my Sun, but I had to use groff in
Xorder to get the sample code to be typeset in typewriter type. I
Xam assuming that ditroff will also format rc.1 correctly for a
Xprinter. If anyone can tell me how to get BSD troff to do this,
XI would be grateful.
X
X------ Feeping Creaturism:
X
XSee the end of the man page, under "INCOMPATIBILITIES" for (known?)
Xdifferences from the "real" rc. Most of these changes were necessary
Xto get rc to work in a reasonable fashion on a real UNIX system; a
Xfew were changes motivated by concern about some inadequacies in
Xthe original design.
X
X------ Credits:
X
XThis shell was written by me, Byron Rakitzis, but kudos go to Paul
XHaahr for letting me know what a shell should do and for contributing
Xcertain bits and pieces to rc (notably the limits code, most of which.c
Xand the backquote redirection code), and to Hugh Redelmeier for running
Xrc through his fussy ANSI compiler and thereby provoking interesting
Xdiscussions about portability, and also for providing many valuable
Xsuggestions for improving rc's code in general. Finally, many thanks
Xgo to David Sanderson, for reworking the man page to format well with
Xtroff, and for providing many suggestions both for rc and its man page.
X
XOf course, without Tom Duff's design of the original rc, I could not
Xhave written this shell (though I probably would have written *a*
Xshell). Almost of all of the features, with minor exceptions, have been
Ximplemented as described in the Unix v10 manuals. Hats off to td for
Xdesigning a C-like, minimal but very useful shell.
END_OF_FILE
  if test 3564 -ne `wc -c <'README'`; then
    echo shar: \"'README'\" unpacked with wrong size!
  fi
  # end of 'README'
fi
if test -f 'builtins.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'builtins.c'\"
else
  echo shar: Extracting \"'builtins.c'\" \(10271 characters\)
  sed "s/^X//" >'builtins.c' <<'END_OF_FILE'
X/* builtins.c: the collection of rc's builtin commands */
X
X#include <setjmp.h>
X#ifndef NOLIMITS
X#include <sys/time.h>
X#include <sys/resource.h>
X#endif
X#include "rc.h"
X#include "utils.h"
X#include "walk.h"
X#include "input.h"
X#include "builtins.h"
X#include "hash.h"
X#include "nalloc.h"
X#include "status.h"
X#include "footobar.h"
X#include "lex.h"
X#include "open.h"
X#include "except.h"
X#include "redir.h"
X#include "glom.h"
X#include "tree.h"
X
Xextern int umask(int);
X
Xstatic void b_break(char **), b_builtin(char **), b_cd(char **),
X	b_echo(char **), b_eval(char **), b_exit(char **), b_limit(char **),
X	b_return(char **), b_shift(char **), b_umask(char **), b_wait(char **),
X	b_whatis(char **);
X
Xstatic builtin_t *const builtins[] = {
X	b_break, b_builtin, b_cd, b_echo, b_eval, b_exec, b_exit,
X	b_limit, b_return, b_shift, b_umask, b_wait, b_whatis, b_dot
X};
X
Xstatic char *const builtins_str[] = {
X	"break", "builtin", "cd", "echo", "eval", "exec", "exit",
X	"limit", "return", "shift", "umask", "wait", "whatis", "."
X};
X
Xbuiltin_t *isbuiltin(char *s) {
X	int i;
X
X	for (i = 0; i < arraysize(builtins_str); i++)
X		if (streq(builtins_str[i], s))
X			return builtins[i];
X	return NULL;
X}
X
X/* funcall() is the wrapper used to invoke shell functions. pushes $*, and "return" returns here. */
X
Xvoid funcall(char **av) {
X	jmp_buf j;
X	Estack e1, e2;
X
X	if (setjmp(j))
X		return;
X
X	starassign(*av, av+1, TRUE);
X	except(RETURN, j, &e1);
X	except(STAR, NULL, &e2);
X	walk(treecpy(fnlookup(*av),nalloc), TRUE);
X	varrm("*", TRUE);
X	unexcept(); /* STAR */
X	unexcept(); /* RETURN */
X}
X
X/* a dummy command. not really used to exec commands (exec() does this simply by not forking) */
X
Xvoid b_exec(char **av) {
X	if (av[1] == NULL) /* on a null exec, perform redirections */
X		doredirs();
X	return;
X}
X
X/* echo -n omits a newline. echo -- -n echos '-n' */
X
Xstatic void b_echo(char **av) {
X	SIZE_T i;
X	char *format = "%a\n";
X
X	if (*++av != NULL) {
X		if (streq(*av, "-n")) {
X			format = "%a";
X			av++;
X		} else if (streq(*av, "--")) {
X			av++;
X		}
X	}
X	
X	i = strarraylen(av) + 1; /* one for the null terminator */
X
X	if (i < FPRINT_SIZE)
X		fprint(1, format, av);
X	else
X		writeall(1, sprint(nalloc(i), format, av), i-1);
X	set(TRUE);
X}
X
X/* cd. traverse $cdpath if the directory given is not an absolute pathname */
X
Xstatic void b_cd(char **av) {
X	List *s, nil;
X	char *path = NULL;
X	SIZE_T t, pathlen = 0;
X
X	if (*++av == NULL) {
X		s = varlookup("home");
X		*av = (s == NULL) ? "/" : s->w;
X	}
X	if (isabsolute(*av)) { /* absolute pathname? */
X		if (chdir(*av) < 0) {
X			set(FALSE);
X			uerror(*av);
X		} else
X			set(TRUE);
X	} else {
X		s = varlookup("cdpath");
X		if (s == NULL) {
X			s = &nil;
X			nil.w = "";
X			nil.n = NULL;
X		}
X		do {
X			if (s != &nil && *s->w != '\0') {
X				t = strlen(*av) + strlen(s->w) + 2;
X				if (t > pathlen)
X					path = nalloc(pathlen = t);
X				strcpy(path, s->w);
X				strcat(path, "/");
X				strcat(path, *av);
X			} else {
X				pathlen = 0;
X				path = *av;
X			}
X			if (chdir(path) >= 0) {
X				set(TRUE);
X				if (interactive && *s->w != '\0' && !streq(s->w,"."))
X					fprint(1,"%s\n",path);
X				return;
X			}
X			s = s->n;
X		} while (s != NULL);
X		fprint(2,"couldn't cd to %s\n", *av);
X		set(FALSE);
X	}
X}
X
Xstatic void b_umask(char **av) {
X	int i;
X
X	if (*++av == NULL) {
X		set(TRUE);
X		i = umask(0);
X		umask(i);
X		fprint(2,"0%o\n",i);
X	} else {
X		i = o2u(*av);
X		if ((unsigned int) i > 0777) {
X			set(FALSE);
X			fprint(2,"bad umask\n");
X		} else {
X			set(TRUE);
X			umask(i);
X		}
X	}
X}
X
Xstatic void b_exit(char **av) {
X	int s;
X
X	if (av[1] == NULL)
X		rc_exit(getstatus());
X	if ((s = a2u(av[1])) >= 0)
X		rc_exit(s);
X	fprint(2,"%s is a bad number\n", av[1]);
X	rc_exit(1);
X}
X
X/* raise a "return" exception, i.e., return from a function. if an integer argument is present, set $status to it */
X
Xstatic void b_return(char **av) {
X	int s;
X
X	if (av[1] != NULL) {
X		s = a2u(av[1]);
X		if (s < 0) {
X			fprint(2,"%s is a bad number\n", av[1]);
X			set(FALSE);
X		} else {
X			setstatus(s << 8);
X		}
X	}
X	rc_raise(RETURN);
X}
X
X/* raise a "break" exception for breaking out of for and while loops */
X
Xstatic void b_break(char **av) {
X	rc_raise(BREAK);
X}
X
X/* shift $* n places (default 1) */
X
Xstatic void b_shift(char **av) {
X	int shift;
X	List *s, *dollarzero;
X
X	shift = (av[1] == NULL ? 1 : a2u(av[1]));
X
X	if (shift < 0) {
X		fprint(2,"%s is a bad number\n", av[1]);
X		set(FALSE);
X		return;
X	}
X
X	s = varlookup("*")->n;
X	dollarzero = varlookup("0");
X
X	while(s != NULL && shift != 0) {
X		s = s->n;
X		--shift;
X	}
X
X	varassign("*", append(dollarzero, s), FALSE);
X	set(TRUE);
X}
X
X/* works by advancing argv by one, really. This means that if builtin is defined as a function you are hosed */
X
Xstatic void b_builtin(char **av) {
X	builtin_t *b;
X
X	if (av[1] == NULL) {
X		set(FALSE);
X		fprint(2,"no arguments to 'builtin'\n");
X		return;
X	}
X	if ((b = isbuiltin(av[1])) == NULL) {
X		set(FALSE);
X		fprint(2,"no such builtin\n");
X	} else {
X		set(TRUE);
X		b(++av);
X	}
X}
X
X/* wait for a given process, or all outstanding processes */
X
Xstatic void b_wait(char **av) {
X	int stat, pid;
X
X	if (av[1] == NULL) {
X		pid = wait(&stat);
X		if (pid < 0)
X			uerror("wait");
X		while(wait(&stat) >= 0)
X			;
X		return;
X	} else {
X		pid = a2u(av[1]);
X		if (pid < 0) {
X			set(FALSE);
X			fprint(2,"%s is a bad number\n", av[1]);
X			return;
X		}
X		while (pid != wait(&stat))
X			if (pid < 0)
X				uerror("wait");
X	}
X	if (pid < 0)
X		set(FALSE);
X	else
X		setstatus(stat);
X}
X
X/*
X   whatis without arguments prints all variables and functions. Otherwise, check to see if a name
X   is defined as a variable, function or pathname.
X*/
X
Xstatic void b_whatis(char **av) {
X	enum bool f,found;
X	int i,j;
X	List *s,*t;
X	Node *n;
X	char *e;
X
X	if (av[1] == NULL) {
X		whatare_all_vars();
X		set(TRUE);
X		return;
X	}
X	found = TRUE;
X	for (i = 1; av[i] != NULL; i++) {
X		f = FALSE;
X		if ((s = varlookup(av[i])) != NULL) {
X			f = TRUE;
X			fprint(1,"%s=%s", av[i], (s->n == NULL ? "" : "("));
X			for (t = s; t->n != NULL; t = t->n)
X				fprint(1,"%s ",strprint(t->w, FALSE, TRUE));
X			fprint(1,"%s%s\n",strprint(t->w, FALSE, TRUE), (s->n == NULL ? "" : ")"));
X		}
X		if ((n = fnlookup(av[i])) != NULL) {
X			f = TRUE;
X			fprint(1,"fn %s {%s}\n",av[i],ptree(n));
X		} else if (isbuiltin(av[i]) != NULL) {
X			f = TRUE;
X			for (j = 0; j < arraysize(builtins_str); j++)
X				if (streq(av[i], builtins_str[j]))
X					break;
X			fprint(1,"builtin %s\n",builtins_str[j]);
X		} else if ((e = which(av[i])) != NULL) {
X			f = TRUE;
X			fprint(1,"%s\n",e);
X		}
X		if (!f) {
X			found = FALSE;
X			fprint(2,"%s not found\n", av[i]);
X		}
X	}
X
X	set(found);
X}
X
X/* push a string to be eval'ed onto the input stack. evaluate it */
X
Xstatic void b_eval(char **av) {
X	boolean i = interactive;
X
X	if (av[1] == NULL)
X		return;
X
X	interactive = FALSE;
X	pushinput(STRING, av + 1);
X	doit(TRUE);
X	interactive = i;
X}
X
X/*
X   push a file to be interpreted onto the input stack. with "-i" treat this as an interactive
X   input source.
X*/
X
Xvoid b_dot(char **av) {
X	int fd;
X	boolean old_i = interactive, i = FALSE;
X	Estack e;
X
X	av++;
X
X	if (*av == NULL || **av == '\0')
X		return;
X
X	if (streq(*av,"-i")) {
X		av++;
X		i = TRUE;
X	}
X
X	if (dasheye) { /* rc -i file has to do the right thing. reset the dasheye state to FALSE, though. */
X		dasheye = FALSE;
X		i = TRUE;
X	}
X
X	fd = rc_open(*av, FROM);
X
X	if (fd < 0) {
X		if (rcrc) /* on rc -l, don't flag nonexistence of .rcrc */
X			rcrc = FALSE;
X		else
X			uerror(*av);
X		set(FALSE);
X		return;
X	}
X	rcrc = FALSE;
X
X	starassign(*av, av+1, TRUE);
X	pushinput(FD, fd);
X	interactive = i;
X	except(STAR, NULL, &e);
X	doit(TRUE);
X	varrm("*", TRUE);
X	unexcept(); /* STAR */
X	interactive = old_i;
X}
X
X/* Berkeley limit support was cleaned up by Paul Haahr. */
X
X#ifdef NOLIMITS
Xstatic void b_limit(char **av) {
X	fprint(2,"rc was compiled without berkeley limits\n");
X	set(FALSE);
X}
X#else
X
Xtypedef struct Suffix Suffix;
Xstruct Suffix {
X	const Suffix *next;
X	long amount;
X	char *name;
X};
X
Xstatic const Suffix
X	kbsuf = { NULL, 1024, "k" },
X	mbsuf = { &kbsuf, 1024*1024, "m" },
X	gbsuf = { &mbsuf, 1024*1024*1024, "g" },
X	stsuf = { NULL, 1, "s" },
X	mtsuf = { &stsuf, 60, "m" },
X	htsuf = { &mtsuf, 60*60, "h" };
X#define	SIZESUF &gbsuf
X#define	TIMESUF &htsuf
X
Xtypedef struct {
X	char *name;
X	int flag;
X	const Suffix *suffix;
X} Limit;
Xstatic const Limit limits[] = {
X	{ "cputime",		RLIMIT_CPU,	TIMESUF },
X	{ "filesize",		RLIMIT_FSIZE,	SIZESUF },
X	{ "datasize",		RLIMIT_DATA,	SIZESUF },
X	{ "stacksize",		RLIMIT_STACK,	SIZESUF },
X	{ "coredumpsize",	RLIMIT_CORE,	SIZESUF },
X	{ "memoryuse",		RLIMIT_RSS,	SIZESUF },
X	{ NULL, 0, NULL }
X};
X
Xextern int getrlimit(int, struct rlimit *);
Xextern int setrlimit(int, struct rlimit *);
X
Xstatic void printlimit(const Limit *limit, boolean hard) {
X	struct rlimit rlim;
X	long lim;
X	getrlimit(limit->flag, &rlim);
X	if (hard)
X		lim = rlim.rlim_max;
X	else
X		lim = rlim.rlim_cur;
X	if (lim == RLIM_INFINITY)
X		fprint(1, "%s \tunlimited\n", limit->name);
X	else {
X		const Suffix *suf;
X		for (suf = limit->suffix; suf != NULL; suf = suf->next)
X			if (lim % suf->amount == 0) {
X				lim /= suf->amount;
X				break;
X			}
X		fprint(1, "%s \t%d%s\n", limit->name, lim, suf == NULL ? "" : suf->name);
X	}
X}
X
Xstatic long parselimit(const Limit *limit, char *s) {
X	int len = strlen(s);
X	long lim = 1;
X	const Suffix *suf = limit->suffix;
X	if (streq(s, "unlimited"))
X		return RLIM_INFINITY;
X	if (suf == TIMESUF && strchr(s, ':') != NULL) {
X		char *t = strchr(s, ':');
X		*t++ = '\0';
X		lim = 60 * a2u(s) + a2u(t);
X	} else {
X		for (; suf != NULL; suf = suf->next)
X			if (streq(suf->name, s + len - strlen(suf->name))) {
X				s[len - strlen(suf->name)] = '\0';
X				lim *= suf->amount;
X				break;
X			}
X		lim *= a2u(s);
X	}
X	if (lim < 0)
X		rc_error("bad limit");
X	return lim;
X}
X
Xstatic void b_limit(char **av) {
X	const Limit *lp = limits;
X	boolean hard = FALSE;
X
X	if (*++av != NULL && streq(*av, "-h")) {
X		av++;
X		hard = TRUE;
X	}
X
X	if (*av == NULL) {
X		for (; lp->name != NULL; lp++)
X			printlimit(lp, hard);
X		return;
X	}
X
X	for (;; lp++) {
X		if (lp->name == NULL)
X			rc_error("no such limit");
X		if (streq(*av, lp->name))
X			break;
X	}
X
X	if (*++av == NULL)
X		printlimit(lp, hard);
X	else {
X		struct rlimit rlim;
X		getrlimit(lp->flag, &rlim);
X		if (hard)
X			rlim.rlim_max = parselimit(lp, *av);
X		else
X			rlim.rlim_cur = parselimit(lp, *av);
X		if (setrlimit(lp->flag, &rlim) == -1)
X			uerror("setrlimit");
X	}
X}
X#endif
END_OF_FILE
  if test 10271 -ne `wc -c <'builtins.c'`; then
    echo shar: \"'builtins.c'\" unpacked with wrong size!
  fi
  # end of 'builtins.c'
fi
if test -f 'hash.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'hash.h'\"
else
  echo shar: Extracting \"'hash.h'\" \(1175 characters\)
  sed "s/^X//" >'hash.h' <<'END_OF_FILE'
Xtypedef struct Function {
X	Node *def;
X	char *extdef;
X} Function;
X
Xtypedef struct Variable {
X	List *def;
X	char *extdef;
X	struct Variable *n;
X} Variable;
X
Xtypedef struct Htab {
X	char *name;
X	void *p;
X} Htab;
X
Xextern Htab *fp, *vp;
X
X#define lookup_fn(s) ((Function *) lookup(s,fp))
X#define lookup_var(s) ((Variable *) lookup(s,vp))
X
Xextern void *lookup(char *, Htab *);
Xextern Function *get_fn_place(char *);
Xextern List *varlookup(char *);
Xextern Node *fnlookup(char *);
Xextern Variable *get_var_place(char *, boolean);
Xextern boolean varassign_string(char *);
Xextern char **makeenv(void);
Xextern char *fnlookup_string(char *);
Xextern char *varlookup_string(char *);
Xextern void alias(char *, List *, boolean);
Xextern void starassign(char *, char **, boolean);
Xextern void delete_fn(char *);
Xextern void delete_var(char *, boolean);
Xextern void fnassign(char *, Node *);
Xextern void fnassign_string(char *);
Xextern void fnrm(char *);
Xextern void initenv(char **);
Xextern void inithash(void);
Xextern void setsigdefaults(void);
Xextern void inithandler(void);
Xextern void varassign(char *, List *, boolean);
Xextern void varrm(char *, boolean);
Xextern void whatare_all_vars(void);
END_OF_FILE
  if test 1175 -ne `wc -c <'hash.h'`; then
    echo shar: \"'hash.h'\" unpacked with wrong size!
  fi
  # end of 'hash.h'
fi
if test -f 'rc.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'rc.1'\"
else
  echo shar: Extracting \"'rc.1'\" \(30362 characters\)
  sed "s/^X//" >'rc.1' <<'END_OF_FILE'
X.\" rc.1
X.\" Dd	distance to space vertically before a "display"
X.\" These are what n/troff use for interparagraph distance
X.if t .nr Dd .4v
X.if n .nr Dd 1v
X.\" Ds	begin a display
X.de Ds
X.RS \\$1
X.sp \\n(Ddu
X.nf
X..
X.\" De	end a display (no trailing vertical spacing)
X.de De
X.fi
X.RE
X..
X.ds Cf C
X.de Cw
X.lg 0
X\%\&\\$3\f\\*(Cf\\$1\f1\&\\$2
X.lg
X..
X.TH RC 1 "28 April 1991"
X.SH NAME
Xrc \- shell
X.SH SYNOPSIS
X.B rc
X[
X.B \-eixvld
X] [
X.B -c
X.I command
X] [
X.I arguments
X]
X.SH DESCRIPTION
X.I rc
Xis a command interpreter and programming language similar to
X.IR sh (1).
XIt is based on the AT&T plan 9 shell of the same name.
XThe shell offers a C-like syntax (much more so than the C shell),
Xand a powerful mechanism for manipulating variables.
XIt is reasonably small and reasonably fast,
Xespecially when compared to contemporary shells.
XIts use is intended to be interactive,
Xbut the language lends itself well to scripts.
X.SH OPTIONS
X.TP
X.B \-e
XIf the
X.Cw \-e
Xoption is present, then
X.I rc
Xwill exit if the exit status of a command is false (nonzero).
X.I rc
Xwill not exit, however, if a conditional fails, e.g., an
X.Cw if()
Xcommand.
X.TP
X.B \-i
XIf the
X.Cw \-i
Xoption is present or if the input to
X.I rc
Xis from a terminal (as determined by
X.IR isatty (3))
Xthen
X.I rc
Xwill be in
X.I interactive
Xmode.
XThat is, a prompt (from
X.Cw $prompt(1)
Xwill be printed before an
Xinput line is taken, and
X.I rc
Xwill ignore the signals
X.Cw SIGINT
Xand
X.Cw SIGQUIT .
X.TP
X.B \-x
XThis option will make
X.I rc
Xprint every command on standard error before it is executed.
XIt can be useful for debugging
X.I rc
Xscripts.
X.TP
X.B \-v
XThis option will echo input to
X.I rc
Xon standard error as it is read.
X.TP
X.B \-l
XIf the
X.Cw \-l
Xoption is present, or if
X.IR rc 's
X.Cw argv[0][0]
Xis a dash
X.Cw - ), (
Xthen
X.I rc
Xwill behave as a login shell.
XThat is, it will try to run commands present in
X.Cw $home/.rcrc ,
Xif this file exists, before reading any other input.
X.TP
X.B \-d
XThis flag causes
X.I rc
Xnot to trap
X.Cw SIGQUIT ,
Xand thus
X.I rc
Xwill dump core when it receives this signal.
XThis option is only useful for debugging
X.IR rc .
X.TP
X.B \-c
XIf
X.Cw \-c
Xis present, commands are executed from the immediately following
Xargument.
XAny further arguments to
X.I rc
Xare placed in
X.Cw $* .
X.PP
X.SH COMMANDS
XA simple command is a sequence of words, separated by white space
X(space and tab) characters that ends with a newline, semicolon
X.Cw ; ), (
Xor ampersand
X.Cw & ). (
XThe first word of a command is the name of that command.
XIf the name begins with 
X.Cw / ,
X.Cw ./ ,
Xor
X.Cw ../ ,
Xthen the name is used as an absolute path
Xname referring to an executable file.
XOtherwise, the name of the command is looked up in a table
Xof shell functions, builtin commands,
Xor as a file in the directories named by
X.Cw $path .
X.SS "Background Tasks"
XA command ending with a
X.Cw &
Xis run in the background; that is,
Xthe shell returns immediately rather than waiting for the command to
Xcomplete.
XBackground commands have
X.I /dev/null
Xconnected to their standard input unless an explicit redirection for
Xstandard input is used.
X.SS "Subshells"
XA command prefixed with an at-sign
X.Cw @ ) (
Xis executed in a subshell.
XThis insulates the parent shell from the effects
Xof a
X.B cd
Xor a variable assignment.
XFor example:
X.Ds
X.Cw "@ {cd ..; make}
X.De
X.PP
Xwill run
X.IR make (1)
Xin the parent directory
X.Cw .. ), (
Xbut leaves the shell running in the current directory.
X.SS "Line continuation"
XA long logical line may be continued over several physical lines by
Xterminating each line (except the last) with a backslash
X.Cw \e ). (
XThe backslash-newline sequence is treated as a space.
XA backslash is not otherwise special to
X.IR rc .
X.SS Quoting
X.IR rc
Xinterprets several characters specially; special characters
Xautomatically terminate words.
XThe following characters are special:
X.Ds
X.Cw "# ; & | ^ $ = ` ' { } ( ) < >\fR
X.De
X.PP
XThe single quote
X.Cw ' ) (
Xprevents special treatment of any character other than itself.
XAll characters, including control characters, newlines,
Xand backslashes between two quote characters are treated as an
Xuninterpreted string.
XA quote character itself may be quoted by placing two quotes in a row.
XThe minimal sequence needed to enter the quote character is
X.Cw '''' .
XThe empty string is represented by
X.Cw '' .
XThus:
X.Ds
X.Cw "echo 'What''s the plan, Stan?'
X.De
X.PP
Xprints out
X.Ds
X.Cw "What's the plan, Stan?
X.De
X.SS Grouping
XZero or more commands may be grouped within braces
X.Cw { ' (`
Xand
X.Cw } '), `
Xand are then treated as one command.
XBraces do not otherwise define scope;
Xthey are used only for command grouping.
XIn particular, be wary of the command:
X.Ds
X.Cw "for (i) {
X.Cw "	command
X.Cw "} | command
X.De
X.PP
XSince pipe binds tighter than
X.BR for ,
Xthis command does not perform what the user expects it to.
XInstead, enclose the whole
X.B for
Xstatement in braces:
X.Ds
X.Cw "{for (i) command} | command
X.De
X.PP
XFortunately,
X.IR rc 's
Xgrammar is simple enough that a (confident) user can
Xunderstand it by examining the skeletal yacc grammar
Xat the end of this man page (see the section entitled
XGRAMMAR).
X.SS "Input and output"
X.PP
XThe standard output may be redirected to a file with
X.Ds
X.Cw "command > file"
X.De
X.PP
Xand the standard input may be taken from a file with
X.Ds
X.Cw "command < file
X.De
X.PP
XFile descriptors other than 0 and 1 may be specified also.
XFor example, to redirect standard error to a file, use:
X.Ds
X.Cw "command >[2] file
X.De
X.PP
XIn order to duplicate a file descriptor, use
X\f\*(Cf>[\fIn\f\*(Cf=\fIm\f\*(Cf]\fR.
XThus to redirect both standard output and standard error
Xto the same file, use
X.Ds
X.Cw "command > file >[2=1]
X.De
X.PP
XTo close a file descriptor that may be open, use
X\f\*(Cf>[\fIn\f\*(Cf=]\fR.
XFor example, to
Xclose file-descriptor 7:
X.Ds
X.Cw "command >[7=]
X.De
X.PP
XIn order to place the output of a command at the end of an already
Xexisting file, use:
X.Ds
X.Cw "command >> file
X.De
X.PP
XIf the file does not exist, then it is created.
X.PP
X``Here documents'' are supported as in
X.IR sh (1)
Xwith the use of
X.Ds
X.Cw "command << 'eof-marker'
X.De
X.PP
XIf the end-of-file marker is enclosed in quotes, then no variable substitution
Xoccurs inside the here document.
XOtherwise, every variable is
Xsubstituted by its space-separated-list value (see Flat Lists, below),
Xand if a
X.Cw ^
Xcharacter follows a variable name, it is deleted.
XThis allows the unambiguous use of variables adjacent to text, as in
X.Ds
X.Cw $variable^follow
X.De
X.PP
XAdditionally,
X.I rc
Xsupports ``here strings'', which are like here documents, except that input
Xis taken directly from a string on the command line. Its use is illustrated
Xhere:
X.Ds
X.Cw "cat <<< 'this is a here string' | wc
X.De
X.PP
X(This feature enables
X.I rc
Xto export functions using here documents into the environment; the author
Xdoes not expect users to find this feature useful.)
X.SS Pipes
XTwo or more commands may be combined in a pipeline by placing the
Xvertical bar
X.Cw | ) (
Xbetween them.
XThe standard output (file descriptor 1)
Xof the command on the left is tied to the standard input (file
Xdescriptor 0) of the command on the right.
XThe notation
X\f\*(Cf|[\fIn\f\*(Cf=\fIm\f\*(Cf]\fR
Xindicates that file descriptor
X.I n
Xof the left process is connected to
Xfile descriptor
X.I m
Xof the right process.
X\f\*(Cf|[\fIn\f\*(Cf]\fR
Xis a shorthand for
X\f\*(Cf|[\fIn\f\*(Cf=0]\fR.
XAs an example, to pipe the standard error of a command to
X.IR wc (1),
Xuse:
X.Ds
X.Cw "command |[2] wc
X.De
X.SS "Commands as Arguments"
XSome commands, like
X.IR cmp (1)
Xor
X.IR diff (1),
Xtake their arguments on the command
Xline, and do not read input from standard input.
XIt is convenient
Xsometimes to build nonlinear pipelines so that a command like cmp can
Xread the output of two other commands at once.
X.I rc
Xdoes it like this:
X.Ds
X.Cw "cmp <{command} <{command}
X.De
X.PP
Xcompares the output of the two commands in braces.
XA note: since this form of
Xredirection is implemented with named pipes, and since one cannot
X.IR lseek (2)
Xon a pipe, commands that use
X.IR lseek (2)
Xwill hang.
XFor example,
Xmost versions of
X.IR diff (1)
Xuse
X.IR lseek (2)
Xon their inputs.
X.SH "CONTROL STRUCTURES"
XThe following may be used for control flow in
X.IR rc :
X.SS "If-else Statements"
X.PD 0
X.sp
X\fBif (\fItest\fB) {
X.br
X.B "    " \fIcmd\fB
X.br
X.TP
X.B } else \fIcmd\fB
XThe
X.I test
Xis executed, and if its return status is zero, the first
Xcommand is executed, otherwise the second is.
XBraces are not mandatory around the commands.
XHowever, an else statement is valid only if it
Xfollows a close-brace on the same line.
XOtherwise, the if is taken to be a simple-if:
X.Ds
X.Cw "if (test)
X.Cw "	command
X.De
X.PD
X.SS "While and For Loops"
X.TP
X.B while (\fItest\fB) \fIcmd\fB
X.I rc
Xexecutes the
X.I test
Xand performs the command as long as the
X.I test
Xis true.
X.TP
X.B for (\fIvar\fB in \fIlist\fB) \fIcmd\fB
X.I rc
Xsets
X.I var
Xto each element of
X.I list
X(which may contain variables and backquote substitutions) and runs
X.IR cmd .
XIf ``\fBin\fR \fIlist\fR'' is omitted, then
X.I rc
Xwill set
X.I var
Xto each element of
X.Cw $*
X(excluding
X.Cw $0 .
XFor example:
X.Ds
X.Cw "for (i in `{ls -F | grep '\e*$' | sed 's/\e*$//'}) { commands }
X.De
X.PP
Xwill set
X.Cw $i
Xto the name of each file in the current directory which is
Xexecutable.
X.SS "Switch"
X.TP
X.B switch (\fIlist\fB) { case \fR...\fB }
X.I rc
Xlooks inside the braces after a
X.B switch
Xstatement for single lines beginning with the word
X.BR case .
XIf any of the patterns following
X.B case
Xmatch the list supplied to
X.BR switch ,
Xthen the commands up until the next
X.B case
Xstatement are executed.
XMetacharacters should not be quoted;
Xmatching is performed only against the strings in
X.IR list ,
Xnot against file names.
X(Matching for case statements is the same as for the
X.Cw ~
Xcommand.)
X.SS "Logical Operators"
XThere are a number of operators in
X.I rc
Xwhich depend on the exit status of a command.
X.Ds
X.Cw "command && command
X.De
X.PP
Xexecutes the first command and then executes the second command if and only if
Xthe first command exits with a zero exit status (``true'' in UNIX).
X.Ds
X.Cw "command || command
X.De
X.PP
Xexecutes the first command executing the second command if and only if
Xthe second command exits with a nonzero exit status (``false'' in UNIX).
X.Ds
X.Cw "! command
X.De
X.PP
Xnegates the exit status of a command.
XThus:
X.Ds
X.Cw "! command || command
X.De
X.PP
Xis equivalent to
X.Ds
X.Cw "command && command
X.De
X.SH "PATTERN MATCHING"
XThere are two forms of pattern matching in
X.IR rc .
XOne is traditional shell globbing.
XThis occurs in matching for file names in argument lists:
X.Ds
X.Cw "command argument argument ...
X.De
X.PP
XWhen the characters
X.Cw "*" ,
X.Cw [
Xor
X.Cw ?
Xoccur in an argument,
X.I rc
Xlooks at the
Xargument as a pattern for matching against files according to the
Xfollowing rules: a
X.Cw *
Xmatches any number (including zero) of
Xcharacters.
XA
X.Cw ?
Xmatches any single character, and a
X.Cw [
Xfollowed by a
Xnumber of characters followed by a
X.Cw ]
Xmatches a single character in that
Xclass.
XThe rules for character class matching are the same as those for
X.IR ed (1),
Xwith the exception that character class negation is achieved
Xwith the tilde
X.Cw ~ ), (
Xnot the caret
X.Cw ^ ), (
Xsince the caret already means
Xsomething else in
X.IR rc .
X.PP
X.I rc
Xalso matches patterns against strings with the
X.Cw ~
Xcommand:
X.Ds
X.Cw "~ subject pattern pattern ...
X.De
X.PP
X.Cw ~
Xsets
X.Cw $status
Xto zero if and only if a supplied pattern matches any
Xsingle element of the subject list.
XThus
X.Ds
X.Cw "~ foo f*
X.De
X.PP
Xsets status to zero, while
X.Ds
X.Cw "~ (bar baz) f*
X.De
X.PP
Xsets status to one.
XThe null list is matched by the null list, so
X.Ds
X.Cw "~ $foo ()
X.De
X.PP
Xchecks to see whether
X.Cw $foo
Xis empty or not.
XThis may also be achieved
Xby the test
X.Ds
X.Cw "~ $#foo 0
X.De
X.PP
XNote that inside a
X.Cw ~
Xcommand
X.I rc
Xdoes not match patterns against file
Xnames, so it is not necessary to quote the characters
X.Cw "*" ,
X.Cw [
Xand
X.Cw "?" .
XFinally, note that if the
X.Cw ~
Xcommand is given a list as its first
Xargument, then a successful match against any of the elements of that
Xlist will cause
X.Cw ~
Xto return true.
XFor example:
X.Ds
X.Cw "~ (foo goo zoo) z*
X.De
X.PP
Xis true.
X.SH "LISTS AND VARIABLES"
XThe primary data structure in
X.IR rc
Xis the list, which is a sequence of words.
XParentheses are used to group lists.
XThe empty list is represented by
X.Cw "()" .
XLists have no hierarchical structure;
Xa list inside another list is expanded so the
Xouter list contains all the elements of the inner list.
XThus, the following are all equivalent
X.Ds
X.Cw "one two three
X
X.Cw "(one two three)
X
X.Cw "((one) () ((two three)))
X.De
X.PP
XNote that the null string,
X.Cw "''" ,
Xand the null list,
X.Cw "()" ,
Xare two very
Xdifferent things.
XAssigning the null string to variable is a valid
Xoperation, but it does not remove its definition.
XFor example,
Xif
X.Cw $a
Xis set to
X.Cw "''" ,
Xthen
X.Cw "$#a" ,
Xreturns a 1.
X.SS "List Concatenation"
XTwo lists may be joined by the concatenation operator
X.Cw ^ ). (
XA single word is treated as a list of length one, so
X.Ds
X.Cw "echo foo^bar
X.De
X.PP
Xproduces the output
X.Ds
X.Cw foobar
X.De
X.PP
XFor lists of more than one element,
Xconcatenation works according to the following rules: if the two lists
Xhave the same number of elements, then concatenation is pairwise:
X.Ds
X.Cw "echo (a\- b\- c\-)^(1 2 3)
X.De
X.PP
Xproduces the output 
X.Ds
X.Cw "a\-1 b\-2 c\-3
X.De
X.PP
XOtherwise, one of the lists must have a single element, and then the
Xconcatenation is distributive:
X.Ds
X.Cw "cc \-^(O g c) (malloc alloca)^.c
X.De
X.PP
Xhas the effect of performing the command
X.Ds
X.Cw "cc \-O \-g \-c malloc.c alloca.c
X.De
X.SS "Free Carets"
X.I rc
Xinserts carets (concatenation operators) for free in certain
Xsituations, in order to save some typing on the user's behalf.
XFor
Xexample, the above example could also be typed in as:
X.Ds
X.Cw "opts=(O g c) files=(malloc alloca) cc \-$opts $files.c
X.De
X.PP
X.I rc
Xtakes care to insert a free-caret between the
X.Cw \- ' `
Xand
X.Cw "$opts" ,
Xas well
Xas between
X.Cw $files
Xand
X.Cw ".c" .
XThe rule for free carets is as follows:  if
Xa word or keyword is immediately
Xfollowed by another word, keyword, dollar-sign or
Xbackquote, then
X.I rc
Xinserts a caret between them.
X.SS "Variables"
XA list may be assigned to a variable, using the notation:
X.Ds
X.Cw "var = list
X.De
X.PP
XAny sequence of non-special characters, except a sequence including
Xonly digits, may be used as a variable name.
XAll user-defined variables are exported into the environment.
X.PP
XThe value of a variable is referenced with the notation:
X.Ds
X.Cw $var
X.De
X.PP
XAny variable which has not been assigned a value returns the null list,
X.Cw "()" ,
Xwhen referenced. In addition, multiple references are allowed:
X.Ds
X.Cw a=foo
X.Cw b=a
X.Cw echo $$b
X.De
X.PP
Xprints
X.Ds
X.Cw foo
X.De
X.PP
XA variable's definition may also be removed by
Xassigning the null list to a variable:
X.Ds
X.Cw var=()
X.De
X.PP
XFor ``free careting'' to work correctly,
X.I rc
Xmust make certain assumptions
Xabout what characters may appear in a variable name.
X.I rc
Xcurrently
Xassumes that a variable name consists only of alphanumeric characters,
Xunderscore
X.Cw _ ) (
Xand star
X.Cw * ). (
XTo reference a variable with other
Xcharacters in its name, quote the variable name.
XThus:
X.Ds
X.Cw "echo $'we$Ird\Variab!le'
X.De
X.SS "Local Variables"
XAny number of variable assignments may be made local to a single
Xcommand by typing:
X.Ds
X.Cw "a=foo b=bar ... command
X.De
X.PP
XThe command may be a compound command, so for example:
X.Ds
X.Cw "path=. ifs=() {
X.Cw 	...
X.De
X.PP
Xsets path to 
X.Cw .
Xand ifs to
X.Cw ()
Xfor the duration of one long compound
Xcommand.
X.SS "Variable Subscripts"
XVariables may be subscripted with the notation
X.Ds
X\f\*(Cf$var(\fIn\f\*(Cf\fR)
X.De
X.PP
Xwhere
X.I n
Xis a list of integers (origin 1).
XThe list of subscripts need
Xnot be in order or even unique.
XThus, if
X.Ds
X.Cw "a=(one two three)
X.De
X.PP
Xthen
X.Ds
X.Cw "echo $a(3 3 3)
X.De
X.PP
Xprints
X.Ds
X.Cw "three three three
X.De
X.PP
XIf
X.I n
Xreferences a nonexistent element, then
X\f\*(Cf$var(\fIn\f\*(Cf\fR)
Xreturns the null
Xlist.
XThe notation
X.Cw "$\fIn" ,
Xwhere
X.I n
Xis an integer, is a shorthand for
X\f\*(Cf$*(\fIn\f\*(Cf)\fR.
XThus,
X.IR rc 's
Xarguments may be referred to as
X.Cw "$1" ,
X.Cw "$2" ,
Xand so on.
X.PP
XNote also that the list of subscripts may be given by any of
X.IR rc 's
Xlist operations:
X.Ds
X.Cw "$var(`{awk 'BEGIN{for(i=1;i<=10;i++)print i;exit; }'})
X.De
X.PP
Xreturns the first 10 elements of
X.Cw $var .
X.PP
XTo count the number of elements in a variable, use
X.Ds
X.Cw $#var
X.De
X.PP
XThis returns a single-element list, with the number of elements in
X.Cw $var .
X.SS "Flat Lists"
XIn order to create a single-element list from a multi-element list,
Xwith the components space-separated, use
X.Ds
X.Cw $^var
X.De
X.PP
XThis is useful when the normal list concatenation rules need to be
Xbypassed.
XFor example, to append a single period at the end of
X.Cw $path ,
Xuse:
X.Ds
X.Cw "echo $^path.
X.De
X.SS "Backquote Substitution"
XA list may be formed from the output of a command by using backquote
Xsubstitution:
X.Ds
X.Cw "`{ command }
X.De
X.PP
Xreturns a list formed from the standard output of the command in braces.
X.Cw $ifs
Xis used to split the output into list elements.
XBy default,
X.Cw $ifs
Xhas the value space-tab-newline.
XThe braces may be omitted if the command is a single word.
XThus
X.Cw `ls
Xmay be used instead of
X.Cw "`{ls}" .
XThis last feature is useful when defining functions that expand
Xto useful argument lists. A frequent use is:
X.Ds
X.Cw "fn src { echo *.[chy] }
X.De
X.PP
Xfollowed by
X.Ds
X.Cw "wc `src
X.De
X.PP
X(This will print out a word-count of all C source files in the current
Xdirectory.)
X.PP
XIn order to override the value of
X.Cw $ifs
Xfor a single backquote
Xsubstitution, use:
X.Ds
X.Cw "`` (ifs-list) { command }
X.De
X.PP
X.Cw $ifs
Xwill be temporarily ignored and the command's output will be split as specified by
Xthe list following the double backquote.
XFor example:
X.Ds
X.Cw "`` ($nl :) {cat /etc/passwd}
X.De
X.PP
Xsplits up
X.I /etc/passwd
Xinto fields, assuming that
X.Cw $nl
Xcontains a newline
Xas its value.
X.SH "SPECIAL VARIABLES"
XSeveral variables are known to
X.I rc
Xand are treated specially.
X.TP
X.B *
XThe argument list of
X.IR rc .
X.Cw "$1, $2,
Xetc. are the same as
X.Cw $*(1) ,
X.Cw $*(2) ,
Xetc.
XThe variable
X.Cw $0
Xholds the value of
X.Cw argv[0]
Xwith which
X.I rc
Xwas invoked.
XAdditionally,
X.Cw $0
Xis set to the name of a function for the duration of
Xthe execution of that function, and
X.Cw $0
Xis also set to the name of the
Xfile being interpreted for the duration of a 
X.Cw .
Xcommand.
X.TP
X.B apid
XThe process ID of the last process started in the background.
X.TP
X.B cdpath
XA list of directories to search for the target of a
X.B cd
Xcommand.
XThe empty string stands for the current directory. Note that
Xan assignment to
X.Cw $cdpath
Xcauses an automatic assignment to
X.Cw $CDPATH ,
Xand vice-versa
X.TP
X.B history
X.Cw $history
Xcontains the name of a file to which commands are appended as
X.I rc
Xreads them.
XThis facilitates the use of a stand-alone history program
Xwhich parses the contents of the history file and presents them to
X.I rc
Xfor reinterpretation.
XIf
X.Cw $history
Xis not set, then
X.I rc
Xdoes not append commands to any file.
X.TP
X.B home
XThe default directory for the builtin cd command and is the directory
Xin which
X.I rc
Xlooks to find its initialization file,
X.IR .rcrc ,
Xif
X.I rc
Xhas been started up as a login shell. Like
X.Cw $cdpath
Xand
X.Cw $CDPATH ,
X.Cw $home
Xand
X.Cw $HOME
Xare aliased to each other.
X.TP
X.B ifs
XThe internal field separator, used for splitting up the output of
Xbackquote commands for digestion as a list.
X.TP
X.B path
XThis is a list of directories to search in for commands.
XThe empty string stands for the current directory. Note that
Xlike
X.Cw $cdpath
Xand
X.Cw $CDPATH ,
X.Cw $path
Xand
X.Cw $PATH
Xare aliased to each other.
X.TP
X.B pid
XThe process ID of the currently running
X.IR rc .
X.TP
X.B prompt
XThis variable holds the two prompts (in list form, of course) that
X.I rc
Xprints.
X.Cw $prompt(1)
Xis printed before each command is read, and
X.Cw $prompt(2)
Xis printed when input is expected to continue on the next
Xline.
X.I rc
Xsets
X.Cw $prompt
Xto
X.Cw "('; ' '')
Xby default.
XThe reason for this is that it enables an
X.I rc
Xuser to grab commands from previous lines using a
Xmouse, and to present them to
X.I rc
Xfor re-interpretation; the semicolon
Xprompt is simply ignored by
X.IR rc .
XThe null
X.Cw $prompt(2)
Xalso has its
Xjustification:  an
X.I rc
Xscript, when typed interactively, will not leave 
X.Cw $prompt(2) 's
Xon the screen,
Xand can therefore be grabbed by a mouse and placed
Xdirectly into a file for use as a shell script, without further editing
Xbeing necessary.
X.TP
X.B prompt (function)
XIf this function is set, then it gets executed every time
X.I rc
Xis about to print
X.Cw "$prompt(1)" .
X.TP
X.B status
XThe exit status of the last command.
XIf the command exited with a numeric value,
Xthat number is the status.
XIf the died with a signal,
Xthe status is the name of that signal; if a core file
Xwas created, the string
X.Cw +core '' ``
Xis appended.
XThe value of
X.Cw $status
Xfor a pipeline is a list, with one entry,
Xas above, for each process in the pipeline.
XFor example, the command 
X.Ds 1i
X.Cw "ls | wc
X.De
X.TP
X\&
Xusually sets
X.Cw $status
Xto
X.Cw "(0 0)" .
X.PP
XThe values of
X.Cw "$path" ,
X.Cw "$cdpath" ,
Xand
X.Cw $home
Xare derived from the environment
Xvalues of
X.Cw "$PATH" ,
X.Cw "$CDPATH" ,
Xand
X.Cw "$HOME" .
XOtherwise, they are derived from
Xthe environment values of
X.Cw $path ,
X.Cw $cdpath
Xand
X.Cw $home .
XThis is for compatibility with other UNIX programs, like
X.IR sh (1).
X.Cw $PATH
Xand
X.Cw $CDPATH
Xare assumed to be colon-separated lists.
X.SH FUNCTIONS
X.I rc
Xfunctions are identical to
X.I rc
Xscripts, except that they are stored
Xin memory and are automatically exported into the environment.
XA shell function is declared as:
X.Ds
X.Cw "fn name { commands }
X.De
X.PP
X.I rc
Xscans the definition in until the close-brace, so the function can
Xspan more than one line.
XThe function definition may be removed by typing
X.Ds
X.Cw "fn name
X.De
X.PP
XWhen a function is executed,
X.Cw $*
Xis set to the arguments to that
Xfunction for the duration of the command.
XThus a reasonable definition for
X.Cw "l" ,
Xa shorthand for
X.IR ls (1),
Xcould be:
X.Ds
X.Cw "fn l { ls -FC $* }
X.De
X.PP
Xbut not
X.Ds
X.Cw "fn l { ls -FC }
X.De
X.SH "INTERRUPTS AND SIGNALS"
X.I rc
Xrecognizes a number of signals, and allows the user to define shell
Xfunctions which act as signal handlers.
X.I rc
Xby default traps
X.Cw SIGINT
Xand
X.Cw SIGQUIT
Xwhen it is interactive mode.
X.Cw SIGQUIT
Xis ignored, unless
X.I rc
Xhas been invoked with the 
X.Cw \-d
Xflag.
XHowever, user-defined signal handlers may be written for these and
Xall other signals.
XThe way to define a signal handler is to
Xwrite a function by the name of the signal in lower case.
XThus:
X.Ds
X.Cw "fn sighup { echo hangup; rm /tmp/rc$pid.*; exit }
X.De
X.PP
XIn addition to Unix signals, rc recognizes the artificial signal
X.Cw SIGEXIT
Xwhich occurs as rc is about to exit.
X.PP
XIn order to remove a signal handler's definition, remove it as though
Xit were a regular function.
XFor example:
X.Ds
X.Cw "fn sigint
X.De
X.PP
Xreturns the handler of
X.Cw SIGINT
Xto the default value.
XIn order to ignore a signal, set the signal handler's value to
X.Cw "{}" .
XThus:
X.Ds
X.Cw "fn sigint {}
X.De
X.PP
Xcauses SIGINT to be ignored by the shell.
X.SH "BUILTIN COMMANDS"
XBuiltin commands execute in the context of the shell, but otherwise
Xbehave exactly like other commands.
X.TP
X.B .  \fR[\fB\-i\fR]\fB \fIfile args\fB
XReads \fIfile\fP as input to
X.IR rc
Xand executes its contents.
XWith a
X.Cw \-i
Xflag, input is interactive.
XThus
Xfrom within a shell script, 
X.Ds 1i
X.Cw ". \-i /dev/tty
X.De
X.TP
X\&
Xdoes the ``right'' thing.
X.TP
X.B break
XBreaks from the innermost
X.B for
Xor
X.BR while ,
Xas in C.
XIt is an error to
Xinvoke
X.B break
Xoutside of a loop.
X(Note that
Xthere is no
X.B break
Xkeyword between
Xcommands in
X.B switch
Xstatements, unlike C.)
X.TP
X.B builtin \fIcommand ...\fB
XExecutes the command as a builtin; no function lookup or directory
Xsearching is done.
XThis command is present to allow functions with the
Xsame names as builtins to use the underlying builtin.
X.TP
X.B cd \fIdirectory\fB
XChange the current directory.
XThe variable
X.Cw $cdpath
Xis searched for
Xpossible locations of \fIdirectory\fP, analogous to the searching of
X.Cw $path
Xfor executable files.
XWith no argument,
X.B cd
Xchanges directory to
X.Cw "$home" .
X.TP
X.B echo \fIargs ...\fB
XPrints its arguments to standard output.
XArguments are separated by spaces.
XIf the first argument is
X.Cw "\-n" ,
Xno final newline is printed.
XIf the first argument is
X.Cw "\-\-" ,
Xthen any subsequent arguments are ignored.
X.TP
X.B eval \fIlist\fB
X.B eval
Xconcatenates the elements of
X.I list
Xwith spaces and feeds the resulting string to
X.I rc
Xfor re-scanning.
XThis is the only time input is rescanned in
X.IR rc .
X.TP
X.B exec \fIcommand\fB
Xreplaces
X.I rc
Xwith the given command.
XIf the exec contains only redirections,
Xthen these redirections apply to the current shell
Xand the shell does not exit.
XFor example,
X.Ds 1i
X.Cw "exec >[2] err.out
X.De
X.TP
X\&
Xplaces further output to standard error in the file
X.IR err.out .
X.TP
X.B exit \fIstatus\fB
XCause the current shell to exit with the given exit
X.IR status .
XIf no argument is given, the current value of
X.Cw $status
Xis used.
X.TP
X.B limit \fR[\fB\-h\fR]\fB \fR[\fIresource\fR]\fB \fR[\fIvalue\fR]\fB
XSimilar to the
X.IR csh (1)
Xlimit builtin, this command operates upon the
XBSD-style limits of a process.
XThe
X.Cw \-h
Xflag displays/alters the hard
Xlimits.
XThe resources which can be shown or altered are
X.BR cputime ,
X.BR filesize ,
X.BR datasize ,
X.BR stacksize ,
X.B coredumpsize
Xand
X.BR memoryuse .
XFor
Xexample:
X.Ds 1i
X.Cw "limit coredumpsize 0
X.De
X.TP
X\&
Xdisables core dumps.
X.TP
X.B return \fIn\fB
XReturns from the current function, with status
X.IR n .
XIf
X.IR n
Xis omitted, then
X.Cw $status
Xis left unchanged.
XIt is an error to invoke
X.B return
Xwhen not inside a function.
X.TP
X.B shift \fIn\fB
XDeletes
X.I n
Xelements from the beginning of
X.Cw $*
Xand shifts the other
Xelements down by
X.IR n .
X.I n
Xdefaults to 1.
X(Note that
X.Cw $0
Xis not affected by
X.BR shift .)
X.TP
X.B umask \fImask\fB
XSets the current umask (see
X.IR umask (2))
Xto the octal
X.IR mask .
XIf no argument is present, the current mask value is printed.
X.TP
X.B wait \fIpid\fB
XWaits for the specified
X.IR pid ,
Xwhich must have been started by
X.IR rc .
XIf no
X.I pid
Xis specified,
X.I rc
Xwaits for any child process to exit.
X.TP
X.B whatis \fIname ...\fB
XPrints a definition of the named objects.
XFor variables, their values
Xare printed; for functions, their definitions are; and for executable
Xfiles, path names are printed.
XWithout arguments,
X.B whatis
Xprints the values of all shell variables and functions. Note that
X.B whatis
Xoutput is suitable for input to
X.IR rc ;
Xby saving the output of
X.B whatis
Xin a file, it should be possible to recreate the state of
X.I rc
Xby sourcing this file with a
X.Cw .
Xcommand.
X.SH GRAMMAR
XHere is
X.IR rc 's
Xgrammar, edited to remove semantic actions.
X.Ds
X\f\*(Cf
X%term BANG DUP ELSE END FN FOR HUH IF IN LBRACK PIPE RBRACK
X%term REDIR STAR SUB SUBSHELL SWITCH TWIDDLE WHILE WORD
X
X%left IF WHILE FOR SWITCH ')' ELSE
X%left ANDAND OROR
X%left BANG SUBSHELL
X%left PIPE
X%left '^'
X%right '$' COUNT FLAT
X%left SUB
X%left '`' BACKBACK
X
X%%
X
Xrc: line end | error end
X
Xend: '\en' | END
X
Xcmdsa: cmd ';' | cmd '&'
X
Xline: cmd | cmdsa line
X
Xbody: cmd | cmdsan body
X
Xcmdsan: cmdsa | cmd '\en'
X
Xbrace: '{' body '}'
X
Xparen: '(' body ')'
X
Xassign: first '=' word
X
Xepilog: /* empty */ | redir epilog
X
Xredir: DUP | REDIR word
X
Xiftail: cmd %prec IF | brace ELSE cmd
X
Xcmd	: /* empty */
X	| simple
X	| brace epilog
X	| IF paren { skipnl(); } iftail
X	| FOR '(' word IN words ')' { skipnl(); } cmd
X	| FOR '(' word ')' { skipnl(); } cmd
X	| WHILE paren { skipnl(); } cmd
X	| SWITCH '(' word ')' { skipnl(); } brace
X	| TWIDDLE word words
X	| cmd ANDAND { skipnl(); } cmd
X	| cmd OROR { skipnl(); } cmd
X 	| cmd PIPE { skipnl(); } cmd
X	| redir cmd %prec BANG
X	| assign cmd %prec BANG
X	| BANG cmd
X	| SUBSHELL cmd
X	| FN words brace
X	| FN words
X
Xsimple: first | simple word | simple redir
X
Xfirst: comword | first '^' word
X
Xword: comword | keyword | word '^' word
X
Xcomword: WORD
X	| COUNT word | FLAT word
X	| '`' word | '`' brace
X	| BACKBACK word brace | BACKBACK word word
X	| '(' words ')'
X	| REDIR brace
X	|  '$' word | '$' word SUB words ')'
X
Xkeyword: FOR | IN | WHILE | IF | SWITCH
X	| FN | ELSE | TWIDDLE | BANG | SUBSHELL
X
Xwords: /* empty */ | words word
X\fR
X.De
X.SH FILES
X.I "$HOME/.rcrc, /tmp/rc*, /dev/null
X.SH CREDITS
X.I rc
Xwas written by Byron Rakitzis, with valuable help
Xfrom Paul Haahr, Hugh Redelmeier and David Sanderson.
XThe design of this shell has been copied from the rc
Xthat Tom Duff wrote at Bell Labs.
X.SH BUGS
X.Cw <{foo}
Xstyle redirection is implemented with named pipes, and it is sometimes
Xpossible to foil rc into removing the FIFO it places in
X.I /tmp 
Xprematurely, or it is even possible to cause rc to hang. This redirection
Xshould be implemented via
X.I /dev/fd
Xon systems which have it.
X.B
XThe functionality of
X.B shift
Xshould be available for variables other than 
X.Cw "$*" .
X.PP
X.B echo
Xis built in only for performance reasons, which is a bad idea.
X.PP
XThere should be a way to avoid exporting a variable.
X.PP
XThe
X.Cw $^var
Xnotation for flattening should allow for using an arbitrary
Xseparating character, not just space.
X.PP
XBug reports should be mailed to
X.Cw "byron@archone.tamu.edu" .
X.SH INCOMPATIBILITIES
XHere is a list of features which distinguish this incarnation of
X.I rc
Xfrom the one described in the Bell Labs manual pages:
X.PP
XThe treatment of if-else is different in the v10
X.IR rc :
Xthat version uses
Xan ``if not'' clause which gets executed if the preceding ``if'' test
Xdoes not succeed.
X.PP
XBackquotes are slightly different in v10
X.IR rc :
Xa backquote must always be followed by
Xa left-brace.
XThis restriction is not present for single-word
Xcommands in this
X.IR rc .
X.PP
XThe following are all new with this version of
X.IR rc :
XThe list flattening operator,
Xhere strings (they facilitate exporting of functions
Xwith here documents into the environment),
Xthe
X.B return
Xand
X.B break
Xkeywords,
Xthe
X.B echo
Xbuiltin,
Xthe support for the GNU
X.IR readline (3)
Xlibrary and
Xthe support for the
X.B prompt
Xfunction.
XThis
X.I rc
Xalso sets
X.Cw $0
Xto the name of a function being executed/file
Xbeing sourced.
X.SH "SEE ALSO"
X``rc \(em A shell for Plan 9 and UNIX'',
XUnix Research System,
X10th Edition,
Xvol. 2. (Saunders College Publishing)
END_OF_FILE
  if test 30362 -ne `wc -c <'rc.1'`; then
    echo shar: \"'rc.1'\" unpacked with wrong size!
  fi
  # end of 'rc.1'
fi
echo shar: End of archive 1 \(of 4\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 2 3 4 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 4 archives.
    rm -f ark[1-9]isdone
else
    echo You still must unpack the following archives:
    echo "        " ${MISSING}
fi
exit 0
exit 0 # Just in case...
-- 
Kent Landfield                   INTERNET: kent@sparky.IMD.Sterling.COM
Sterling Software, IMD           UUCP:     uunet!sparky!kent
Phone:    (402) 291-8300         FAX:      (402) 291-4362
Please send comp.sources.misc-related mail to kent@uunet.uu.net.