rc

[fork] interactive rc shell
Log | Files | Refs | README | LICENSE

commit 609a278bf6e301ea9a021793de314ddcadb806ba
parent 14106ccc3b2cb03c8b9a1df1a1b3e3953aaee2f8
Author: Toby Goodwin <tjg@star.le.ac.uk>
Date:   Fri, 12 Oct 2001 10:25:58 +0100

beta: rc-1.6b3

Diffstat:
MAUTHORS | 10+++++-----
MCOPYING | 2+-
MChangeLog | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AFutures | 12++++++++++++
MINSTALL | 7+++++--
MMakefile.am | 2++
MNEWS | 41++++++++++++++++++-----------------------
MREADME | 6+++---
Macconfig.h | 3+++
Macinclude.m4 | 13+++++++++++++
Mbuiltins.c | 6+++---
Mconfigure.ac | 6++++--
Mfootobar.c | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mglob.c | 18++++--------------
Mglom.c | 3+--
Mhash.c | 3++-
Mheredoc.c | 10+++++-----
Minput.c | 53++++++++++++++++++++++++++++++++++++-----------------
Mlex.c | 27+++++++++++++++++++++------
Mmain.c | 12++++++++----
Mmksignal.c | 17+++++++++--------
Mnalloc.c | 3++-
Mopen.c | 18++++++++++++++++++
Mparse.y | 8++++----
Mprint.c | 4----
Mproto.h | 5++++-
Mrc.1 | 231+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mrc.h | 17++++++++++++-----
Mtree.c | 9+++++----
Mtrip.rc | 41+++++++++++++++++++++++++++++------------
Mtripping.c | 11+++++++++--
Mutils.c | 2+-
Mvar.c | 2+-
Mwalk.c | 6+++++-
34 files changed, 536 insertions(+), 271 deletions(-)

