v19i008: A reimplementation of the System V shell, Part08/08

Rich Salz rsalz at uunet.uu.net
Wed May 31 04:50:51 AEST 1989


Submitted-by: ka at 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 at uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.



More information about the Comp.sources.unix mailing list