rsalz@uunet.uu.net (Rich Salz) (05/31/89)
Submitted-by: ka@june.cs.washington.edu (Kenneth Almquist) Posting-number: Volume 19, Issue 8 Archive-name: ash/part08 # This is part 8 of ash. To unpack, feed it into the shell (not csh). # The ash distribution consists of eight pieces. Be sure you get them all. # After you unpack everything, read the file README. if test ! -d bltin then mkdir bltin fi echo extracting bltin/binary_op cat > bltin/binary_op <<\EOF # List of binary operators used by test/expr. # # Copyright 1989 by Kenneth Almquist. All rights reserved. # This file is part of ash, which is distributed under the terms specified # by the Ash General Public License. See the file named LICENSE. OR1 -o 1 OR2 | 1 AND1 -a 2 AND2 & 2 STREQ = 4 OP_STRING STRNE != 4 OP_STRING EQ -eq 4 OP_INT NE -ne 4 OP_INT GT -gt 4 OP_INT LT -lt 4 OP_INT LE -le 4 OP_INT GE -ge 4 OP_INT PLUS + 5 OP_INT MINUS - 5 OP_INT TIMES * 6 OP_INT DIVIDE / 6 OP_INT REM % 6 OP_INT MATCHPAT : 7 OP_STRING EOF if test `wc -c < bltin/binary_op` -ne 588 then echo 'bltin/binary_op is the wrong size' fi echo extracting bltin/bltin.h cat > bltin/bltin.h <<\EOF /* * This file is included by programs which are optionally built into the * shell. If SHELL is defined, we try to map the standard UNIX library * routines to ash routines using defines. * * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. */ #include "../shell.h" #include "../mystring.h" #ifdef SHELL #include "../output.h" #define stdout out1 #define stderr out2 #define printf out1fmt #define putc(c, file) outc(c, file) #define putchar(c) out1c(c) #define fprintf outfmt #define fputs outstr #define fflush flushout #define INITARGS(argv) #else #undef NULL #include <stdio.h> #undef main #define INITARGS(argv) if ((commandname = argv[0]) == NULL) {fputs("Argc is zero\n", stderr); exit(2);} else #endif #ifdef __STDC__ pointer stalloc(int); void error(char *, ...); #else pointer stalloc(); void error(); #endif extern char *commandname; EOF if test `wc -c < bltin/bltin.h` -ne 1011 then echo 'bltin/bltin.h is the wrong size' fi echo extracting bltin/catf.1 cat > bltin/catf.1 <<\EOF .TH CATF 1 .SH NAME \" Copyright (C) 1989 by Kenneth Almquist. catf \- concatenate files .SH SYNOPSIS .B catf .I file... .SH COPYRIGHT .if n Copyright (C) 1989 by Kenneth Almquist. .if t Copyright \(co 1989 by Kenneth Almquist. .SH DESCRIPTION Catf copies the files specified as arguments to the standard output. As a special case, the file name ``-'' causes .I catf to read from the standard input. .SH "HISTORICAL NOTE" Early versions of UNIX had a program which was very similar to .IR catf . It was named .IR cat . EOF if test `wc -c < bltin/catf.1` -ne 521 then echo 'bltin/catf.1 is the wrong size' fi echo extracting bltin/catf.c cat > bltin/catf.c <<\EOF /* * Copy the files given as arguments to the standard output. The file * name "-" refers to the standard input. * * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. */ #define main catfcmd #include "bltin.h" #include "../error.h" #include <sys/param.h> #include <fcntl.h> #ifdef SBUFSIZE #define BUFSIZE() SBUFSIZE #else #ifdef MAXBSIZE #define BUFSIZE() MAXBSIZE #else #define BUFSIZE() BSIZE #endif #endif main(argc, argv) char **argv; { char *filename; char *buf = stalloc(BUFSIZE()); int fd; int i; #ifdef SHELL volatile int input; struct jmploc jmploc; struct jmploc *volatile savehandler; #endif INITARGS(argv); #ifdef SHELL input = -1; if (setjmp(jmploc.loc)) { close(input); handler = savehandler; longjmp(handler, 1); } savehandler = handler; handler = &jmploc; #endif while ((filename = *++argv) != NULL) { if (filename[0] == '-' && filename[1] == '\0') { fd = 0; } else { #ifdef SHELL INTOFF; if ((fd = open(filename, O_RDONLY)) < 0) error("Can't open %s", filename); input = fd; INTON; #else if ((fd = open(filename, O_RDONLY)) < 0) { fprintf(stderr, "catf: Can't open %s\n", filename); exit(2); } #endif } while ((i = read(fd, buf, BUFSIZE())) > 0) { #ifdef SHELL if (out1 == &memout) { register char *p; for (p = buf ; --i >= 0 ; p++) { outc(*p, &memout); } } else { write(1, buf, i); } #else write(1, buf, i); #endif } if (fd != 0) close(fd); } #ifdef SHELL handler = savehandler; #endif } EOF if test `wc -c < bltin/catf.c` -ne 1795 then echo 'bltin/catf.c is the wrong size' fi echo extracting bltin/echo.1 cat > bltin/echo.1 <<\EOF .TH ECHO 1 .SH NAME \" Copyright (C) 1989 by Kenneth Almquist. echo \- produce message in a shell script .SH SYNOPSIS .B echo [ .B -n | .B -e ] .I args... .SH COPYRIGHT .if n Copyright (C) 1989 by Kenneth Almquist. .if t Copyright \(co 1989 by Kenneth Almquist. .SH DESCRIPTION .I Echo prints its arguments on the standard output, separated by spaces. Unless the .B -n option is present, a newline is output following the arguments. The .B -e option causes .I echo to treat the escape sequences specially, as described in the following paragraph. The .B -e option is the default, and is provided solely for compatibility with other systems. Only one of the options .B -n and .B -e may be given. .PP If any of the following sequences of characters is encountered during output, the sequence is not output. Instead, the specified action is performed: .nr i 0.6i .de i .sp .ti -\\niu \\$1 \c .if \w'\\$1'-\\ni .br .. .in 1.1i .ta 0.6i .i \eb A backspace character is output. .i \ec Subsequent output is suppressed. This is normally used at the end of the last argument to suppress the trailing newline that .I echo would otherwise output. .i \ef Output a form feed. .i \en Output a newline character. .i \er Output a carriage return. .i \et Output a (horizontal) tab character. .i \ev Output a vertical tab. .i \e0\fIdigits\fR Output the character whose value is given by zero to three digits. If there are zero digits, a nul character is output. .i \e\e Output a backslash. .in -1.1i .SH HINTS Remember that backslash is special to the shell and needs to be escaped. To output a message to standard error, say .sp .ti +1i echo message >&2 .SH BUGS The octal character escape mechanism (\e0\fIdigits\fR) differs from the C language mechanism. .PP There is no way to force .I echo to treat its arguments literally, rather than interpreting them as options and escape sequences. EOF if test `wc -c < bltin/echo.1` -ne 1879 then echo 'bltin/echo.1 is the wrong size' fi echo extracting bltin/echo.c cat > bltin/echo.c <<\EOF /* * Echo command. * * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. */ #define main echocmd #include "bltin.h" #define eflag 1 main(argc, argv) char **argv; { register char **ap; register char *p; register char c; int count; int nflag = 0; #ifndef eflag int eflag = 0; #endif ap = argv; if (argc) ap++; if ((p = *ap) != NULL) { if (equal(p, "-n")) { nflag++; ap++; } else if (equal(p, "-e")) { #ifndef eflag eflag++; #endif ap++; } } while ((p = *ap++) != NULL) { while ((c = *p++) != '\0') { if (c == '\\' && eflag) { switch (*p++) { case 'b': c = '\b'; break; case 'c': return 0; /* exit */ case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case '\\': break; /* c = '\\' */ case '0': c = 0; count = 3; while (--count >= 0 && (unsigned)(*p - '0') < 8) c = (c << 3) + (*p++ - '0'); break; default: p--; break; } } putchar(c); } if (*ap) putchar(' '); } if (! nflag) putchar('\n'); return 0; } EOF if test `wc -c < bltin/echo.c` -ne 1419 then echo 'bltin/echo.c is the wrong size' fi echo extracting bltin/error.c cat > bltin/error.c <<\EOF /* * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. */ #include <stdio.h> char *commandname; void #ifdef __STDC__ error(char *msg, ...) { #else error(msg) char *msg; { #endif fprintf(stderr, "%s: %s\n", commandname, msg); exit(2); } EOF if test `wc -c < bltin/error.c` -ne 422 then echo 'bltin/error.c is the wrong size' fi echo extracting bltin/expr.1 cat > bltin/expr.1 <<\EOF .TH EXPR 1 .SH NAME \" Copyright (C) 1989 by Kenneth Almquist. expr, text \- evaluate expressions .SH SYNOPSIS .B expr .I expression .br .B test .I expression .br .B [ .I expression .B ] .SH COPYRIGHT .if n Copyright (C) 1989 by Kenneth Almquist. .if t Copyright \(co 1989 by Kenneth Almquist. .SH DESCRIPTION .I Expr evaluates the expression and prints the result. .I Test evaluates the expression without printing the result. The ``['' command is a synonym for .IR test ; when invoked under this name the last argument to .I expr must be a ``]'', which is deleted and not considered part of the expression. .PP Three data types may occur in the .IR expression : string, integer, and boolean. The rules for conversion are as follows: .sp .nr i 2 .ta \nii .in +\nii .ti -\nii \fIstring\fR->\fIinteger\fR Done via .IR atoi (3). .ti -\nii \fIinteger\fR->\fIstring\fR Convert to decimal reprentation. .ti -\nii \fIstring\fR->\fIboolean\fR "" -> false, everything else to true. .ti -\nii \fIboolean\fR->\fIstring\fR false -> "", true -> "true". .ti -\nii \fIinteger\fR->\fIboolean\fR 0 -> false, everything else to true. .ti -\nii \fIboolean\fR->\fIinteger\fR false -> 0, true -> 1. .in -\nii .PP Any argument to .I expr which is not a legal operator is treated as a string operand of type .IR string . If the operand is enclosed in single quotes, these are deleted. (Note that single quotes are stripped by the shell, so the single quotes must be quoted, typically by enclosing them in double quotes.) .PP As a special case, if .I expression is omitted, the result is false. .PP We now list the operators. The syntax .sp .ti +8 \fIinteger\fB op \fIinteger\fR -> \fIboolean\fB (3)\fR .sp means that \fBop\fR is a binary operator which takes operands of type \fIinteger\fR and produces a result of type \fIboolean\fR. The ``(3)'' means that the priority of \fBop\fR is 3. Operands are automaticly converted to the appropriate type. The type \fIany\fR is used for operator that take operands of any type. .nr p 1 .de b .HP 0.5i \fI\\$1\fB \\$2 \fI\\$3\fR -> \\fI\\$4\\fR (\\np) .br .. .de u .HP 0.5i \\$1 \fI\\$2\fR -> \\fI\\$3\\fR (\\np) .br .. .b any -o any any Returns the value of the left hand operand if the left hand operand would yield .I true if converted to type .IR boolean , and the value of the right hand operand otherwise. The right hand operand is evaluated only if necessary. ``|'' is a synonym for ``-o''. .nr p \np+1 .b any -a any any Returns the value of the left hand operand if the left hand operand would yield .I false if converted to type .IR boolean , and the value of the right hand operand otherwise. The right hand operand is evaluated only if necessary. ``&'' is a synonym for ``-a''. .nr p \np+1 .u ! boolean boolean Returns true if the operand is false, and false if the operand is true. .nr p \np+1 .b string = string boolean True if the two strings are equal. .b string != string boolean True if the two strings are not equal. .b integer -eq integer boolean True if the two operands are equal. .b integer -ne integer boolean True if the two operands are not equal. .b integer -gt integer boolean True if the first operand is greater than the second one. .b integer -lt integer boolean True if the first operand is less than the second one. .b integer -ge integer boolean True if the first operand is greater than or equal to the second one. .b integer -le integer boolean True if the first operand is less than or equal to the second one. .nr p \np+1 .b integer + integer integer Add two integers. .b integer - integer integer Subtract two integers. .nr p \np+1 .b integer * integer integer Multiply two integers. ``*'' is special to the shell, so you generally have to write this operator as ``\e*''. .b integer / integer integer Divide two integers. .b integer % integer integer Returns the remainder when the first operand is divided by the second one. .nr p \np+1 .b string : string "integer or string" The second operand is interpreted as a regular expression (as in the System V .I ed program). This operator attempts to match part (or all) of the first operand with the regular expression. The match must start at the beginning of the first operand. If the regular expression contains and \e( \e) pairs, then the result of this operator is the string which is matched by the regular expression between these pairs, or the null string if no match occurred. Otherwise, the result is the number of characters matched by the regular expression, or zero if no no match occurred. .nr p \np+1 .u -n string integer Returns the number of characters in the string. .u -z string boolean Returns true if the string contains zero characters. .u -t integer boolean Returns true if the specified file descriptor is associated with a tty. .PP The remaining operators all deal with files. Except as noted, they return false if the specified file does not exist. The ones dealing with permission use the effective user and group ids of the shell. .u -r string boolean True if you have read permission on the file. .u -w string boolean True if you have write permission on the file. .u -x string boolean True if you have execute permission on the file. .u -f string boolean True if the file is a regular file. .u -d string boolean True if the file is a directory. .u -c string boolean True if the file is a character special file. .u -b string boolean True if the file is a block special file. .u -p string boolean True if the file is a named pipe (i.e. a fifo). .u -u string boolean True if the file is setuid. .u -g string boolean True if the file is setgid. .u -k string boolean True if the file has the sticky bit set. .u -s string "integer or boolean" Returns the size of the file, or 0 if the file does not exist. .SH "EXIT CODE" 0 if the result of .I expression would be .I true if the result were converted to .IR boolean . .br 1 if the result of .I expression would be .I false if the result were converted to .IR boolean . .br 2 if .I expression is syntactically incorrect. .SH EXAMPLES .HP 0.5i filesize=`expr -s file` .br Sets the shell variable .I filesize to the size of .IR file . .HP 0.5i if [ -s file ]; then command; fi .br Execute .I command if .I file exists and is not empty. .HP 0.5i x=`expr "'$x'" : '.\{4\}\(.\{0,3\}\)'` .br Sets .I x to the substring of .I x beginning after the fourth character of .I x and continuing for three characters or until the end of the string, whichever comes first. .HP 0.5i x=`expr X"$x" : X'.\{4\}\(.\{0,3\}\)'` .br This example is the same as the previous one, but it uses a leading ``X'' rather than single quotes to make things work when the value of .I x looks like an operator. An alternative would be to write .HP 0.5i x=`expr "'$x'" : '.\{4\}\(.\{0,3\}\)'` .br This encloses the value of .I x in single quotes to keep it from being confused with an operator, which is simpler than the preceding example, but also less portable. .SH BUGS The relational operators of the System V .I expr command are not implemented. .PP Certain features of this version of .I expr are not present in System V, so care should be used when writing portable code. EOF if test `wc -c < bltin/expr.1` -ne 7136 then echo 'bltin/expr.1 is the wrong size' fi echo extracting bltin/expr.c cat > bltin/expr.c <<\EOF /* * The expr and test commands. * * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. */ #define main exprcmd #include "bltin.h" #include "operators.h" #include <sys/types.h> #include <sys/stat.h> #define STACKSIZE 12 #define NESTINCR 16 /* data types */ #define STRING 0 #define INTEGER 1 #define BOOLEAN 2 /* * This structure hold a value. The type keyword specifies the type of * the value, and the union u holds the value. The value of a boolean * is stored in u.num (1 = TRUE, 0 = FALSE). */ struct value { int type; union { char *string; long num; } u; }; struct operator { short op; /* which operator */ short pri; /* priority of operator */ }; struct filestat { char *name; /* name of file */ int rcode; /* return code from stat */ struct stat stat; /* status info on file */ }; extern char *match_begin[10]; /* matched string */ extern short match_length[10]; /* defined in regexp.c */ extern short number_parens; /* number of \( \) pairs */ #ifdef __STDC__ int expr_is_false(struct value *); void expr_operator(int, struct value *, struct filestat *); int lookup_op(char *, char *const*); char *re_compile(char *); /* defined in regexp.c */ int re_match(char *, char *); /* defined in regexp.c */ long atol(const char *); #else int expr_is_false(); void expr_operator(); int lookup_op(); char *re_compile(); /* defined in regexp.c */ int re_match(); /* defined in regexp.c */ long atol(); #endif main(argc, argv) char **argv; { char **ap; char *opname; char c; char *p; int print; int nest; /* parenthises nesting */ int op; int pri; int skipping; int binary; struct operator opstack[STACKSIZE]; struct operator *opsp; struct value valstack[STACKSIZE + 1]; struct value *valsp; struct filestat fs; INITARGS(argv); c = **argv; print = 1; if (c == 't') print = 0; else if (c == '[') { if (! equal(argv[argc - 1], "]")) error("missing ]"); argv[argc - 1] = NULL; print = 0; } ap = argv + 1; fs.name = NULL; /* * We use operator precedence parsing, evaluating the expression * as we parse it. Parentheses are handled by bumping up the * priority of operators using the variable "nest." We use the * variable "skipping" to turn off evaluation temporarily for the * short circuit boolean operators. (It is important do the short * circuit evaluation because under NFS a stat operation can take * infinitely long.) */ nest = 0; skipping = 0; opsp = opstack + STACKSIZE; valsp = valstack; if (*ap == NULL) { valstack[0].type = BOOLEAN; valstack[0].u.num = 0; goto done; } for (;;) { opname = *ap++; if (opname == NULL) syntax: error("syntax error"); if (opname[0] == '(' && opname[1] == '\0') { nest += NESTINCR; continue; } else if (*ap && (op = lookup_op(opname, unary_op)) >= 0) { if (opsp == &opstack[0]) overflow: error("Expression too complex"); --opsp; opsp->op = op; opsp->pri = op_priority[op] + nest; continue; } else { if (opname[0] == '\'') { for (p = opname ; *++p != '\0' ; ); if (--p > opname && *p == '\'') { *p = '\0'; opname++; } } valsp->type = STRING; valsp->u.string = opname; valsp++; } for (;;) { opname = *ap++; if (opname == NULL) { if (nest != 0) goto syntax; pri = 0; break; } if (opname[0] != ')' || opname[1] != '\0') { if ((op = lookup_op(opname, binary_op)) < 0) goto syntax; op += FIRST_BINARY_OP; pri = op_priority[op] + nest; break; } if ((nest -= NESTINCR) < 0) goto syntax; } while (opsp < &opstack[STACKSIZE] && opsp->pri >= pri) { binary = opsp->op; for (;;) { valsp--; c = op_argflag[opsp->op]; if (c == OP_INT) { if (valsp->type == STRING) valsp->u.num = atol(valsp->u.string); valsp->type = INTEGER; } else if (c >= OP_STRING) { /* OP_STRING or OP_FILE */ if (valsp->type == INTEGER) { p = stalloc(32); #ifdef SHELL fmtstr(p, 32, "%d", valsp->u.num); #else sprintf(p, "%d", valsp->u.num); #endif valsp->u.string = p; } else if (valsp->type == BOOLEAN) { if (valsp->u.num) valsp->u.string = "true"; else valsp->u.string = ""; } valsp->type = STRING; if (c == OP_FILE && (fs.name == NULL || ! equal(fs.name, valsp->u.string))) { fs.name = valsp->u.string; fs.rcode = stat(valsp->u.string, &fs.stat); } } if (binary < FIRST_BINARY_OP) break; binary = 0; } if (! skipping) expr_operator(opsp->op, valsp, &fs); else if (opsp->op == AND1 || opsp->op == OR1) skipping--; valsp++; /* push value */ opsp++; /* pop operator */ } if (opname == NULL) break; if (opsp == &opstack[0]) goto overflow; if (op == AND1 || op == AND2) { op = AND1; if (skipping || expr_is_false(valsp - 1)) skipping++; } if (op == OR1 || op == OR2) { op = OR1; if (skipping || ! expr_is_false(valsp - 1)) skipping++; } opsp--; opsp->op = op; opsp->pri = pri; } done: if (print) { if (valstack[0].type == STRING) printf("%s\n", valstack[0].u.string); else if (valstack[0].type == INTEGER) printf("%ld\n", valstack[0].u.num); else if (valstack[0].u.num != 0) printf("true\n"); } return expr_is_false(&valstack[0]); } int expr_is_false(val) struct value *val; { if (val->type == STRING) { if (val->u.string[0] == '\0') return 1; } else { /* INTEGER or BOOLEAN */ if (val->u.num == 0) return 1; } return 0; } /* * Execute an operator. Op is the operator. Sp is the stack pointer; * sp[0] refers to the first operand, sp[1] refers to the second operand * (if any), and the result is placed in sp[0]. The operands are converted * to the type expected by the operator before expr_operator is called. * Fs is a pointer to a structure which holds the value of the last call * to stat, to avoid repeated stat calls on the same file. */ void expr_operator(op, sp, fs) int op; struct value *sp; struct filestat *fs; { int i; switch (op) { case NOT: sp->u.num = expr_is_false(sp); sp->type = BOOLEAN; break; case ISREAD: i = 04; goto permission; case ISWRITE: i = 02; goto permission; case ISEXEC: i = 01; permission: if (fs->stat.st_uid == geteuid()) i <<= 6; else if (fs->stat.st_gid == getegid()) i <<= 3; goto filebit; /* true if (stat.st_mode & i) != 0 */ case ISFILE: i = S_IFREG; goto filetype; case ISDIR: i = S_IFDIR; goto filetype; case ISCHAR: i = S_IFCHR; goto filetype; case ISBLOCK: i = S_IFBLK; goto filetype; case ISFIFO: #ifdef S_IFIFO i = S_IFIFO; goto filetype; #else goto false; #endif filetype: if ((fs->stat.st_mode & S_IFMT) == i && fs->rcode >= 0) { true: sp->u.num = 1; } else { false: sp->u.num = 0; } sp->type = BOOLEAN; break; case ISSETUID: i = S_ISUID; goto filebit; case ISSETGID: i = S_ISGID; goto filebit; case ISSTICKY: i = S_ISVTX; filebit: if (fs->stat.st_mode & i && fs->rcode >= 0) goto true; goto false; case ISSIZE: sp->u.num = fs->rcode >= 0? fs->stat.st_size : 0L; sp->type = INTEGER; break; case ISTTY: sp->u.num = isatty(sp->u.num); sp->type = BOOLEAN; break; case NULSTR: if (sp->u.string[0] == '\0') goto true; goto false; case STRLEN: sp->u.num = strlen(sp->u.string); sp->type = INTEGER; break; case OR1: case AND1: /* * These operators are mostly handled by the parser. If we * get here it means that both operands were evaluated, so * the value is the value of the second operand. */ *sp = *(sp + 1); break; case STREQ: case STRNE: i = 0; if (equal(sp->u.string, (sp + 1)->u.string)) i++; if (op == STRNE) i = 1 - i; sp->u.num = i; sp->type = BOOLEAN; break; case EQ: if (sp->u.num == (sp + 1)->u.num) goto true; goto false; case NE: if (sp->u.num != (sp + 1)->u.num) goto true; goto false; case GT: if (sp->u.num > (sp + 1)->u.num) goto true; goto false; case LT: if (sp->u.num < (sp + 1)->u.num) goto true; goto false; case LE: if (sp->u.num <= (sp + 1)->u.num) goto true; goto false; case GE: if (sp->u.num >= (sp + 1)->u.num) goto true; goto false; case PLUS: sp->u.num += (sp + 1)->u.num; break; case MINUS: sp->u.num -= (sp + 1)->u.num; break; case TIMES: sp->u.num *= (sp + 1)->u.num; break; case DIVIDE: if ((sp + 1)->u.num == 0) error("Division by zero"); sp->u.num /= (sp + 1)->u.num; break; case REM: if ((sp + 1)->u.num == 0) error("Division by zero"); sp->u.num %= (sp + 1)->u.num; break; case MATCHPAT: { char *pat; pat = re_compile((sp + 1)->u.string); if (re_match(pat, sp->u.string)) { if (number_parens > 0) { sp->u.string = match_begin[1]; sp->u.string[match_length[1]] = '\0'; } else { sp->u.num = match_length[0]; sp->type = INTEGER; } } else { if (number_parens > 0) { sp->u.string[0] = '\0'; } else { sp->u.num = 0; sp->type = INTEGER; } } } break; } } int lookup_op(name, table) char *name; char *const*table; { register char *const*tp; register char const *p; char c = name[1]; for (tp = table ; (p = *tp) != NULL ; tp++) { if (p[1] == c && equal(p, name)) return tp - table; } return -1; } EOF if test `wc -c < bltin/expr.c` -ne 10535 then echo 'bltin/expr.c is the wrong size' fi echo extracting bltin/line.1 cat > bltin/line.1 <<\EOF .TH LINE 1 .SH NAME \" Copyright (C) 1989 by Kenneth Almquist. line \- read a line .SH SYNOPSIS .B line .SH COPYRIGHT .if n Copyright (C) 1989 by Kenneth Almquist. .if t Copyright \(co 1989 by Kenneth Almquist. .SH DESCRIPTION .I Line copies one line from its standard input to its standard output. If it encounters an end of file before reading a newline, it outputs a newline character and returns an exit status of 1. EOF if test `wc -c < bltin/line.1` -ne 423 then echo 'bltin/line.1 is the wrong size' fi echo extracting bltin/line.c cat > bltin/line.c <<\EOF /* * The line command. Reads one line from the standard input and writes it * to the standard output. * * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. */ #define main linecmd #include "bltin.h" main(argc, argv) char **argv; { char c; for (;;) { if (read(0, &c, 1) != 1) { putchar('\n'); return 1; } putchar(c); if (c == '\n') return 0; } } EOF if test `wc -c < bltin/line.c` -ne 562 then echo 'bltin/line.c is the wrong size' fi echo extracting bltin/makefile cat > bltin/makefile <<\EOF # Copyright (C) 1989 by Kenneth Almquist. All rights reserved. # This file is part of ash, which is distributed under the terms specified # by the Ash General Public License. See the file named LICENSE. LIBFILES=catfcmd.o echocmd.o exprcmd.o linecmd.o nlechocmd.o\ operators.o regexp.o DEBUG=-g CFLAGS=$(DEBUG) #CC=gcc all:$P bltinlib.a catf echo expr line nlecho true umask bltinlib.a:$P $(LIBFILES) ar rc $@ $(LIBFILES) catf: catf.c bltin.h ../shell.h ../error.h error.o stalloc.o $(CC) $(CFLAGS) -o $@ catf.c error.o stalloc.o catfcmd.o: catf.c bltin.h ../shell.h ../error.h $(CC) -DSHELL $(CFLAGS) -c catf.c mv catf.o $@ expr: expr.c bltin.h ../shell.h operators.h operators.o regexp.o error.o stalloc.o $(CC) $(CFLAGS) -o $@ expr.c operators.o regexp.o error.o stalloc.o -rm -f test '[' ln expr test ln expr '[' exprcmd.o: expr.c bltin.h ../shell.h operators.h $(CC) -DSHELL $(CFLAGS) -c expr.c mv expr.o $@ operators.c operators.h: unary_op binary_op mkexpr ./mkexpr operators.o: ../shell.h operators.h regexp.o: bltin.h ../shell.h echo: echo.c bltin.h ../shell.h $(CC) $(CFLAGS) -o $@ echo.c echocmd.o: echo.c bltin.h ../shell.h $(CC) -DSHELL $(CFLAGS) -c echo.c mv echo.o $@ line: line.c bltin.h ../shell.h $(CC) $(CFLAGS) -o $@ line.c linecmd.o: line.c bltin.h ../shell.h $(CC) -DSHELL $(CFLAGS) -c line.c mv line.o $@ nlecho: nlecho.c bltin.h ../shell.h $(CC) $(CFLAGS) -o $@ nlecho.c nlechocmd.o: nlecho.c bltin.h ../shell.h $(CC) -DSHELL $(CFLAGS) -c nlecho.c mv nlecho.o $@ umask: umask.c bltin.h $(CC) $(CFLAGS) -o $@ umask.c true: > : chmod 755 : rm -f true ln : true stalloc.o: ../shell.h EOF if test `wc -c < bltin/makefile` -ne 1653 then echo 'bltin/makefile is the wrong size' fi echo extracting bltin/mkexpr cat > bltin/mkexpr <<\EOF # Copyright 1989 by Kenneth Almquist. All rights reserved. # # This file is part of ash. Ash is distributed under the terms specified # by the Ash General Public License. See the file named LICENSE. exec > operators.h awk '/^[^#]/ {printf "#define %s %d\n", $1, n++}' unary_op binary_op awk '/^[^#]/ {n++} END {printf "\n#define FIRST_BINARY_OP %d\n", n} ' unary_op echo ' #define OP_INT 1 /* arguments to operator are integer */ #define OP_STRING 2 /* arguments to operator are string */ #define OP_FILE 3 /* argument is a file name */ extern char *const unary_op[]; extern char *const binary_op[]; extern const char op_priority[]; extern const char op_argflag[];' exec > operators.c echo '/* * Operators used in the expr/test command. */ #include "../shell.h" #include "operators.h" char *const unary_op[] = {' awk '/^[^#]/ {printf " \"%s\",\n", $2}' unary_op echo ' NULL }; char *const binary_op[] = {' awk '/^[^#]/ {printf " \"%s\",\n", $2}' binary_op echo ' NULL }; const char op_priority[] = {' awk '/^[^#]/ {printf " %s,\n", $3}' unary_op binary_op echo '}; const char op_argflag[] = {' awk '/^[^#]/ {if (length($4) > 0) printf " %s,\n", $4 else printf " 0,\n"} ' unary_op binary_op echo '};' EOF if test `wc -c < bltin/mkexpr` -ne 1256 then echo 'bltin/mkexpr is the wrong size' fi chmod 755 bltin/mkexpr echo extracting bltin/nlecho.1 cat > bltin/nlecho.1 <<\EOF .TH NLECHO 1 .SH NAME \" Copyright (C) 1989 by Kenneth Almquist. nlecho \- echo arguments, one per line .SH SYNOPSIS .B nlecho [ .I arg ] ... .SH COPYRIGHT .if n Copyright (C) 1989 by Kenneth Almquist. .if t Copyright \(co 1989 by Kenneth Almquist. .SH DESCRIPTION .I Nlecho prints its arguments on the standard output, one argument per line. EOF if test `wc -c < bltin/nlecho.1` -ne 345 then echo 'bltin/nlecho.1 is the wrong size' fi echo extracting bltin/nlecho.c cat > bltin/nlecho.c <<\EOF /* * Echo the command argument to the standard output, one line at a time. * This command is useful for debugging th shell and whenever you what * to output strings literally. * * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. */ #define main nlechocmd #include "bltin.h" main(argc, argv) char **argv; { register char **ap; for (ap = argv + 1 ; *ap ; ap++) { fputs(*ap, stdout); putchar('\n'); } return 0; } EOF if test `wc -c < bltin/nlecho.c` -ne 613 then echo 'bltin/nlecho.c is the wrong size' fi echo extracting bltin/regexp.c cat > bltin/regexp.c <<\EOF /* * Regular expression matching for expr(1). Bugs: The upper bound of * a range specified by the \{ feature cannot be zero. * * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. */ #include "bltin.h" #define RE_END 0 /* end of regular expression */ #define RE_LITERAL 1 /* normal character follows */ #define RE_DOT 2 /* "." */ #define RE_CCL 3 /* "[...]" */ #define RE_NCCL 4 /* "[^...]" */ #define RE_LP 5 /* "\(" */ #define RE_RP 6 /* "\)" */ #define RE_MATCHED 7 /* "\digit" */ #define RE_EOS 8 /* "$" matches end of string */ #define RE_STAR 9 /* "*" */ #define RE_RANGE 10 /* "\{num,num\}" */ char *match_begin[10]; short match_length[10]; short number_parens; char * re_compile(pattern) char *pattern; { register char *p; register char c; char *comp; register char *q; char *begin; char *endp; register int len; int first; int type; char *stackp; char stack[10]; int paren_num; int i; char *malloc(); p = pattern; if (*p == '^') p++; comp = q = malloc(2 * strlen(p) + 1); begin = q; stackp = stack; paren_num = 0; for (;;) { switch (c = *p++) { case '\0': *q = '\0'; goto out; case '.': *q++ = RE_DOT; len = 1; break; case '[': begin = q; *q = RE_CCL; if (*p == '^') { *q = RE_NCCL; p++; } q++; first = 1; while (*p != ']' || first == 1) { if (p[1] == '-' && p[2] != ']') { *q++ = '-'; *q++ = p[0]; *q++ = p[2]; p += 3; } else if (*p == '-') { *q++ = '-'; *q++ = '-'; *q++ = '-'; p++; } else { *q++ = *p++; } first = 0; } p++; *q++ = '\0'; len = q - begin; break; case '$': if (*p != '\0') goto dft; *q++ = RE_EOS; break; case '*': if (len == 0) goto dft; type = RE_STAR; range: i = (type == RE_RANGE)? 3 : 1; endp = q + i; begin = q - len; do { --q; *(q + i) = *q; } while (--len > 0); q = begin; *q++ = type; if (type == RE_RANGE) { i = 0; while ((unsigned)(*p - '0') <= 9) i = 10 * i + (*p++ - '0'); *q++ = i; if (*p != ',') { *q++ = i; } else { p++; i = 0; while ((unsigned)(*p - '0') <= 9) i = 10 * i + (*p++ - '0'); *q++ = i; } if (*p != '\\' || *++p != '}') error("RE error"); p++; } q = endp; break; case '\\': if ((c = *p++) == '(') { if (++paren_num > 9) error("RE error"); *q++ = RE_LP; *q++ = paren_num; *stackp++ = paren_num; len = 0; } else if (c == ')') { if (stackp == stack) error("RE error"); *q++ = RE_RP; *q++ = *--stackp; len = 0; } else if (c == '{') { type = RE_RANGE; goto range; } else if ((unsigned)(c - '1') < 9) { /* should check validity here */ *q++ = RE_MATCHED; *q++ = c - '0'; len = 2; } else { goto dft; } break; default: dft: *q++ = RE_LITERAL; *q++ = c; len = 2; break; } } out: if (stackp != stack) error("RE error"); number_parens = paren_num; return comp; } re_match(pattern, string) char *pattern; char *string; { char **pp; static int match(); match_begin[0] = string; for (pp = &match_begin[1] ; pp <= &match_begin[9] ; pp++) *pp = 0; return match(pattern, string); } static match(pattern, string) char *pattern; char *string; { register char *p, *q; int counting; int low, high, count; char *curpat; char *start_count; int negate; int found; char *r; int len; char c; p = pattern; q = string; counting = 0; for (;;) { if (counting) { if (++count > high) goto bad; p = curpat; } switch (*p++) { case RE_END: match_length[0] = q - match_begin[0]; return 1; case RE_LITERAL: if (*q++ != *p++) goto bad; break; case RE_DOT: if (*q++ == '\0') goto bad; break; case RE_CCL: negate = 0; goto ccl; case RE_NCCL: negate = 1; ccl: found = 0; c = *q++; while (*p) { if (*p == '-') { if (c >= *++p && c <= *++p) found = 1; } else { if (c == *p) found = 1; } p++; } p++; if (found == negate) goto bad; break; case RE_LP: match_begin[*p++] = q; break; case RE_RP: match_length[*p] = q - match_begin[*p]; p++; break; case RE_MATCHED: r = match_begin[*p]; len = match_length[*p++]; while (--len >= 0) { if (*q++ != *r++) goto bad; } break; case RE_EOS: if (*q != '\0') goto bad; break; case RE_STAR: low = 0; high = 32767; goto range; case RE_RANGE: low = *p++; high = *p++; if (high == 0) high = 32767; range: curpat = p; start_count = q; count = 0; counting++; break; } } bad: if (! counting) return 0; len = 1; if (*curpat == RE_MATCHED) len = match_length[curpat[1]]; while (--count >= low) { if (match(p, start_count + count * len)) return 1; } return 0; } EOF if test `wc -c < bltin/regexp.c` -ne 5073 then echo 'bltin/regexp.c is the wrong size' fi echo extracting bltin/stalloc.c cat > bltin/stalloc.c <<\EOF /* * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. */ #include "../shell.h" void error(); pointer malloc(); pointer stalloc(nbytes) { register pointer p; if ((p = malloc(nbytes)) == NULL) error("Out of space"); return p; } EOF if test `wc -c < bltin/stalloc.c` -ne 413 then echo 'bltin/stalloc.c is the wrong size' fi echo extracting bltin/umask.c cat > bltin/umask.c <<\EOF /* * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. */ #include <stdio.h> main(argc, argv) char **argv; { int mask; if (argc > 1) { fprintf(stderr, "umask: only builtin version of umask can set value\n"); exit(2); } printf("%.4o\n", umask(0)); return 0; } EOF if test `wc -c < bltin/umask.c` -ne 461 then echo 'bltin/umask.c is the wrong size' fi echo extracting bltin/unary_op cat > bltin/unary_op <<\EOF # List of unary operators used by test/expr. # # Copyright (C) 1989 by Kenneth Almquist. All rights reserved. # This file is part of ash, which is distributed under the terms specified # by the Ash General Public License. See the file named LICENSE. NOT ! 3 ISREAD -r 12 OP_FILE ISWRITE -w 12 OP_FILE ISEXEC -x 12 OP_FILE ISFILE -f 12 OP_FILE ISDIR -d 12 OP_FILE ISCHAR -c 12 OP_FILE ISBLOCK -b 12 OP_FILE ISFIFO -p 12 OP_FILE ISSETUID -u 12 OP_FILE ISSETGID -g 12 OP_FILE ISSTICKY -k 12 OP_FILE ISSIZE -s 12 OP_FILE ISTTY -t 12 OP_INT NULSTR -z 12 OP_STRING STRLEN -n 12 OP_STRING EOF if test `wc -c < bltin/unary_op` -ne 628 then echo 'bltin/unary_op is the wrong size' fi if test ! -d funcs then mkdir funcs fi echo extracting funcs/cmv cat > funcs/cmv <<\EOF # Conditional move--don't replace an existing file. # Copyright (C) 1989 by Kenneth Almquist. All rights reserved. # This file is part of ash, which is distributed under the terms specified # by the Ash General Public License. cmv() { if test $# != 2 then echo "cmv: arg count" return 2 fi if test -f "$2" -o -w "$2" then echo "$2 exists" return 2 fi /bin/mv "$1" "$2" } EOF if test `wc -c < funcs/cmv` -ne 384 then echo 'funcs/cmv is the wrong size' fi echo extracting funcs/dirs cat > funcs/dirs <<\EOF # pushd, popd, and dirs --- written by Chris Bertin # Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris # as modified by Patrick Elam of GTRI and Kenneth Almquist at UW pushd () { SAVE=`pwd` if [ "$1" = "" ] then if [ "$DSTACK" = "" ] then echo "pushd: directory stack empty." return 1 fi set $DSTACK cd $1 || return shift 1 DSTACK="$*" else cd $1 > /dev/null || return fi DSTACK="$SAVE $DSTACK" dirs } popd () { if [ "$DSTACK" = "" ] then echo "popd: directory stack empty." return 1 fi set $DSTACK cd $1 shift DSTACK=$* dirs } dirs () { echo "`pwd` $DSTACK" return 0 } EOF if test `wc -c < funcs/dirs` -ne 609 then echo 'funcs/dirs is the wrong size' fi echo extracting funcs/kill cat > funcs/kill <<\EOF # Copyright (C) 1989 by Kenneth Almquist. All rights reserved. # This file is part of ash, which is distributed under the terms specified # by the Ash General Public License. # # Convert job names to process ids and then run /bin/kill. kill() { local args x args= for x in "$@" do case $x in %*) x=`jobid "$x"` ;; esac args="$args $x" done /bin/kill $args } EOF if test `wc -c < funcs/kill` -ne 372 then echo 'funcs/kill is the wrong size' fi echo extracting funcs/login cat > funcs/login <<\EOF # Copyright (C) 1989 by Kenneth Almquist. All rights reserved. # This file is part of ash, which is distributed under the terms specified # by the Ash General Public License. # # replaces the login builtin in the BSD shell login () exec login "$@" EOF if test `wc -c < funcs/login` -ne 250 then echo 'funcs/login is the wrong size' fi echo extracting funcs/newgrp cat > funcs/newgrp <<\EOF # Copyright (C) 1989 by Kenneth Almquist. All rights reserved. # This file is part of ash, which is distributed under the terms specified # by the Ash General Public License. newgrp() exec newgrp "$@" EOF if test `wc -c < funcs/newgrp` -ne 203 then echo 'funcs/newgrp is the wrong size' fi rm -f funcs/popd ln funcs/dirs funcs/popd rm -f funcs/pushd ln funcs/dirs funcs/pushd echo extracting funcs/read cat > funcs/read <<\EOF # Copyright (C) 1989 by Kenneth Almquist. All rights reserved. # This file is part of ash, which is distributed under the terms specified # by the Ash General Public License. read () { if test "$#" = 0 then echo "Usage: read variable..." return 2 fi read_line="`line`" read_status=$? read_flag="$-" set -f for read_word in $read_line do set +f if test $# -eq 0 then eval "$read_var=\"\$$read_var \"'$read_word'" else eval "$1='$read_word'" read_var="$1" shift fi done set +f set "-$read_flag" while test $# -gt 0 do eval "$1=" shift done return "$read_status" } EOF if test `wc -c < funcs/read` -ne 597 then echo 'funcs/read is the wrong size' fi echo extracting funcs/suspend cat > funcs/suspend <<\EOF # Copyright (C) 1989 by Kenneth Almquist. All rights reserved. # This file is part of ash, which is distributed under the terms specified # by the Ash General Public License. suspend() { local - set +j kill -TSTP 0 } EOF if test `wc -c < funcs/suspend` -ne 222 then echo 'funcs/suspend is the wrong size' fi echo extracting funcs/tset cat > funcs/tset <<\EOF # Run the tset command, setting the shell variables appropriately. With # the -s flag, set TERMCAP as well as TERM. The -s flag must come first. # # Copyright (C) 1989 by Kenneth Almquist. All rights reserved. # This file is part of ash, which is distributed under the terms specified # by the Ash General Public License. tset() { case $1 in -s) eval "$(/usr/ucb/tset "$@")" *) TERM=$(/usr/ucb/tset - "$@") esac } EOF if test `wc -c < funcs/tset` -ne 422 then echo 'funcs/tset is the wrong size' fi echo Archive 8 unpacked exit -- Please send comp.sources.unix-related mail to rsalz@uunet.uu.net. Use a domain-based address or give alternate paths, or you may lose out.