diff --git a/AUTHORS b/AUTHORS @@ -29,8 +29,8 @@ version of rc presented here differs in some respects. Tim would like to thank these people for their contributions since he took over maintenance of rc. Aharon Robbins, Arvid Requate, Bengt Kleberg, Brynjulv Hauksson, Byron Rakitzis, Chris Siebenmann, Dale -Scheetz, David Luyer, David Swasey, Donn Cave, Gerry Tomlinson, Gert-Jan -Vons, Ian Lance Taylor, Jeremy Fitzhardinge, Marc Moorcroft, Mark H -Wilkinson, Mark K Gardner, Raymond Venneker, Rich $alz, Rob Savoye, -Scott Schwartz, Stefan Dalibor, Steve Simon, Tom Culliton, Tom Tromey, -Vincent Broman, Wolfgang Zekoll. +Scheetz, David Luyer, David Swasey, Decklin Foster, Donn Cave, Gerry +Tomlinson, Gert-Jan Vons, Ian Lance Taylor, Jeremy Fitzhardinge, Marc +Moorcroft, Mark H Wilkinson, Mark K Gardner, Raymond Venneker, Rich +$alz, Rob Savoye, Scott Schwartz, Stefan Dalibor, Steve Simon, Thomas +Nordin, Tom Culliton, Tom Tromey, Vincent Broman, Wolfgang Zekoll. diff --git a/COPYING b/COPYING @@ -1,5 +1,5 @@ /* - * Copyright 1991, 1999 Byron Rakitzis. All rights reserved. + * Copyright 1991, 2001 Byron Rakitzis. All rights reserved. * * This software is not subject to any license of the American Telephone * and Telegraph Company or of the Regents of the University of California. diff --git a/ChangeLog b/ChangeLog @@ -665,3 +665,61 @@ Changes since rc-1.5b2 available on the web. Release: rc-1.6b2. + +2000-04-19 + + Bug: isatty() tests in input.c are relevant to any fd, not just 0. + Now `. -i /dev/tty' works right. + + Bug: fn sigexit wasn't always cleared in child shells. + + Bug: `~ () '*'' dumped core. + +2000-05-25 + + Portability: need special runes for read() returning EIO under job + control systems. + +2000-06-20 + + Feature: add `-I' flag (definitively not interactive) for + compatibility with Plan 9 rc. + +2000-11-27 + + Portability: configure fails to detect strerror() on NetBSD; we need + to include <string.h>. + +2001-06-18 + + Bug: you can't pass a short to a stdargs function! + +2001-09-27 + + Documentation: we don't consider that `.' failing to search path is + a bug. + + Feature: ^A in lists is now handled transparently. + +2001-10-01 + + Bug: it's no longer possible to use parentheses to sneak a word that + needs quoting past the "quoting detector", so `fn x { echo $(x.y) }' + now works both in the current rc and its descendants. + + Documentation: mention the catastrophic effects of a semantic error + in fn prompt. + +2001-10-03 + + Feature: exported lists no longer use redundant parentheses. + +2001-10-04 + + Bug: semantic errors in `fn prompt' no longer throw rc into a tailspin. + + Feature: don't export $cdpath or $home (the lower-case versions). + +2001-10-12 + + Release: rc-1.6b3. diff --git a/Futures b/Futures @@ -0,0 +1,12 @@ +I18N. + +Offer more control over what rc prints about commands (e.g. a way to +allow EPIPE to generate an error). + +Faster; smaller; cheaper. + +Dynamically load readline and friends. + +Make --disable-def-interp the default. + +Allow builtin wait more than one argument. diff --git a/INSTALL b/INSTALL @@ -150,8 +150,11 @@ produce a few warnings. Warnings about "implicit declaration" of system functions may occur if your system's include files are deficient---they are usually harmless. -There is a warning about "variable `n' might be clobbered" in walk.c. -I'm not sure how serious this warning is, nor how to eliminate it. +Some older versions of gcc warn about "variable `n' might be +clobbered" in walk.c. As far as I can tell, this is gcc telling you +that it might not be compiling the code correctly! I don't know how +to eliminate the warning, but it doesn't appear to cause any problems +in practice. On some systems there are warnings about "passing arg 2 of `getgroups' from incompatible pointer type" in which.c. I believe this is due to an diff --git a/Makefile.am b/Makefile.am @@ -61,6 +61,8 @@ DISTCLEANFILES = sigmsgs.c sigmsgs.h statval.h # mv y.tab.c parse.c # mv y.tab.h parse.h +check: trip + trip: rc tripping ./rc -p < $(srcdir)/trip.rc diff --git a/NEWS b/NEWS @@ -1,29 +1,24 @@ -Highlights of changes since rc-1.4. See ChangeLog for further details. +Highlights of changes since rc-1.6. See ChangeLog for further details. -Portability. The major change since the last full release is that rc -now uses GNU autoconf and automake. Other parts of the build process -have been tidied up too, so that building rc should now be painless. It -has been tested on a wide variety of Unix-like systems. +Portability. Many minor tweaks, including fixes for BeOS, CygWin, and +gcc-3. -Bug fixes. The following bugs have been fixed: the interaction with -readline was not always correct; SIGCLD set to SIG_IGN could cause a -hang; some obscure pipeline incantations could cause a hang; the `wait' -builtin could become uninterruptible; an application could crash rc -by making stdin nonblocking; the `-i' flag didn't work with readline; -$history didn't work properly over NFS; a few memory leaks; and an off -by one bug in reporting line numbers of errors. +Bug fixes. A number of bugs have been fixed. The serious ones were: +a core dump, triggered by `~ () '*''; premature exit, triggered by +sourcing a file which could be open()ed but not read() (such as a +directory on many systems); uninterruptible looping, triggered by +semantic errors in `fn prompt'. -New features. The following features are new: the `-s' flag, which -causes rc to read from stdin even if arguments are present; the `-V' -flag, which prints a version string; $bqstatus which holds the exit -status of the last backquote command; additional options to the `whatis' -builtin; and support for vrl, another lightweight readline-style -library. +New features. The following features are new: the `$version' variable +replaces the `-V' flag; the `-I' flag (definitively not interactive) +was added for compatibility with the Plan 9 rc; ASCII SOH (^A) is now +handled transparently. -What happened to rc-1.5? A number of beta releases were made with names -like rc-1.5b2, which might suggest improvements over rc-1.5. To avoid -any possibility of confusion, I decided to name this release rc-1.6 -instead. +Documentation. Distributions of this rc used to include a PostScript +paper given by Tom Duff to the UKUUG, describing the Plan 9 rc. This +paper is no longer distributed with rc, but instead is available on +the web, both in its original PostScript version, and an updated HTML +version. Tim Goodwin -1999-05-26 +2001-10-05 diff --git a/README b/README @@ -1,8 +1,8 @@ -This is beta release rc-1.6b2. +This is beta release rc-1.6b3. See COPYING for copying information. All files are - Copyright 1991, 1999 Byron Rakitzis. + Copyright 1991, 2001 Byron Rakitzis. See INSTALL for build and installation information. @@ -24,7 +24,7 @@ make rc; this information is extremely valuable. FEEPING CREATURISM -See the end of the man page, under "INCOMPATABILITIES" for (known?) +See the end of the man page, under "INCOMPATIBILITIES" for (known?) differences from the "real" rc. Most of these changes were necessary to get rc to work in a reasonable fashion on a real (i.e. commercial, non-Labs) Unix system; a few were changes motivated by concern about diff --git a/acconfig.h b/acconfig.h @@ -34,6 +34,9 @@ /* Define if you have sigsetjmp(). */ #undef HAVE_SIGSETJMP +/* Define in you have strerror(). */ +#undef HAVE_STRERROR + /* Define if you want rc to encode strange characters in the environment. */ #undef PROTECT_ENV diff --git a/acinclude.m4 b/acinclude.m4 @@ -29,6 +29,19 @@ sigsetjmp(e, 1); esac ]) +dnl Similarly, AC_CHECK_FUNCS doesn't find strerror() on NetBSD. +AC_DEFUN(RC_FUNC_STRERROR, [ + AC_CACHE_CHECK(for strerror, rc_cv_strerror, + AC_TRY_LINK([ +#include <string.h> + ], [ +strerror(0); + ], rc_cv_strerror=yes, rc_cv_strerror=no)) + case "$rc_cv_strerror" in + yes) AC_DEFINE(HAVE_STRERROR) ;; + esac +]) + dnl HPUX needs _KERNEL defined to pick up RLIMIT_foo defines. (Why?) AC_DEFUN(RC_NEED_KERNEL, [ AC_CACHE_CHECK(if _KERNEL is required for RLIMIT defines, rc_cv_kernel_rlimit, diff --git a/builtins.c b/builtins.c @@ -251,7 +251,7 @@ extern void b_builtin(char **ignore) { /* wait for a given process, or all outstanding processes */ static void b_wait(char **av) { - int stat; + int status; pid_t pid; if (av[1] == NULL) { waitforall(); @@ -265,8 +265,8 @@ static void b_wait(char **av) { badnum(av[1]); return; } - if (rc_wait4(pid, &stat, FALSE) > 0) - setstatus(pid, stat); + if (rc_wait4(pid, &status, FALSE) > 0) + setstatus(pid, status); else set(FALSE); sigchk(); diff --git a/configure.ac b/configure.ac @@ -8,7 +8,7 @@ dnl Automake stuff. dnl Use this one for snapshots... dnl AM_INIT_AUTOMAKE(rc, 1.6s`echo $RELDATE |sed 's/-//g'`) dnl ...and this one for releases -AM_INIT_AUTOMAKE(rc, 1.6b2) +AM_INIT_AUTOMAKE(rc, 1.6b3) AM_CONFIG_HEADER(config.h) @@ -38,13 +38,15 @@ AC_TYPE_SIZE_T AC_TYPE_UID_T AC_CHECK_TYPE(ssize_t, long) -AC_CHECK_FUNCS(getgroups setpgrp setrlimit strerror) +AC_CHECK_FUNCS(getgroups setpgrp setrlimit) RC_FUNC_GETGROUPS RC_FUNC_SIGSETJMP AC_FUNC_SETPGRP +RC_FUNC_STRERROR + RC_NEED_KERNEL RC_TYPE_RLIM_T diff --git a/footobar.c b/footobar.c @@ -46,14 +46,14 @@ static int defaultfd(int op) { /* convert a function in Node * form into something rc can parse (and humans can read?) */ static bool Tconv(Format *f, int ignore) { + bool dollar = f->flags & FMT_altform; Node *n = va_arg(f->args, Node *); + if (n == NULL) { fmtprint(f, "()"); return FALSE; } switch (n->type) { - case nWord: fmtprint(f, "%S", n->u[0].s); break; - case nQword: fmtprint(f, "%#S", n->u[0].s); break; case nBang: fmtprint(f, "!%T", n->u[0].p); break; case nCase: fmtprint(f, "case %T", n->u[0].p); break; case nNowait: fmtprint(f, "%T&", n->u[0].p); break; @@ -70,21 +70,34 @@ static bool Tconv(Format *f, int ignore) { case nSwitch: fmtprint(f, "switch(%T){%T}", n->u[0].p, n->u[1].p); break; case nMatch: fmtprint(f, "~ %T %T", n->u[0].p, n->u[1].p); break; case nWhile: fmtprint(f, "while(%T)%T", n->u[0].p, n->u[1].p); break; - case nLappend: fmtprint(f, "(%T %T)", n->u[0].p, n->u[1].p); break; case nForin: fmtprint(f, "for(%T in %T)%T", n->u[0].p, n->u[1].p, n->u[2].p); break; case nVarsub: fmtprint(f, "$%T(%T)", n->u[0].p, n->u[1].p); break; + case nWord: + fmtprint(f, quotep(n->u[0].s, dollar) ? "%#S" : "%S", n->u[0].s); + break; + case nLappend: { + static bool inlist; + if (!inlist) { + inlist = TRUE; + fmtprint(f, "(%T %T)", n->u[0].p, n->u[1].p); + inlist = FALSE; + } else { + fmtprint(f, "%T %T", n->u[0].p, n->u[1].p); + } + break; + } case nCount: case nFlat: case nVar: { char *lp = "", *rp = ""; Node *n0 = n->u[0].p; - if (n0->type != nWord && n0->type != nQword) + if (n0->type != nWord) lp = "(", rp = ")"; switch (n->type) { default: panic("this can't happen"); break; - case nCount: fmtprint(f, "$#%s%T%s", lp, n0, rp); break; - case nFlat: fmtprint(f, "$^%s%T%s", lp, n0, rp); break; - case nVar: fmtprint(f, "$%s%T%s", lp, n0, rp); break; + case nCount: fmtprint(f, "$#%s%#T%s", lp, n0, rp); break; + case nFlat: fmtprint(f, "$^%s%#T%s", lp, n0, rp); break; + case nVar: fmtprint(f, "$%s%#T%s", lp, n0, rp); break; } break; } @@ -212,45 +225,52 @@ extern char *get_name(char *s) { } } -/* - Hacky routine to split a ^A-separated environment variable. Scans - backwards. Be VERY CAREFUL with the loop boundary conditions. e.g., - notice what happens when there is no ^A in the extdef. (It does - the right thing, of course :-) -*/ - -#define skipleft(p) do --p; while (*p != '\0' && *p != '\001'); +/* interpret a variable from environment. ^A separates list elements; + ^B escapes a literal ^A or ^B. For minimal surprise, ^B followed + by anything other than ^A or ^B is preserved. */ extern List *parse_var(char *extdef) { - char *endp, *realend, *sepp; - List *tailp, *new; - - extdef = strchr(extdef, '='); - *extdef++ = '\0'; /* null "initiate" the string for rtol scanning */ - - sepp = realend = strchr(extdef, '\0'); - tailp = NULL; - - while (1) { - endp = sepp; /* set endp to end of current mebmer */ - skipleft(sepp); /* advance sepp to the previous \1, */ - *endp = '\0'; /* and null terminate the member. */ - + char *begin, *end, *from, *to; + int len; + List *first, *last, *new; + + first = last = NULL; + begin = strchr(extdef, '='); + assert(begin); /* guaranteed by initenv() */ + while (*begin) { + ++begin; + end = begin; + len = 0; + while (*end != ENV_SEP && *end != '\0') { + if (*end == ENV_ESC) { + ++end; + if (*end != ENV_SEP && *end != ENV_ESC) --end; + } + ++end; ++len; + } new = enew(List); - new->w = ecpy(sepp+1); + if (last) + last->n = new; + else + first = new; + last = new; + new->w = ealloc(len + 1); new->m = NULL; - new->n = tailp; - tailp = new; - - if (sepp < extdef) /* break when sepp hits the null "initiator" */ - break; - if (endp != realend) /* else restore the \1 in between members */ - *endp = '\001'; + new->n = NULL; + to = new->w; + for (from = begin; from < end; ++from) { + if (*from == ENV_ESC) { + ++from; + if (*from != ENV_SEP && *from != ENV_ESC) + --from; + } + *to = *from; + ++to; + } + *to = '\0'; + begin = end; } - if (endp != realend) - *endp = '\001'; - *--extdef = '='; /* restore extdef '=' */ - return tailp; + return first; } /* get an environment entry for a function and have rc parse it. */ @@ -279,16 +299,42 @@ static bool Aconv(Format *f, int ignore) { return FALSE; } +/* %L -- print a list */ static bool Lconv(Format *f, int ignore) { - List *l = va_arg(f->args, List *); - char *sep = va_arg(f->args, char *); - char *fmt = (f->flags & FMT_leftside) ? "%s%s" : "%-S%s"; + bool plain; + char *sep; + List *l, *n; + + plain = f->flags & FMT_leftside; + l = va_arg(f->args, List *); + sep = va_arg(f->args, char *); if (l == NULL && (f->flags & FMT_leftside) == 0) fmtprint(f, "()"); else { - List *s; - for (s = l; s != NULL; s = s->n) - fmtprint(f, fmt, s->w, s->n == NULL ? "" : sep); + for (; l != NULL; l = n) { + n = l->n; + fmtprint(f, plain ? "%s" : "%-S", l->w); + if (n != NULL) fmtputc(f, *sep); + } + } + return FALSE; +} + +/* %W -- print a list for exporting */ +static bool Wconv(Format *f, int ignore) { + List *l, *n; + + l = va_arg(f->args, List *); + for (; l != NULL; l = n) { + char c, *s; + + for (s = l->w; (c = *s) != '\0'; ++s) { + if (c == ENV_SEP || c == ENV_ESC) + fmtputc(f, ENV_ESC); + fmtputc(f, c); + } + n = l->n; + if (n != NULL) fmtputc(f, ENV_SEP); } return FALSE; } @@ -329,6 +375,7 @@ void initprint(void) { fmtinstall('S', Sconv); fmtinstall('T', Tconv); fmtinstall('D', Dconv); + fmtinstall('W', Wconv); #if PROTECT_ENV fmtinstall('F', Fconv); #else diff --git a/glob.c b/glob.c @@ -34,23 +34,13 @@ static List *sort(List *); extern bool lmatch(List *s, List *p) { List *q; - int i; - bool okay; if (s == NULL) { if (p == NULL) /* null matches null */ return TRUE; - for (; p != NULL; p = p->n) { /* one or more stars match null */ - if (*p->w != '\0') { /* the null string is a special case; it does *not* match () */ - okay = TRUE; - for (i = 0; p->w[i] != '\0'; i++) - if (p->w[i] != '*' || p->m[i] != 1) { - okay = FALSE; - break; - } - if (okay) - return TRUE; - } - } + for (; p != NULL; p = p->n) /* one or more stars match null */ + if (strspn(p->w, "*") == strlen(p->w) && + p->m != NULL && strlen(p->m) == strlen(p->w)) + return TRUE; return FALSE; } for (; s != NULL; s = s->n) diff --git a/glom.c b/glom.c @@ -384,7 +384,7 @@ extern List *glom(Node *n) { words = n->u[0].p; tail = NULL; while (words != NULL && (words->type == nArgs || words->type == nLappend)) { - if (words->u[1].p != NULL && words->u[1].p->type != nWord && words->u[1].p->type != nQword) + if (words->u[1].p != NULL && words->u[1].p->type != nWord) break; head = glom(words->u[1].p); if (head != NULL) { @@ -405,7 +405,6 @@ extern List *glom(Node *n) { qredir(n); return NULL; case nWord: - case nQword: return word(n->u[0].s, n->u[1].s); case nNmpipe: return mkcmdarg(n); diff --git a/hash.c b/hash.c @@ -230,7 +230,8 @@ extern void initenv(char **envp) { static bool var_exportable(char *s) { static char *notforexport[] = { - "apid", "apids", "ifs", "path", "pid", "version", "*" + "apid", "apids", "cdpath", "home", "ifs", + "path", "pid", "version", "*" }; int i; for (i = 0; i < arraysize(notforexport); i++) diff --git a/heredoc.c b/heredoc.c @@ -82,14 +82,14 @@ static Node *parseheredoc(char *s) { while ((c = *s++) != '\0' && c != '$') ; *--s = '\0'; - node = mk(nQword, begin, NULL); + node = mk(nWord, begin, NULL); break; } case '$': { char *begin = ++s, *var; c = *s++; if (c == '$') { - node = mk(nQword, "$", NULL); + node = mk(nWord, "$", NULL); c = *s; } else { size_t len = 0; @@ -129,7 +129,7 @@ extern int heredoc(int end) { char *s = readheredoc(here->name); if (dead) return FALSE; - n->u[2].p = here->quoted ? mk(nQword, s, NULL) : parseheredoc(s); + n->u[2].p = here->quoted ? mk(nWord, s, NULL, FALSE) : parseheredoc(s); n->u[0].i = rHerestring; } while ((here = here->n) != NULL); } @@ -140,7 +140,7 @@ extern int heredoc(int end) { extern int qdoc(Node *name, Node *n) { Hq *new, **prev; - if (name->type != nWord && name->type != nQword) { + if (name->type != nWord) { yyerror("eof-marker not a single literal word"); flushu(); return FALSE; @@ -149,7 +149,7 @@ extern int qdoc(Node *name, Node *n) { ; *prev = new = nnew(Hq); new->name = name->u[0].s; - new->quoted = (name->type == nQword); + new->quoted = name->u[2].i; new->doc = n; new->n = NULL; return TRUE; diff --git a/input.c b/input.c @@ -38,7 +38,11 @@ static Input *istack, *itop; static int (*realgchar)(void); static void (*realugchar)(int); -int last; +int lastchar; + +#if EDITLINE || READLINE +static char *rlinebuf, *prompt; +#endif #if EDITLINE || READLINE static char *rlinebuf, *prompt; @@ -49,7 +53,7 @@ extern int gchar() { if (eofread) { eofread = FALSE; - return last = EOF; + return lastchar = EOF; } while ((c = (*realgchar)()) == '\0') @@ -63,7 +67,7 @@ extern void ugchar(int c) { } static int dead() { - return last = EOF; + return lastchar = EOF; } static void ugdead(int ignore) { @@ -80,7 +84,7 @@ static void ugalive(int c) { /* get the next character from a string. */ static int stringgchar() { - return last = (inbuf[chars_out] == '\0' ? EOF : inbuf[chars_out++]); + return lastchar = (inbuf[chars_out] == '\0' ? EOF : inbuf[chars_out++]); } /* @@ -94,7 +98,7 @@ static int fdgchar() { if (chars_out >= chars_in + 2) { /* has the buffer been exhausted? if so, replenish it */ while (1) { #if EDITLINE || READLINE - if (interactive && istack->fd == 0 && isatty(0)) { + if (interactive && istack->t == iFd && isatty(istack->fd)) { /* The readline library doesn't handle read() returning EAGAIN. */ makeblocking(istack->fd); rlinebuf = rc_readline(prompt); @@ -117,10 +121,18 @@ static int fdgchar() { do { r = rc_read(istack->fd, inbuf + 2, BUFSIZE); sigchk(); - if (errno == EAGAIN) { + switch (errno) { + case EAGAIN: if (!makeblocking(istack->fd)) panic("not O_NONBLOCK"); errno = EINTR; + break; + case EIO: + if (makesamepgrp(istack->fd)) + errno = EINTR; + else + errno = EIO; + break; } } while (r < 0 && errno == EINTR); if (r < 0) { @@ -132,13 +144,13 @@ static int fdgchar() { break; } if (chars_in == 0) - return last = EOF; + return lastchar = EOF; chars_out = 2; if (dashvee) writeall(2, inbuf + 2, chars_in); history(); } - return last = inbuf[chars_out++]; + return lastchar = inbuf[chars_out++]; } /* set up the input stack, and put a "dead" input at the bottom, so that yyparse will always read eof */ @@ -159,7 +171,7 @@ static void pushcommon() { istack->ibuf = inbuf; istack->lineno = lineno; istack->saved = save_lineno; - istack->last = last; + istack->last = lastchar; istack->eofread = eofread; istack++; idiff = istack - itop; @@ -206,7 +218,7 @@ extern void popinput() { realgchar = dead; realugchar = ugdead; } - last = istack->last; + lastchar = istack->last; eofread = istack->eofread; inbuf = istack->ibuf; chars_out = istack->index; @@ -222,7 +234,7 @@ extern void popinput() { extern void flushu() { int c; - if (last == '\n' || last == EOF) + if (lastchar == '\n' || lastchar == EOF) return; while ((c = gchar()) != '\n' && c != EOF) ; /* skip to newline */ @@ -234,9 +246,9 @@ extern void flushu() { extern Node *doit(bool clobberexecit) { bool eof; - bool execit ; + bool execit; Jbwrap j; - Estack e1, e2; + Estack e1; Edata jerror; if (dashen) @@ -247,6 +259,7 @@ extern Node *doit(bool clobberexecit) { except(eError, jerror, &e1); for (eof = FALSE; !eof;) { Edata block; + Estack e2; block.b = newblock(); except(eArena, block, &e2); sigchk(); @@ -261,12 +274,18 @@ extern Node *doit(bool clobberexecit) { if (interactive) { List *s; if (!dashen && fnlookup("prompt") != NULL) { + static bool died = FALSE; static char *arglist[] = { "prompt", NULL }; - funcall(arglist); + + if (!died) { + died = TRUE; + funcall(arglist); + } + died = FALSE; } if ((s = varlookup("prompt")) != NULL) { #if EDITLINE || READLINE - if (istack->t == iFd && istack->fd == 0 && isatty(0)) + if (istack->t == iFd && isatty(istack->fd)) prompt = s->w; else #endif @@ -277,7 +296,7 @@ extern Node *doit(bool clobberexecit) { inityy(); if (yyparse() == 1 && execit) rc_raise(eError); - eof = (last == EOF); /* "last" can be clobbered during a walk() */ + eof = (lastchar == EOF); /* "lastchar" can be clobbered during a walk() */ if (parsetree != NULL) { if (execit) walk(parsetree, TRUE); @@ -353,7 +372,7 @@ extern void print_prompt2() { lineno++; if (interactive) { #if EDITLINE || READLINE - if (istack->t == iFd && istack->fd == 0 && isatty(0)) + if (istack->t == iFd && isatty(istack->fd)) prompt = prompt2; else #endif diff --git a/lex.c b/lex.c @@ -69,6 +69,18 @@ enum filedescriptors { UNSET = -9, CLOSED = -1 }; +/* does this string require quoting? */ +extern bool quotep(char *s, bool dollar) { + unsigned char c; + const char *meta; + + meta = dollar ? dnw : nw; + while ((c = *s++)) + if (meta[c]) + return TRUE; + return FALSE; +} + extern int yylex() { static bool dollar = FALSE; bool saw_meta = FALSE; @@ -147,6 +159,7 @@ top: while ((c = gchar()) == ' ' || c == '\t') } else { y->word.m = NULL; } + y->word.q = FALSE; return WORD; } if (c == '`' || c == '!' || c == '@' || c == '~' || c == '$' || c == '\'') { @@ -179,7 +192,8 @@ top: while ((c = gchar()) == ' ' || c == '\t') case '\'': w = RW; i = 0; - do { + /* double ' to quote it, like this: 'how''s it going?' */ + while ((c = gchar()) != '\'' || (c = gchar()) == '\'') { buf[i++] = c; if (c == '\n') print_prompt2(); @@ -190,11 +204,12 @@ top: while ((c = gchar()) == ' ' || c == '\t') } if (i >= bufsize) buf = realbuf = erealloc(buf, bufsize *= 2); - } while ((c = gchar()) != '\'' || (c = gchar()) == '\''); /* quote "'" thus: 'how''s it going?' */ + } ugchar(c); buf[i] = '\0'; y->word.w = ncpy(buf); y->word.m = NULL; + y->word.q = TRUE; return WORD; case '\\': if ((c = gchar()) == '\n') { @@ -306,13 +321,13 @@ extern void yyerror(const char *s) { if (!interactive) { if (w != NW) tok = realbuf; - else if (last == EOF) + else if (lastchar == EOF) tok = "eof"; - else if (last == '\n') + else if (lastchar == '\n') tok = "end of line"; else - tok = nprint((last < 32 || last > 126) ? "(decimal %d)" : "'%c'", last); - fprint(2, "line %d: %s near %s\n", lineno - (last == '\n'), s, tok); + tok = nprint((lastchar < 32 || lastchar > 126) ? "(decimal %d)" : "'%c'", lastchar); + fprint(2, "line %d: %s near %s\n", lineno - (lastchar == '\n'), s, tok); } else fprint(2, "%s\n", s); } diff --git a/main.c b/main.c @@ -2,7 +2,7 @@ #include "rc.h" -bool dashdee, dashee, dashvee, dashex, dashell, dasheye, +bool dashdee, dashee, dashvee, dashex, dashell, dashEYE, dasheye, dashen, dashpee, dashess, interactive; pid_t rc_pid; @@ -19,7 +19,7 @@ extern int main(int argc, char *argv[], char *envp[]) { dollarzero = argv[0]; rc_pid = getpid(); dashell = (*argv[0] == '-'); /* Unix tradition */ - while ((c = rc_getopt(argc, argv, "c:deilnopsvx")) != -1) + while ((c = rc_getopt(argc, argv, "c:deiIlnopsvx")) != -1) switch (c) { case 'c': dashsee[0] = rc_optarg; @@ -30,6 +30,10 @@ extern int main(int argc, char *argv[], char *envp[]) { case 'e': dashee = TRUE; break; + case 'I': + dashEYE = TRUE; + interactive = FALSE; + break; case 'i': dasheye = interactive = TRUE; break; @@ -59,8 +63,8 @@ extern int main(int argc, char *argv[], char *envp[]) { } quitopts: argv += rc_optind; - /* use isatty() iff -i is not set, and iff the input is not from a script or -c flags */ - if (!dasheye && dashsee[0] == NULL && (dashess || *argv == NULL)) + /* use isatty() iff neither -i nor -I is set, and iff the input is not from a script or -c flags */ + if (!dasheye && !dashEYE && dashsee[0] == NULL && (dashess || *argv == NULL)) interactive = isatty(0); if (!dashoh) { checkfd(0, rFrom); diff --git a/mksignal.c b/mksignal.c @@ -1,5 +1,6 @@ -#include <stdio.h> #include <signal.h> +#include <stdio.h> +#include <stdlib.h> #ifdef NSIG #define NUMSIG NSIG @@ -7,13 +8,13 @@ #define NUMSIG 1 #endif -typedef struct { +struct signaming { int signo; - char *signame; - char *sigmsg; -} signaming; + const char *signame; + const char *sigmsg; +}; -static signaming signamings[] = { +static struct signaming signamings[] = { #ifdef SIGABRT { SIGABRT, "sigabrt", "abort"}, #endif @@ -191,7 +192,7 @@ static signaming signamings[] = { { 0, 0, 0} }; -static void barf(char *msg) { +static void barf(const char *msg) { fprintf(stderr, "mksignals: %s\n", msg); exit(1); } @@ -199,7 +200,7 @@ static void barf(char *msg) { int main(void) { int maxsig = NUMSIG-1; int s; - signaming *snp; + struct signaming *snp; FILE *outf; for (snp = signamings; snp->signo; ++snp) diff --git a/nalloc.c b/nalloc.c @@ -50,7 +50,8 @@ extern void *nalloc(size_t n) { ulp->used = base + n; return &ulp->mem[base]; } else { - getblock(n); /* assert(ul->used) == 0 */ + getblock(n); + assert(ul->used == 0); (ulp = ul)->used = n; return &ulp->mem[0]; } diff --git a/open.c b/open.c @@ -43,3 +43,21 @@ extern bool makeblocking(int fd) { } return TRUE; } + +/* make a file descriptor the same pgrp as us. Returns TRUE if +it changes anything. */ + +extern bool makesamepgrp(int fd) { + pid_t grp; + + grp = getpgrp(); + + if (tcgetpgrp(fd) == grp) + return FALSE; + + if (tcsetpgrp(fd, grp) < 0) { + uerror("tcsetgrp"); + return FALSE; + } + return TRUE; +} diff --git a/parse.y b/parse.y @@ -124,7 +124,7 @@ first : comword | first '^' sword { $$ = mk(nConcat,$1,$3); } sword : comword - | keyword { $$ = mk(nWord,$1, NULL); } + | keyword { $$ = mk(nWord, $1, NULL, FALSE); } word : sword | word '^' sword { $$ = mk(nConcat,$1,$3); } @@ -139,7 +139,7 @@ comword : '$' sword { $$ = mk(nVar,$2); } | BACKBACK word sword { $$ = mk(nBackq,$2,$3); } | '(' nlwords ')' { $$ = $2; } | REDIR brace { $$ = mk(nNmpipe,$1.type,$1.fd,$2); } - | WORD { $$ = ($1.w[0] == '\'') ? mk(nQword, $1.w+1, NULL) : mk(nWord,$1.w, $1.m); } + | WORD { $$ = mk(nWord, $1.w, $1.m, $1.q); } keyword : FOR { $$ = "for"; } | IN { $$ = "in"; } @@ -166,7 +166,7 @@ optnl : /* empty */ %% void initparse() { - star = treecpy(mk(nVar,mk(nWord,"*",NULL)), ealloc); - nolist = treecpy(mk(nVar,mk(nWord,"ifs",NULL)), ealloc); + star = treecpy(mk(nVar, mk(nWord,"*", NULL, FALSE)), ealloc); + nolist = treecpy(mk(nVar, mk(nWord,"ifs", NULL, FALSE)), ealloc); } diff --git a/print.c b/print.c @@ -20,7 +20,6 @@ static bool name(Format *format, int ignore) { \ } Flag(uconv, FMT_unsigned) -Flag(hconv, FMT_short) Flag(rc_lconv, FMT_long) #if HAVE_QUAD_T @@ -103,8 +102,6 @@ static void intconv(Format *format, unsigned int radix, int upper, const char *a if (flags & FMT_long) n = va_arg(format->args, long); - else if (flags & FMT_short) - n = va_arg(format->args, short); else n = va_arg(format->args, int); @@ -202,7 +199,6 @@ static void inittab(void) { fmttab['%'] = pctconv; fmttab['u'] = uconv; - fmttab['h'] = hconv; fmttab['l'] = rc_lconv; fmttab['#'] = altconv; fmttab['-'] = leftconv; diff --git a/proto.h b/proto.h @@ -77,8 +77,10 @@ extern char *sys_errlist[]; #if SETPGRP_VOID /* Smells like POSIX: should all be ok. */ #else -/* BSD: fake it. */ +/* Old BSD: fake it. */ #define setpgid(pid, pgrp) setpgrp(pid, pgrp) +#include <sys/ioctl.h> +#define tcgetpgrp(fd) ioctl((fd), TIOCGPGRP) #define tcsetpgrp(fd, pgrp) ioctl((fd), TIOCSPGRP, &(pgrp)) #endif @@ -86,6 +88,7 @@ extern char *sys_errlist[]; /* Nothing doing. */ #define setpgid() +#define tcgetpgrp() #define tcsetpgrp() #endif /*HAVE_SETPGRP */ diff --git a/rc.1 b/rc.1 @@ -161,12 +161,12 @@ .if !"\\$4"" .Xf \\$2 \\$1 "\\$3\\f\\$1\\$4\\*(Xi" "\\$5" "\\$6" "\\$7" "\\$8" "\\$9" .if "\\$4"" \\$3\fR\s10 .. -.TH RC 1 "1999-12-10" +.TH RC 1 "2001-10-12" .SH NAME rc \- shell .SH SYNOPSIS .B rc -.RB [ \-deilnopsVvx ] +.RB [ \-deiIlnopsvx ] .RB [ \-c .IR command ] .RI [ arguments ] @@ -242,13 +242,23 @@ will be in mode. That is, a prompt (from .Cr $prompt(1)\^ ) -will be printed before an -input line is taken, and +will be printed before an input line is taken, and .I rc -will ignore the signals +will ignore +.Cr SIGINT . +.TP +.Cr \-I +If the +.Cr \-I +flag is present, or if the input to +.I rc +is not from a terminal, then +.I rc +will not be in interactive mode. No prompts will be printed, and .Cr SIGINT -and -.Cr SIGQUIT . +will cause +.I rc +to exit. .TP .Cr \-l If the @@ -261,7 +271,7 @@ is a dash then .I rc will behave as a login shell. -That is, it will try to run commands present in +That is, it will run commands from .Cr $home/.rcrc , if this file exists, before reading any other input. .TP @@ -339,7 +349,7 @@ of shell functions, builtin commands, or as a file in the directories named by .Cr $path . .SS "Background Tasks" -A command ending with a +A command ending with .Cr & is run in the background; that is, the shell returns immediately rather than waiting for the command to @@ -506,6 +516,7 @@ Note that no spaces may appear in these constructs: .Ds .Cr "command > [2] file" .De +.PP would send the output of the command to a file named .Cr [2] , with the intended filename appearing in the command's argument list. @@ -618,9 +629,7 @@ redirection is implemented with some kind of pipe, and since one cannot .IR lseek (2) on a pipe, commands that use .IR lseek (2) -will hang. -For example, -most versions of +will hang. For example, some versions of .IR diff (1) use .IR lseek (2) @@ -671,7 +680,7 @@ and performs the command as long as the .I test is true. .TP -.Ci "for (" var " in" " list" ) " cmd" +.Ci "for (" var " in " list ) " cmd" .I rc sets .I var @@ -899,18 +908,16 @@ Note that the null string, .Cr "''" , and the null list, .Cr "()" , -are two very -different things. -Assigning the null string to variable is a valid -operation, but it does not remove its definition. -For example, -if -.Cr $a -is set to -.Cr "''" , -then -.Cr "$#a" , -returns a 1. +are two very different things. Assigning the null string to a +variable is a valid operation, but it does not remove its definition. +.Ds +.Cr "null = '' empty = () echo $#null $#empty" +.De +.PP +produces the output +.Ds +.Cr "1 0" +.De .SS "List Concatenation" Two lists may be joined by the concatenation operator .Rc ( ^ ). @@ -981,7 +988,7 @@ A list may be assigned to a variable, using the notation: .PP The special variable .Cr * -may be assigned to using this notation; +may also be assigned to using this notation; .I rc has no .B set @@ -993,19 +1000,20 @@ digits, may be used as a variable name. Any character except may be used, but special characters must be quoted. All user-defined variables are exported into the environment. .PP -The value of a variable is referenced with the notation: +The value of a variable is referenced with the dollar +.Rc ( $ ) +operator: .Ds .Ci $ var .De .PP Any variable which has not been assigned a value returns the null list, .Cr "()" , -when referenced. -In addition, multiple references are allowed: +when referenced. Multiple references are allowed: .Ds -.Cr a=foo -.Cr b=a -.Cr "echo $$b" +.Cr "a = foo" +.Cr "b = a" +.Cr "echo $ $ b" .De .PP prints @@ -1064,16 +1072,11 @@ Variables may be subscripted with the notation .PP where .I n -is a list of integers (origin 1). -The list of subscripts need -not be in order or even unique. -Thus, if +is a list of integers (origin 1). The opening parenthesis must +immediately follow the variable name. The list of subscripts need not +be in order or even unique. Thus, .Ds .Cr "a=(one two three)" -.De -.PP -then -.Ds .Cr "echo $a(3 3 3)" .De .PP @@ -1119,14 +1122,15 @@ This returns a single-element list, with the number of elements in .Cr $var . .SS "Flat Lists" In order to create a single-element list from a multi-element list, -with the components space-separated, use +with the components space-separated, use the dollar-caret +.Rc ( $^ ) +operator: .Ds .Cr $^var .De .PP This is useful when the normal list concatenation rules need to be -bypassed. -For example, to append a single period at the end of +bypassed. For example, to append a single period at the end of .Cr $path , use: .Ds @@ -1245,7 +1249,9 @@ Note also that an assignment to .Cr $cdpath causes an automatic assignment to .Cr $CDPATH , -and vice-versa. +and vice-versa. Only +.Cr $CDPATH +is exported into the environment. .TP .Cr history .Cr $history @@ -1299,11 +1305,11 @@ and and .Cr $PATH are aliased to each other. -If -.Cr $path -or +If neither .Cr $PATH -is not set at startup time, +nor +.Cr $path +is set at startup time, .Cr $path assumes a default value suitable for your system. This is typically @@ -1355,19 +1361,15 @@ is about to print .Cr "$prompt(1)" . .TP .Cr status " (read only)" -The exit status of the last command. -If the command exited with a numeric value, -that number is the status. -If the died with a signal, -the status is the name of that signal; if a core file -was created, the string +The exit status of the last command. If the command exited with a +numeric value, that number is the status. If the command died with a +signal, the status is the name of that signal; if a core file was +created, the string .Rc `` +core '' -is appended. -The value of +is appended. The value of .Cr $status -for a pipeline is a list, with one entry, -as above, for each process in the pipeline. -For example, the command +for a pipeline is a list, with one entry, as above, for each process +in the pipeline. For example, the command .Ds .Cr "ls | wc" .De @@ -1497,7 +1499,9 @@ Thus: .Cr "fn sigint {}" .De .PP -causes SIGINT to be ignored by the shell. +causes +.Cr SIGINT +to be ignored by the shell. Only signals that are being ignored are passed on to programs run by .IR rc ; signal functions are not exported. @@ -1629,9 +1633,10 @@ The resources which can be shown or altered are .BR filesize , .BR datasize , .BR stacksize , -.B coredumpsize -and -.BR memoryuse . +.BR coredumpsize , +.BR memoryuse , +and, where supported, +.BR descriptors . For example: .Ds @@ -1766,22 +1771,66 @@ For example, .Ds .Cr "whatis \-\|\- \-p" .De +.SH EXAMPLES +The +.B shift +builtin only shifts +.Cr "$*" . +This function can shift any variable (except +.Cr "$lshift" ). +.Ds +.Cr "fn lshift { lshift=$*; *=$$1; shift $lshift(2); $lshift(1)=$* }" +.De +.PP +With this definition in place, +.Ds +.Cr "walrus = (shoes ships sealing-wax cabbages kings)" +.Cr "lshift walrus 3" +.Cr "whatis walrus" +.De +.PP +prints +.Ds +.Cr "walrus=(cabbages kings)" +.De +.PP +The +.Cr $^var +operator flattens a list by separating each element with a space. +This function allows the separator to be an arbitrary string. +.Ds +.Cr "fn lflat {" +.Cr " lflat=$*; *=$$1" +.Cr " while () {" +.Cr " echo -n $1; shift" +.Cr " ~ $#* 0 && break" +.Cr " echo -n $lflat(2)" +.Cr "}" +.De +.PP +With this definition in place, +.Ds +.Cr "hops=(uunet mcvax ukc tlg)" +.Cr lflat hops ! +.De +.PP +prints (with no final newline) +.Ds +.Cr uunet!mcvax!ukc!tlg +.De .SH "EXIT STATUS" The exit status of .I rc -is normally the same as that of the -last command executed. If the last -command was a pipeline, +is normally the same as that of the last command executed. If the +last command was a pipeline, .I rc exits .Cr 0 -if every command in the pipeline did; -otherwise it exits +if every command in the pipeline did; otherwise it exits .Cr 1 . .PP .I rc -can be made to exit with a particular -status using the +can be made to exit with a particular status using the .B exit builtin. .SH GRAMMAR @@ -1894,12 +1943,6 @@ The design of this shell was copied from the .I rc that Tom Duff wrote at Bell Labs. .SH BUGS -Environment variables that contain the ASCII character SOH (also known -as '\\001', or CTRL-A) will be split into lists at that character in -descendant -.IR rc -processes. -.PP There is a compile-time limit on the number of .Cr ; separated commands in a line: usually 500. @@ -1913,31 +1956,35 @@ On modern systems that support or .Cr /proc/self/fd , .Cr <{foo} -style redirection is implemented that way. -However, on other systems it is implemented with named pipes, -and it is sometimes +style redirection is implemented that way. However, on older systems +it is implemented with named pipes. Allegedly, it is sometimes possible to foil .I rc into removing the FIFO it places in .Cr /tmp prematurely, or it is even possible to cause .I rc -to hang. -.PP -The functionality of -.B shift -should be available for variables other than -.Cr "$*" . +to hang. (The current maintainer has never seen this, but then he +doesn't use systems which lack +.Cr /dev/fd +any more. If anybody can reproduce this problem, please let the +maintainer know.) .PP .B echo is built in only for performance reasons, which is a bad idea. .PP There should be a way to avoid exporting a variable. .PP -The -.Cr $^var -notation for flattening should allow for using an arbitrary -separating character, not just space. +Extra parentheses around a +.Cr ~ +expression or a +.Cr ! +expression are a syntax error. Thus, this code is illegal. +.Ds +.Cr "while ((~ $1 -*) && (! ~ $1 --)) { ..." +.De +.TP +The redundant inner parentheses must be omitted. .PP It is usually possible to use parentheses to defeat free caret insertion; that is, to use @@ -2020,12 +2067,12 @@ for .IR file . This .I rc -does not; this is a bug. +does not, since it is not considered useful. .PP The list flattening operator, .Cr $^foo , is spelt -.Cr $"foo +.Cr "$\"foo" in those versions of the Bell Labs .IR rc which have it. @@ -2063,7 +2110,7 @@ Unix Research System, Tenth Edition, Volume 2. (Saunders College Publishing) .PP -.Cr http://www.star.le.ac.uk/~tjg/rc/misc/td.html , +.Cr http://www.star.le.ac.uk/~tjg/rc/misc/td , an updated version of the above paper. .PP .IR history (1) diff --git a/rc.h b/rc.h @@ -1,13 +1,18 @@ +#define NDEBUG 1 +#include <assert.h> + #include "config.h" #include "proto.h" /* datatypes */ +#define ENV_SEP '\001' +#define ENV_ESC '\002' + /* braindamaged IBM header files #define true and false */ #undef FALSE #undef TRUE - typedef void builtin_t(char **); typedef struct Block Block; typedef struct Dup Dup; @@ -27,8 +32,8 @@ typedef struct Format Format; typedef union Edata Edata; typedef enum nodetype { - nAndalso, nAssign, nBackq, nBang, nBody, nCbody, nNowait, nBrace, nConcat, - nCount, nElse, nFlat, nDup, nEpilog, nNewfn, nForin, nIf, nQword, + nAndalso, nAssign, nBackq, nBang, nBody, nCbody, nNowait, nBrace, + nConcat, nCount, nElse, nFlat, nDup, nEpilog, nNewfn, nForin, nIf, nOrelse, nPipe, nPre, nRedir, nRmfn, nArgs, nSubshell, nCase, nSwitch, nMatch, nVar, nVarsub, nWhile, nWord, nLappend, nNmpipe } nodetype; @@ -95,6 +100,7 @@ struct Redir { struct Word { char *w, *m; + bool q; }; struct Rq { @@ -133,7 +139,6 @@ struct Format { enum { FMT_quad = 1, /* %q */ FMT_long = 2, /* %l */ - FMT_short = 4, /* %h */ FMT_unsigned = 8, /* %u */ FMT_zeropad = 16, /* %0 */ FMT_leftside = 32, /* %- */ @@ -273,11 +278,12 @@ extern void pushfd(int); extern void pushstring(char **, bool); extern void popinput(void); extern void closefds(void); -extern int last; +extern int lastchar; extern bool rcrc; /* lex.c */ +extern bool quotep(char *, bool); extern int yylex(void); extern void inityy(void); extern void yyerror(const char *); @@ -305,6 +311,7 @@ extern void restoreblock(Block *); /* open.c */ extern int rc_open(const char *, redirtype); extern bool makeblocking(int); +extern bool makesamepgrp(int); /* print.c */ /* diff --git a/tree.c b/tree.c @@ -18,10 +18,11 @@ extern Node *mk(int /*nodetype*/ t,...) { n->u[1].i = va_arg(ap, int); n->u[2].i = va_arg(ap, int); break; - case nWord: case nQword: - n = nalloc(offsetof(Node, u[2])); + case nWord: + n = nalloc(offsetof(Node, u[3])); n->u[0].s = va_arg(ap, char *); n->u[1].s = va_arg(ap, char *); + n->u[2].i = va_arg(ap, int); break; case nBang: case nNowait: case nCount: case nFlat: case nRmfn: case nSubshell: @@ -79,7 +80,7 @@ extern Node *treecpy(Node *s, void *(*alloc)(size_t)) { n->u[1].i = s->u[1].i; n->u[2].i = s->u[2].i; break; - case nWord: case nQword: + case nWord: n = (*alloc)(offsetof(Node, u[2])); n->u[0].s = strcpy((char *) (*alloc)(strlen(s->u[0].s) + 1), s->u[0].s); if (s->u[1].s != NULL) { @@ -138,7 +139,7 @@ extern void treefree(Node *s) { /* NOTREACHED */ case nDup: break; - case nWord: case nQword: + case nWord: efree(s->u[0].s); efree(s->u[1].s); break; diff --git a/trip.rc b/trip.rc @@ -318,7 +318,7 @@ submatch 'cdpath='''' cd frobnatz' 'couldn''t cd to frobnatz' 'cd to frobnatz su submatch 'wait 1 2 3' 'too many arguments to wait' 'arg count' $rc -c 'wait 1' >[2]/dev/null && fail wait 1 -sleep 5& +sleep 3& expect $apid echo $apids wait @@ -329,7 +329,6 @@ if (~ `` '' {wait} ?) # # matching # - touch /tmp/abc.$pid /tmp/bbc.$pid mkdir /tmp/dir.$pid /tmp/dip.$pid touch /tmp/dir.$pid/^(a b c) /tmp/dip.$pid/^(a b c) @@ -369,7 +368,6 @@ rm -rf /tmp/dir.$pid /tmp/dip.$pid # # signals # - fn sigint {eval} kill -2 $pid fn sigint @@ -377,7 +375,6 @@ fn sigint # # path searching # - $rc -c /frobnatz >[2]/dev/null && fail 'search error' touch /tmp/noexec.$pid @@ -427,15 +424,13 @@ $rc -c '. /frobnatz' >[2]/dev/null && fail 'dot of a nonexistent file' # # stdin # - if (!~ `{echo echo hi | $rc} hi) fail piping stdin to rc # # functions, variables & environment # - -fn --- {for(i)a|[2=3]b>>c<<<'e'&f>[2=1]} +fn --- {for(i)a|[2=3]b>>c<<<e&f>[2=1]} if (whatis printenv >/dev/null>[2=1]) { printenv=printenv @@ -444,7 +439,7 @@ if (whatis printenv >/dev/null>[2=1]) { } else printenv=() -if (~ $#printenv 1 && !~ `` $nl {$printenv | grep fn___2d__2d__2d} 'fn___2d__2d__2d={for(i in $*)a|[2=3]b >>c <<<''e''&f >[2=1]}') +if (~ $#printenv 1 && !~ `` $nl {$printenv | grep fn___2d__2d__2d} 'fn___2d__2d__2d={for(i in $*)a|[2=3]b >>c <<<e&f >[2=1]}') fail protect_env fn --- {replace} @@ -470,7 +465,6 @@ fn_ff='{' prompt='' if (!~ `` $nl {$rc -cff>[2=1]} 'line 1: '*' error near eof') # # statuses # - ~ `{$rc -ec 'sleep 10&kill -9 $apid;wait'>[2=1]} killed || fail status diagnostic @@ -481,7 +475,6 @@ submatch 'exit foo' 'bad status' 'exit diagnostic' # # control structures # - if (!~ `{false || echo hi} hi) fail '||' if (!~ `{true && echo hi} hi) @@ -522,7 +515,6 @@ submatch 'fn () {eval}' 'null function name' 'assigning null function name' # # prompt # - fn prompt {echo hi} prompt=() if (!~ `{$rc -i /dev/null>[2]/dev/null} hi) fail fn prompt fn prompt @@ -530,7 +522,6 @@ fn prompt # # history # - history=/tmp/hist.$pid prompt='' echo 'history=()' | $rc -i if (!~ `{cat /tmp/hist.$pid} 'history=()') @@ -569,3 +560,29 @@ eof # Believe it or not, I broke root directory globbing in rc-1.6b1. x=/* ~ '/*' $^x && fail root directory globbing + +# fn sigexit should be cleared in children + +x = () +expect nonesuch not found +x = `{true | nonesuch}; if (~ $x trip) fail sigexit in children +x = `{ < /dev/null wc |grep xxx }; if (~ $x trip) fail sigexit in children +x = `{{ wc | wc } < /dev/null }; if (~ $x trip) fail sigexit in children + +# core dumps in glob.c +~ () '*' && fail globber problem +~ () '**' && fail globber problem + +# check for ctrl-a bug +x=`{./tripping a} +# ~ `{$rc -c 'echo $x'} $x || fail ctrl-a bug detected + +# check for hilarious quoting bug introduced while fixing ctrl-a +x=('#' '#' '#') +eval z^`{whatis -v x} +~ $#zx 3 || fail hilarious quoting bug + +# parens bypass quote detector bug +fn x {echo x.y $(x.y)} +~ ``''{whatis -f x} 'fn x {echo x.y $''x.y''} +' || fail sneaky parens bug diff --git a/tripping.c b/tripping.c @@ -3,14 +3,18 @@ #include <fcntl.h> #include <stdio.h> -void out0(void) { +static void out0(void) { putchar('t'); putchar('r'); putchar('\0'); putchar('u'); putchar('e'); putchar('\n'); } -void makenonblock(void) { +static void ctrl_a(void) { + puts("a\001ab\002b"); +} + +static void makenonblock(void) { int flags; if ((flags = fcntl(0, F_GETFL)) == -1) @@ -25,6 +29,9 @@ int main(int argc, char **argv) { case '0': out0(); break; + case 'a': + ctrl_a(); + break; case 'n': makenonblock(); break; diff --git a/utils.c b/utils.c @@ -57,7 +57,7 @@ extern int n2u(char *s, unsigned int base) { /* The last word in portable ANSI: a strcmp wrapper for qsort */ extern int starstrcmp(const void *s1, const void *s2) { - return strcmp(*(char **)s1, *(char **)s2); + return strcmp(*(char * const *)s1, *(char * const *)s2); } /* tests to see if pathname begins with "/", "./", or "../" */ diff --git a/var.c b/var.c @@ -107,7 +107,7 @@ extern char *varlookup_string(char *name) { return look->extdef; if (look->def == NULL) return NULL; - return look->extdef = mprint("%F=%-L", name, look->def, "\001"); + return look->extdef = mprint("%F=%W", name, look->def); } /* remove a variable from the symtab. "stack" determines whether a level of scoping is popped or not */ diff --git a/walk.c b/walk.c @@ -33,7 +33,7 @@ top: sigchk(); switch (n->type) { case nArgs: case nBackq: case nConcat: case nCount: case nFlat: case nLappend: case nRedir: case nVar: - case nVarsub: case nWord: case nQword: + case nVarsub: case nWord: exec(glob(glom(n)), parent); /* simple command */ break; case nBody: @@ -217,6 +217,7 @@ top: sigchk(); if (n->u[0].p->type == nRedir || n->u[0].p->type == nDup) { if (redirq == NULL && !dofork(parent)) /* subshell on first preredir */ break; + setsigdefaults(FALSE); qredir(n->u[0].p); if (!haspreredir(n->u[1].p)) doredirs(); /* no more preredirs, empty queue */ @@ -246,6 +247,7 @@ top: sigchk(); if (n->u[1].p == NULL) { WALK(n->u[0].p, parent); } else if (dofork(parent)) { + setsigdefaults(FALSE); walk(n->u[1].p, TRUE); /* Do redirections */ redirq = NULL; /* Reset redirection queue */ walk(n->u[0].p, FALSE); /* Do commands */ @@ -321,6 +323,7 @@ static void dopipe(Node *n) { rc_error(NULL); } if ((pid = rc_fork()) == 0) { + setsigdefaults(FALSE); redirq = NULL; /* clear preredir queue */ mvfd(p[0], r->u[1].i); if (fd_prev != 1) @@ -337,6 +340,7 @@ static void dopipe(Node *n) { close(p[0]); } if ((pid = rc_fork()) == 0) { + setsigdefaults(FALSE); mvfd(fd_prev, fd_out); walk(r, FALSE); exit(getstatus());