v15i020: Tools to create and unpack shell archives, Part03/03
Rich Salz
rsalz at uunet.uu.net
Sat May 28 04:21:16 AEST 1988
Submitted-by: Rich Salz <rsalz at uunet.uu.net>
Posting-number: Volume 15, Issue 20
Archive-name: cshar/part03
#! /bin/sh
# This is a shell archive. Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file". To overwrite existing
# files, type "sh file -c". You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g.. If this archive is complete, you
# will see the following message at the end:
# "End of archive 3 (of 3)."
# Contents: parser.c
# Wrapped by rsalz at fig.bbn.com on Fri May 27 14:15:09 1988
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'parser.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'parser.c'\"
else
echo shar: Extracting \"'parser.c'\" \(24206 characters\)
sed "s/^X//" >'parser.c' <<'END_OF_FILE'
X/*
X** An interpreter that can unpack many /bin/sh shell archives.
X** This program should really be split up into a couple of smaller
X** files; it started with Argify and SynTable as a cute ten-minute
X** hack and it just grew.
X**
X** Also, note that (void) casts abound, and that every command goes
X** to some trouble to return a value. That's because I decided
X** not to implement $? "properly."
X*/
X#include "shar.h"
X#ifdef RCSID
static char RCS[] =
X "$Header: parser.c,v 2.0 88/05/27 13:27:39 rsalz Exp $";
X#endif /* RCSID */
X
X
X/*
X** Manifest constants, handy shorthands.
X*/
X
X/* Character classes used in the syntax table. */
X#define C_LETR 1 /* A letter within a word */
X#define C_WHIT 2 /* Whitespace to separate words */
X#define C_WORD 3 /* A single-character word */
X#define C_DUBL 4 /* Something like <<, e.g. */
X#define C_QUOT 5 /* Quotes to group a word */
X#define C_META 6 /* Heavy magic character */
X#define C_TERM 7 /* Line terminator */
X
X/* Macros used to query character class. */
X#define ISletr(c) (SynTable[(c)] == C_LETR)
X#define ISwhit(c) (SynTable[(c)] == C_WHIT)
X#define ISword(c) (SynTable[(c)] == C_WORD)
X#define ISdubl(c) (SynTable[(c)] == C_DUBL)
X#define ISquot(c) (SynTable[(c)] == C_QUOT)
X#define ISmeta(c) (SynTable[(c)] == C_META)
X#define ISterm(c) (SynTable[(c)] == C_TERM)
X
X
X/*
X** Data types
X*/
X
X/* Command dispatch table. */
typedef struct {
X char Name[10]; /* Text of command name */
X int (*Func)(); /* Function that implements it */
X} COMTAB;
X
X/* A shell variable. We only have a few of these. */
typedef struct {
X char *Name;
X char *Value;
X} VAR;
X
X
X/*
X** Global variables.
X*/
X
XFILE *Input; /* Current input stream */
char *File; /* Input filename */
int Interactive; /* isatty(fileno(stdin))? */
X#ifdef MSDOS
jmp_buf jEnv; /* Pop out of main loop */
X#endif MSDOS
X
extern COMTAB Dispatch[]; /* Defined below... */
static VAR VarList[MAX_VARS]; /* Our list of variables */
static char Text[BUFSIZ]; /* Current text line */
static int LineNum = 1; /* Current line number */
static int Running = TRUE; /* Working, or skipping? */
static short SynTable[256] = { /* Syntax table */
X /* \0 001 002 003 004 005 006 007 */
X C_TERM, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT,
X /* \h \t \n 013 \f \r 016 017 */
X C_WHIT, C_WHIT, C_TERM, C_WHIT, C_TERM, C_TERM, C_WHIT, C_WHIT,
X /* 020 021 022 023 024 025 026 027 */
X C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT,
X /* can em sub esc fs gs rs us */
X C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT, C_WHIT,
X
X /* sp ! " # $ % & ' */
X C_WHIT, C_LETR, C_QUOT, C_TERM, C_LETR, C_LETR, C_DUBL, C_QUOT,
X /* ( ) * + , - . / */
X C_WORD, C_WORD, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR,
X /* 0 1 2 3 4 5 6 7 */
X C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR,
X /* 8 9 : ; < = > ? */
X C_LETR, C_LETR, C_LETR, C_DUBL, C_DUBL, C_LETR, C_DUBL, C_LETR,
X
X /* @ A B C D E F G */
X C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR,
X /* H I J K L M N O */
X C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR,
X /* P Q R S T U V W */
X C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR,
X /* X Y Z [ \ ] ^ _ */
X C_LETR, C_LETR, C_LETR, C_LETR, C_META, C_LETR, C_LETR, C_LETR,
X
X /* ` a b c d e f g */
X C_WORD, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR,
X /* h i j k l m n o */
X C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR,
X /* p q r s t u v w */
X C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR, C_LETR,
X /* x y z { | } ~ del */
X C_LETR, C_LETR, C_LETR, C_LETR, C_DUBL, C_LETR, C_LETR, C_WHIT,
X};
X
X/**
X*** E R R O R R O U T I N E S
X**/
X
X
X/*
X** Print message with current line and line number.
X*/
static void
Note(text, arg)
X char *text;
X char *arg;
X{
X Fprintf(stderr, "\nIn line %d of %s:\n\t", LineNum, File);
X Fprintf(stderr, text, arg);
X Fprintf(stderr, "Current line:\n\t%s\n", Text);
X (void)fflush(stderr);
X}
X
X
X/*
X** Print syntax message and die.
X*/
void
SynErr(text)
X char *text;
X{
X Note("Fatal syntax error in %s statement.\n", text);
X exit(1);
X}
X
X/**
X*** I N P U T R O U T I N E S
X**/
X
X
X/*
X** Miniscule regular-expression matcher; only groks the . meta-character.
X*/
static int
Matches(p, text)
X REGISTER char *p;
X REGISTER char *text;
X{
X for (; *p && *text; text++, p++)
X if (*p != *text && *p != '.')
X return(FALSE);
X return(TRUE);
X}
X
X
X
X/*
X** Read input, possibly handling escaped returns. Returns a value so
X** we can do things like "while (GetLine(TRUE))", which is a hack. This
X** should also be split into two separate routines, and punt the Flag
X** argument, but so it goes.
X*/
int
GetLine(Flag)
X REGISTER int Flag;
X{
X REGISTER char *p;
X REGISTER char *q;
X char buf[LINE_SIZE];
X
X if (Interactive) {
X Fprintf(stderr, "Line %d%s> ", LineNum, Running ? "" : "(SKIP)");
X (void)fflush(stderr);
X }
X Text[0] = '\0';
X for (q = Text; fgets(buf, sizeof buf, Input); q += strlen(strcpy(q, buf))) {
X LineNum++;
X p = &buf[strlen(buf) - 1];
X if (*p != '\n') {
X Note("Input line too long.\n", (char *)NULL);
X exit(1);
X }
X if (!Flag || p == buf || p[-1] != '\\') {
X (void)strcpy(q, buf);
X return(1);
X }
X p[-1] = '\0';
X if (Interactive) {
X Fprintf(stderr, "PS2> ");
X (void)fflush(stderr);
X }
X }
X Note("RAN OUT OF INPUT.\n", (char *)NULL);
X exit(1);
X /* NOTREACHED */
X}
X
X
X/*
X** Copy a sub-string of characters into dynamic space.
X*/
static char *
CopyRange(Start, End)
X char *Start;
X char *End;
X{
X char *p;
X int i;
X
X i = End - Start + 1;
X p = strncpy(NEW(char, i + 1), Start, i);
X p[i] = '\0';
X return(p);
X}
X
X
X/*
X** Split a line up into shell-style "words."
X*/
int
Argify(ArgV)
X char **ArgV;
X{
X REGISTER char **av;
X REGISTER char *p;
X REGISTER char *q;
X
X for (av = ArgV, p = Text; *p; p++) {
X /* Skip whitespace, but treat "\ " as a letter. */
X for (; ISwhit(*p); p++)
X if (ISmeta(*p))
X p++;
X if (ISterm(*p))
X break;
X switch (SynTable[*p]) {
X default:
X Note("Bad case %x in Argify.\n", (char *)SynTable[*p]);
X /* FALLTHROUGH */
X case C_META:
X p++;
X /* FALLTHROUGH */
X case C_WHIT:
X case C_LETR:
X for (q = p; ISletr(*++q) || ISmeta(q[-1]); )
X ;
X *av++ = CopyRange(p, --q);
X p = q;
X break;
X case C_DUBL:
X if (*p == p[1]) {
X *av++ = CopyRange(p, p + 1);
X p++;
X break;
X }
X /* FALLTHROUGH */
X case C_WORD:
X *av++ = CopyRange(p, p);
X break;
X case C_QUOT:
X for (q = p; *++q; )
X if (*q == *p && !ISmeta(q[-1]))
X break;
X *av++ = CopyRange(p + 1, q - 1);
X p = q;
X break;
X }
X }
X *av = NULL;
X if (av > &ArgV[MAX_WORDS - 1])
X SynErr("TOO MANY WORDS IN LINE");
X return(av - ArgV);
X}
X
X/**
X*** V A R I A B L E R O U T I N E S
X**/
X
X
X/*
X** Return the value of a variable, or an empty string.
X*/
static char *
GetVar(Name)
X REGISTER char *Name;
X{
X REGISTER VAR *Vptr;
X
X for (Vptr = VarList; Vptr < &VarList[MAX_VARS]; Vptr++)
X if (EQ(Vptr->Name, Name))
X return(Vptr->Value);
X
X /* Try the environment. */
X return((Name = getenv(Name)) ? Name : "");
X}
X
X
X/*
X** Insert a variable/value pair into the list of variables.
X*/
void
SetVar(Name, Value)
X REGISTER char *Name;
X REGISTER char *Value;
X{
X REGISTER VAR *Vptr;
X REGISTER VAR *FreeVar;
X
X /* Skip leading whitespace in variable names, sorry... */
X while (ISwhit(*Name))
X Name++;
X
X /* Try to find the variable in the table. */
X for (Vptr = VarList, FreeVar = NULL; Vptr < &VarList[MAX_VARS]; Vptr++)
X if (Vptr->Name) {
X if (EQ(Vptr->Name, Name)) {
X free(Vptr->Value);
X Vptr->Value = COPY(Value);
X return;
X }
X }
X else if (FreeVar == NULL)
X FreeVar = Vptr;
X
X if (FreeVar == NULL) {
X Fprintf(stderr, "Overflow, can't do '%s=%s'\n", Name, Value);
X SynErr("ASSIGNMENT");
X }
X FreeVar->Name = COPY(Name);
X FreeVar->Value = COPY(Value);
X}
X
X
X/*
X** Expand variable references inside a word that are of the form:
X** foo${var}bar
X** foo$$bar
X** Returns a pointer to a static area which is overwritten every
X** other time it is called, so that we can do EQ(Expand(a), Expand(b)).
X*/
static char *
XExpand(p)
X REGISTER char *p;
X{
X static char buff[2][VAR_VALUE_SIZE];
X static int Flag;
X REGISTER char *q;
X REGISTER char *n;
X REGISTER char Closer;
X char name[VAR_NAME_SIZE];
X
X /* This is a hack, but it makes things easier in DoTEST, q.v. */
X if (p == NULL)
X return(p);
X
X /* Pick the "other" buffer then loop over the string to be expanded. */
X for (Flag = 1 - Flag, q = buff[Flag]; *p; )
X if (*p == '$')
X if (*++p == '$') {
X (void)sprintf(name, "%d", Pid());
X q += strlen(strcpy(q, name));
X p++;
X }
X else if (*p == '?') {
X /* Fake it -- all commands always succeed, here. */
X *q++ = '0';
X *q = '\0';
X p++;
X }
X else {
X Closer = (*p == '{') ? *p++ : '\0';
X for (n = name; *p && *p != Closer; )
X *n++ = *p++;
X if (*p)
X p++;
X *n = '\0';
X q += strlen(strcpy(q, GetVar(name)));
X }
X else
X *q++ = *p++;
X *q = '\0';
X return(buff[Flag]);
X}
X
X
X/*
X** Do a variable assignment of the form:
X** var=value
X** var="quoted value"
X** var="...${var}..."
X** etc.
X*/
static void
DoASSIGN(Name)
X REGISTER char *Name;
X{
X REGISTER char *Value;
X REGISTER char *q;
X REGISTER char Quote;
X
X /* Split out into name:value strings, and deal with quoted values. */
X Value = IDX(Name, '=');
X *Value = '\0';
X if (ISquot(*++Value))
X for (Quote = *Value++, q = Value; *++q && *q != Quote; )
X ;
X else
X for (q = Value; ISletr(*q); q++)
X ;
X *q = '\0';
X
X SetVar(Name, Expand(Value));
X}
X
X/**
X*** " O U T P U T " C O M M A N D S
X**/
X
X
X/*
X** Do a cat command. Understands the following:
X** cat >arg1 <<arg2
X** cat >>arg1 <<arg2
X** cat >>arg1 /dev/null
X** Except that arg2 is assumed to be quoted -- i.e., no expansion of meta-chars
X** inside the "here" document is done. The IO redirection can be in any order.
X*/
X/* ARGSUSED */
static int
DoCAT(ac, av)
X int ac;
X REGISTER char *av[];
X{
X REGISTER FILE *Out;
X REGISTER char *Ending;
X REGISTER char *Source;
X REGISTER int V;
X REGISTER int l;
X
X /* Parse the I/O redirecions. */
X for (V = TRUE, Source = NULL, Out = NULL, Ending = NULL; *++av; )
X if (EQ(*av, ">") && av[1]) {
X av++;
X /* This is a hack, but maybe MS-DOS doesn't have /dev/null? */
X Out = Running ? fopen(Expand(*av), "w") : stderr;
X }
X else if (EQ(*av, ">>") && av[1]) {
X av++;
X /* And besides, things are actually faster this way. */
X Out = Running ? fopen(Expand(*av), "a") : stderr;
X }
X else if (EQ(*av, "<<") && av[1]) {
X for (Ending = *++av; *Ending == '\\'; Ending++)
X ;
X l = strlen(Ending);
X }
X else if (!EQ(Source = *av, "/dev/null"))
X SynErr("CAT (bad input filename)");
X
X if (Out == NULL || (Ending == NULL && Source == NULL)) {
X Note("Missing parameter in CAT command.\n", (char *)NULL);
X V = FALSE;
X }
X
X /* Read the input, spit it out. */
X if (V && Running && Out != stderr) {
X if (Source == NULL)
X while (GetLine(FALSE) && !EQn(Text, Ending, l))
X (void)fputs(Text, Out);
X (void)fclose(Out);
X }
X else
X while (GetLine(FALSE) && !EQn(Text, Ending, l))
X ;
X
X return(V);
X}
X
X
X/*
X** Do a SED command. Understands the following:
X** sed sX^yyyyXX >arg1 <<arg2
X** sed -e sX^yyyyXX >arg1 <<arg2
X** Where the yyyy is a miniscule regular expression; see Matches(), above.
X** The "X" can be any single character and the ^ is optional (sigh). No
X** shell expansion is done inside the "here' document. The IO redirection
X** can be in any order.
X*/
X/* ARGSUSED */
static int
DoSED(ac, av)
X int ac;
X REGISTER char *av[];
X{
X REGISTER FILE *Out;
X REGISTER char *Pattern;
X REGISTER char *Ending;
X REGISTER char *p;
X REGISTER int V;
X REGISTER int l;
X REGISTER int i;
X
X /* Parse IO redirection stuff. */
X for (V = TRUE, Out = NULL, Pattern = NULL, Ending = NULL; *++av; )
X if (EQ(*av, ">") && av[1]) {
X av++;
X Out = Running ? fopen(Expand(*av), "w") : stderr;
X }
X else if (EQ(*av, ">>") && av[1]) {
X av++;
X Out = Running ? fopen(Expand(*av), "a") : stderr;
X }
X else if (EQ(*av, "<<") && av[1]) {
X for (Ending = *++av; *Ending == '\\'; Ending++)
X ;
X l = strlen(Ending);
X }
X else
X Pattern = EQ(*av, "-e") && av[1] ? *++av : *av;
X
X /* All there? */
X if (Out == NULL || Ending == NULL || Pattern == NULL) {
X Note("Missing parameter in SED command.\n", (char *)NULL);
X V = FALSE;
X }
X
X /* Parse the substitute command and its pattern. */
X if (*Pattern != 's') {
X Note("Bad SED command -- not a substitute.\n", (char *)NULL);
X V = FALSE;
X }
X else {
X Pattern++;
X p = Pattern + strlen(Pattern) - 1;
X if (*p != *Pattern || *--p != *Pattern) {
X Note("Bad substitute pattern in SED command.\n", (char *)NULL);
X V = FALSE;
X }
X else {
X /* Now check the pattern. */
X if (*++Pattern == '^')
X Pattern++;
X for (*p = '\0', i = strlen(Pattern), p = Pattern; *p; p++)
X if (*p == '[' || *p == '*' || *p == '$') {
X Note("Bad meta-character in SED pattern.\n", (char *)NULL);
X V = FALSE;
X }
X }
X }
X
X /* Spit out the input. */
X if (V && Running && Out != stderr) {
X while (GetLine(FALSE) && !EQn(Text, Ending, l))
X (void)fputs(Matches(Pattern, Text) ? &Text[i] : Text, Out);
X (void)fclose(Out);
X }
X else
X while (GetLine(FALSE) && !EQn(Text, Ending, l))
X ;
X
X return(V);
X}
X
X/**
X*** " S I M P L E " C O M M A N D S
X**/
X
X
X/*
X** Parse a cp command of the form:
X** cp /dev/null arg
X** We should check if "arg" is a safe file to clobber, but...
X*/
static int
DoCP(ac, av)
X int ac;
X char *av[];
X{
X FILE *F;
X
X if (Running) {
X if (ac != 3 || !EQ(av[1], "/dev/null"))
X SynErr("CP");
X if (F = fopen(Expand(av[2]), "w")) {
X (void)fclose(F);
X return(TRUE);
X }
X Note("Can't create %s.\n", av[2]);
X }
X return(FALSE);
X}
X
X
X/*
X** Do a mkdir command of the form:
X** mkdir arg
X*/
static int
DoMKDIR(ac, av)
X int ac;
X char *av[];
X{
X if (Running) {
X if (ac != 2)
X SynErr("MKDIR");
X if (mkdir(Expand(av[1]), 0777) >= 0)
X return(TRUE);
X Note("Can't make directory %s.\n", av[1]);
X }
X return(FALSE);
X}
X
X
X/*
X** Do a cd command of the form:
X** cd arg
X** chdir arg
X*/
static int
DoCD(ac, av)
X int ac;
X char *av[];
X{
X if (Running) {
X if (ac != 2)
X SynErr("CD");
X if (chdir(Expand(av[1])) >= 0)
X return(TRUE);
X Note("Can't cd to %s.\n", av[1]);
X }
X return(FALSE);
X}
X
X
X/*
X** Do the echo command. Understands the "-n" hack.
X*/
X/* ARGSUSED */
static int
DoECHO(ac, av)
X int ac;
X char *av[];
X{
X int Flag;
X
X if (Running) {
X if (Flag = av[1] != NULL && EQ(av[1], "-n"))
X av++;
X while (*++av)
X Fprintf(stderr, "%s ", Expand(*av));
X if (!Flag)
X Fprintf(stderr, "\n");
X (void)fflush(stderr);
X }
X return(TRUE);
X}
X
X
X/*
X** Generic "handler" for commands we can't do.
X*/
static int
DoIT(ac, av)
X int ac;
X char *av[];
X{
X if (Running)
X Fprintf(stderr, "You'll have to do this yourself:\n\t%s ", *av);
X return(DoECHO(ac, av));
X}
X
X
X/*
X** Do an EXIT command.
X*/
static int
DoEXIT(ac, av)
X int ac;
X char *av[];
X{
X ac = *++av ? atoi(Expand(*av)) : 0;
X Fprintf(stderr, "Exiting, with status %d\n", ac);
X#ifdef MSDOS
X longjmp(jEnv, 1);
X#endif /* MSDOS */
X return(ac);
X}
X
X
X/*
X** Do an EXPORT command. Often used to make sure the archive is being
X** unpacked with the Bourne (or Korn?) shell. We look for:
X** export PATH blah blah blah
X*/
static int
DoEXPORT(ac, av)
X int ac;
X char *av[];
X{
X if (ac < 2 || !EQ(av[1], "PATH"))
X SynErr("EXPORT");
X return(TRUE);
X}
X
X/**
X*** F L O W - O F - C O N T R O L C O M M A N D S
X**/
X
X
X/*
X** Parse a "test" statement. Returns TRUE or FALSE. Understands the
X** following tests:
X** test {!} -f arg Is arg {not} a plain file?
X** test {!} -d arg Is arg {not} a directory?
X** test {!} $var -eq $var Is the variable {not} equal to the variable?
X** test {!} $var != $var Is the variable {not} equal to the variable?
X** test {!} ddd -ne `wc -c {<} arg`
X** Is size of arg {not} equal to ddd in bytes?
X** test -f arg -a $var -eq val
X** Used by my shar, check for file clobbering
X** These last two tests are starting to really push the limits of what is
X** reasonable to hard-code, but they are common cliches in shell archive
X** "programming." We also understand the [ .... ] way of writing test.
X** If we can't parse the test, we show the command and ask the luser.
X*/
static int
DoTEST(ac, av)
X REGISTER int ac;
X REGISTER char *av[];
X{
X REGISTER char **p;
X REGISTER char *Name;
X REGISTER FILE *DEVTTY;
X REGISTER int V;
X REGISTER int i;
X char buff[LINE_SIZE];
X
X /* Quick test. */
X if (!Running)
X return(FALSE);
X
X /* See if we're called as "[ ..... ]" */
X if (EQ(*av, "[")) {
X for (i = 1; av[i] && !EQ(av[i], "]"); i++)
X ;
X free(av[i]);
X av[i] = NULL;
X ac--;
X }
X
X /* Ignore the "test" argument. */
X av++;
X ac--;
X
X /* Inverted test? */
X if (EQ(*av, "!")) {
X V = FALSE;
X av++;
X ac--;
X }
X else
X V = TRUE;
X
X /* Testing for file-ness? */
X if (ac == 2 && EQ(av[0], "-f") && (Name = Expand(av[1])))
X return(GetStat(Name) && Ftype(Name) == F_FILE ? V : !V);
X
X /* Testing for directory-ness? */
X if (ac == 2 && EQ(av[0], "-d") && (Name = Expand(av[1])))
X return(GetStat(Name) && Ftype(Name) == F_DIR ? V : !V);
X
X /* Testing a variable's value? */
X if (ac == 3 && (EQ(av[1], "-eq") || EQ(av[1], "=")))
X return(EQ(Expand(av[0]), Expand(av[2])) ? V : !V);
X if (ac == 3 && (EQ(av[1], "-ne") || EQ(av[1], "!=")))
X return(!EQ(Expand(av[0]), Expand(av[2])) ? V : !V);
X
X /* Testing a file's size? */
X if (ac == (av[5] && EQ(av[5], "<") ? 8 : 7) && isdigit(av[0][0])
X && (EQ(av[1], "-ne") || EQ(av[1], "-eq"))
X && EQ(av[2], "`") && EQ(av[3], "wc")
X && EQ(av[4], "-c") && EQ(av[ac - 1], "`")) {
X if (GetStat(av[ac - 2])) {
X if (EQ(av[1], "-ne"))
X return(Fsize(av[ac - 2]) != atol(av[0]) ? V : !V);
X return(Fsize(av[ac - 2]) == atol(av[0]) ? V : !V);
X }
X Note("Can't get status of %s.\n", av[ac - 2]);
X }
X
X /* Testing for existing, but can clobber? */
X if (ac == 6 && EQ(av[0], "-f") && EQ(av[2], "-a")
X && (EQ(av[4], "!=") || EQ(av[4], "-ne")))
X return(GetStat(Name = Expand(av[1])) && Ftype(Name) == F_FILE
X && EQ(Expand(av[3]), Expand(av[5])) ? !V : V);
X
X /* I give up -- print it out, and let's ask Mikey, he can do it... */
X Fprintf(stderr, "Can't parse this test:\n\t");
X for (i = FALSE, p = av; *p; p++) {
X Fprintf(stderr, "%s ", *p);
X if (p[0][0] == '$')
X i = TRUE;
X }
X if (i) {
X Fprintf(stderr, "\n(Here it is with shell variables expanded...)\n\t");
X for (p = av; *p; p++)
X Fprintf(stderr, "%s ", Expand(*p));
X }
X Fprintf(stderr, "\n");
X
X DEVTTY = fopen(THE_TTY, "r");
X do {
X Fprintf(stderr, "Is value true/false/quit [tfq] (q): ");
X (void)fflush(stderr);
X clearerr(DEVTTY);
X if (fgets(buff, sizeof buff, DEVTTY) == NULL
X || buff[0] == 'q' || buff[0] == 'Q' || buff[0] == '\n')
X SynErr("TEST");
X if (buff[0] == 't' || buff[0] == 'T') {
X (void)fclose(DEVTTY);
X return(TRUE);
X }
X } while (buff[0] != 'f' && buff[0] != 'F');
X (void)fclose(DEVTTY);
X return(FALSE);
X}
X
X
X/*
X** Do an IF statement.
X*/
static int
DoIF(ac, av)
X REGISTER int ac;
X REGISTER char *av[];
X{
X REGISTER char **p;
X REGISTER int Flag;
X char *vec[MAX_WORDS];
X char **Pushed;
X
X /* Skip first argument. */
X if (!EQ(*++av, "[") && !EQ(*av, "test"))
X SynErr("IF");
X ac--;
X
X /* Look for " ; then " on this line, or "then" on next line. */
X for (Pushed = NULL, p = av; *p; p++)
X if (Flag = EQ(*p, ";")) {
X if (p[1] == NULL || !EQ(p[1], "then"))
X SynErr("IF");
X *p = NULL;
X ac -= 2;
X break;
X }
X if (!Flag) {
X (void)GetLine(TRUE);
X if (Argify(vec) > 1)
X Pushed = &vec[1];
X if (!EQ(vec[0], "then"))
X SynErr("IF (missing THEN)");
X }
X
X if (DoTEST(ac, av)) {
X if (Pushed)
X (void)Exec(Pushed);
X while (GetLine(TRUE)) {
X if ((ac = Argify(vec)) == 1 && EQ(vec[0], "fi"))
X break;
X if (EQ(vec[0], "else")) {
X DoUntil("fi", FALSE);
X break;
X }
X (void)Exec(vec);
X }
X }
X else
X while (GetLine(TRUE)) {
X if ((ac = Argify(vec)) == 1 && EQ(vec[0], "fi"))
X break;
X if (EQ(vec[0], "else")) {
X if (ac > 1)
X (void)Exec(&vec[1]);
X DoUntil("fi", Running);
X break;
X }
X }
X return(TRUE);
X}
X
X
X/*
X** Do a FOR statement.
X*/
static int
DoFOR(ac, av)
X REGISTER int ac;
X REGISTER char *av[];
X{
X REGISTER char *Var;
X REGISTER char **Values;
X REGISTER int Found;
X long Here;
X char *vec[MAX_WORDS];
X
X /* Check usage, get variable name and eat noise words. */
X if (ac < 4 || !EQ(av[2], "in"))
X SynErr("FOR");
X Var = av[1];
X ac -= 3;
X av += 3;
X
X /* Look for "; do" on this line, or just "do" on next line. */
X for (Values = av; *++av; )
X if (Found = EQ(*av, ";")) {
X if (av[1] == NULL || !EQ(av[1], "do"))
X SynErr("FOR");
X *av = NULL;
X break;
X }
X if (!Found) {
X (void)GetLine(TRUE);
X if (Argify(vec) != 1 || !EQ(vec[0], "do"))
X SynErr("FOR (missing DO)");
X }
X
X for (Here = ftell(Input); *Values; ) {
X SetVar(Var, *Values);
X DoUntil("done", Running);
X ;
X /* If we're not Running, only go through the loop once. */
X if (!Running)
X break;
X if (*++Values && (fseek(Input, Here, 0) < 0 || ftell(Input) != Here))
X SynErr("FOR (can't seek back)");
X }
X
X return(TRUE);
X}
X
X
X/*
X** Do a CASE statement of the form:
X** case $var in
X** text1)
X** ...
X** ;;
X** esac
X** Where text1 is a simple word or an asterisk.
X*/
static int
DoCASE(ac, av)
X REGISTER int ac;
X REGISTER char *av[];
X{
X REGISTER int FoundIt;
X char *vec[MAX_WORDS];
X char Value[VAR_VALUE_SIZE];
X
X if (ac != 3 || !EQ(av[2], "in"))
X SynErr("CASE");
X (void)strcpy(Value, Expand(av[1]));
X
X for (FoundIt = FALSE; GetLine(TRUE); ) {
X ac = Argify(vec);
X if (EQ(vec[0], "esac"))
X break;
X /* This is for vi: (-; sigh. */
X if (ac != 2 || !EQ(vec[1], ")"))
X SynErr("CASE");
X if (!FoundIt && (EQ(vec[0], Value) || EQ(vec[0], "*"))) {
X FoundIt = TRUE;
X if (Running && ac > 2)
X (void)Exec(&vec[2]);
X DoUntil(";;", Running);
X }
X else
X DoUntil(";;", FALSE);
X }
X return(TRUE);
X}
X
X
X
X/*
X** Dispatch table of known commands.
X*/
static COMTAB Dispatch[] = {
X { "cat", DoCAT },
X { "case", DoCASE },
X { "cd", DoCD },
X { "chdir", DoCD },
X { "chmod", DoIT },
X { "cp", DoCP },
X { "echo", DoECHO },
X { "exit", DoEXIT },
X { "export", DoEXPORT },
X { "for", DoFOR },
X { "if", DoIF },
X { "mkdir", DoMKDIR },
X { "rm", DoIT },
X { "sed", DoSED },
X { "test", DoTEST },
X { "[", DoTEST },
X { ":", DoIT },
X { "", NULL }
X};
X
X
X/*
X** Dispatch on a parsed line.
X*/
int
XExec(av)
X REGISTER char *av[];
X{
X REGISTER int i;
X REGISTER COMTAB *p;
X
X /* We have to re-calculate this because our callers can't always
X pass the count down to us easily. */
X for (i = 0; av[i]; i++)
X ;
X if (i) {
X /* Is this a command we know? */
X for (p = Dispatch; p->Func; p++)
X if (EQ(av[0], p->Name)) {
X i = (*p->Func)(i, av);
X if (p->Func == DoEXIT)
X /* Sigh; this is a hack. */
X return(-FALSE);
X break;
X }
X
X /* If not a command, try it as a variable assignment. */
X if (p->Func == NULL)
X /* Yes, we look for "=" in the first word, but pass down
X the whole line. */
X if (IDX(av[0], '='))
X DoASSIGN(Text);
X else
X Note("Command %s unknown.\n", av[0]);
X
X /* Free the line. */
X for (i = 0; av[i]; i++)
X free(av[i]);
X }
X return(TRUE);
X}
X
X
X/*
X** Do until we reach a specific terminator.
X*/
static
DoUntil(Terminator, NewVal)
X char *Terminator;
X int NewVal;
X{
X char *av[MAX_WORDS];
X int OldVal;
X
X for (OldVal = Running, Running = NewVal; GetLine(TRUE); )
X if (Argify(av)) {
X if (EQ(av[0], Terminator))
X break;
X (void)Exec(av);
X }
X
X Running = OldVal;
X}
END_OF_FILE
if test 24206 -ne `wc -c <'parser.c'`; then
echo shar: \"'parser.c'\" unpacked with wrong size!
fi
# end of 'parser.c'
fi
echo shar: End of archive 3 \(of 3\).
cp /dev/null ark3isdone
MISSING=""
for I in 1 2 3 ; do
if test ! -f ark${I}isdone ; then
MISSING="${MISSING} ${I}"
fi
done
if test "${MISSING}" = "" ; then
echo You have unpacked all 3 archives.
echo "See the README"
rm -f ark[1-9]isdone
else
echo You still need to unpack the following archives:
echo " " ${MISSING}
fi
## End of shell archive.
exit 0
--
Please send comp.sources.unix-related mail to rsalz at uunet.uu.net.
More information about the Comp.sources.unix
mailing list