about summary refs log tree commit diff
path: root/Src/builtin.c
diff options
context:
space:
mode:
Diffstat (limited to 'Src/builtin.c')
-rw-r--r--Src/builtin.c726
1 files changed, 418 insertions, 308 deletions
diff --git a/Src/builtin.c b/Src/builtin.c
index a8174a2bc..1bf26aeb8 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -46,14 +46,14 @@ static struct builtin builtins[] =
     BUILTIN(".", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL),
     BUILTIN(":", BINF_PSPECIAL, bin_true, 0, -1, 0, NULL, NULL),
     BUILTIN("alias", BINF_MAGICEQUALS | BINF_PLUSOPTS, bin_alias, 0, -1, 0, "Lgmr", NULL),
-    BUILTIN("autoload", BINF_TYPEOPTS, bin_functions, 0, -1, 0, "tUXwkz", "u"),
+    BUILTIN("autoload", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "tUXwkz", "u"),
     BUILTIN("bg", 0, bin_fg, 0, -1, BIN_BG, NULL, NULL),
     BUILTIN("break", BINF_PSPECIAL, bin_break, 0, 1, BIN_BREAK, NULL, NULL),
     BUILTIN("bye", 0, bin_break, 0, 1, BIN_EXIT, NULL, NULL),
     BUILTIN("cd", 0, bin_cd, 0, 2, BIN_CD, NULL, NULL),
     BUILTIN("chdir", 0, bin_cd, 0, 2, BIN_CD, NULL, NULL),
     BUILTIN("continue", BINF_PSPECIAL, bin_break, 0, 1, BIN_CONTINUE, NULL, NULL),
-    BUILTIN("declare", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AEFHLRTUZafghilprtux", NULL),
+    BUILTIN("declare", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%lprtux", NULL),
     BUILTIN("dirs", 0, bin_dirs, 0, -1, 0, "clpv", NULL),
     BUILTIN("disable", 0, bin_enable, 0, -1, BIN_DISABLE, "afmr", NULL),
     BUILTIN("disown", 0, bin_fg, 0, -1, BIN_DISOWN, NULL, NULL),
@@ -62,12 +62,18 @@ static struct builtin builtins[] =
     BUILTIN("enable", 0, bin_enable, 0, -1, BIN_ENABLE, "afmr", NULL),
     BUILTIN("eval", BINF_PSPECIAL, bin_eval, 0, -1, BIN_EVAL, NULL, NULL),
     BUILTIN("exit", BINF_PSPECIAL, bin_break, 0, 1, BIN_EXIT, NULL, NULL),
-    BUILTIN("export", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, BIN_EXPORT, "EFHLRTUZafhilprtu", "xg"),
+    BUILTIN("export", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, BIN_EXPORT, "E:%F:%HL:%R:%TUZ:%afhi:%lprtu", "xg"),
     BUILTIN("false", 0, bin_false, 0, -1, 0, NULL, NULL),
-    BUILTIN("fc", BINF_FCOPTS, bin_fc, 0, -1, BIN_FC, "nlreIRWAdDfEim", NULL),
+    /*
+     * We used to behave as if the argument to -e was optional.
+     * But that's actually not useful, so it's more consistent to
+     * cause an error.
+     */
+    BUILTIN("fc", 0, bin_fc, 0, -1, BIN_FC, "nlre:IRWAdDfEim",
+	    NULL),
     BUILTIN("fg", 0, bin_fg, 0, -1, BIN_FG, NULL, NULL),
-    BUILTIN("float", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "EFHghlprtux", "E"),
-    BUILTIN("functions", BINF_TYPEOPTS, bin_functions, 0, -1, 0, "mtuU", NULL),
+    BUILTIN("float", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "E:%F:%Hghlprtux", "E"),
+    BUILTIN("functions", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "mtuU", NULL),
     BUILTIN("getln", 0, bin_read, 0, -1, 0, "ecnAlE", "zr"),
     BUILTIN("getopts", 0, bin_getopts, 2, -1, 0, NULL, NULL),
     BUILTIN("hash", BINF_MAGICEQUALS, bin_hash, 0, -1, 0, "Ldfmrv", NULL),
@@ -77,11 +83,11 @@ static struct builtin builtins[] =
 #endif
 
     BUILTIN("history", 0, bin_fc, 0, -1, BIN_FC, "nrdDfEim", "l"),
-    BUILTIN("integer", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "Hghilprtux", "i"),
+    BUILTIN("integer", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "Hghi:%lprtux", "i"),
     BUILTIN("jobs", 0, bin_fg, 0, -1, BIN_JOBS, "dlpZrs", NULL),
     BUILTIN("kill", 0, bin_kill, 0, -1, 0, NULL, NULL),
     BUILTIN("let", 0, bin_let, 1, -1, 0, NULL, NULL),
-    BUILTIN("local", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AEFHLRTUZahilprtux", NULL),
+    BUILTIN("local", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%ahi:%lprtux", NULL),
     BUILTIN("log", 0, bin_log, 0, 0, 0, NULL, NULL),
     BUILTIN("logout", 0, bin_break, 0, 1, BIN_LOGOUT, NULL, NULL),
 
@@ -94,14 +100,14 @@ static struct builtin builtins[] =
 #endif
 
     BUILTIN("popd", 0, bin_cd, 0, 2, BIN_POPD, NULL, NULL),
-    BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "RDPbnrsflzNu0123456789pioOcm-", NULL),
+    BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "RDPbnrsf:lzNu:pioOcm-", NULL),
     BUILTIN("printf", 0, bin_print, 1, -1, BIN_PRINTF, NULL, NULL),
     BUILTIN("pushd", 0, bin_cd, 0, 2, BIN_PUSHD, NULL, NULL),
     BUILTIN("pushln", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, NULL, "-nz"),
     BUILTIN("pwd", 0, bin_pwd, 0, 0, 0, "rLP", NULL),
-    BUILTIN("r", BINF_R, bin_fc, 0, -1, BIN_FC, "nrl", NULL),
-    BUILTIN("read", 0, bin_read, 0, -1, 0, "ceklnpqrstzuAE0123456789", NULL),
-    BUILTIN("readonly", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AEFHLRTUZafghilptux", "r"),
+    BUILTIN("r", 0, bin_fc, 0, -1, BIN_R, "nrl", NULL),
+    BUILTIN("read", 0, bin_read, 0, -1, 0, "cek:%lnpqrstzu:AE", NULL),
+    BUILTIN("readonly", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%lptux", "r"),
     BUILTIN("rehash", 0, bin_hash, 0, 0, 0, "df", "r"),
     BUILTIN("return", BINF_PSPECIAL, bin_break, 0, 1, BIN_RETURN, NULL, NULL),
     BUILTIN("set", BINF_PSPECIAL, bin_set, 0, -1, 0, NULL, NULL),
@@ -115,7 +121,7 @@ static struct builtin builtins[] =
     BUILTIN("trap", BINF_PSPECIAL, bin_trap, 0, -1, 0, NULL, NULL),
     BUILTIN("true", 0, bin_true, 0, -1, 0, NULL, NULL),
     BUILTIN("type", 0, bin_whence, 0, -1, 0, "ampfsw", "v"),
-    BUILTIN("typeset", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AEFHLRTUZafghilprtuxm", NULL),
+    BUILTIN("typeset", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%lprtuxm", NULL),
     BUILTIN("umask", 0, bin_umask, 0, 1, 0, "S", NULL),
     BUILTIN("unalias", 0, bin_unhash, 1, -1, 0, "m", "a"),
     BUILTIN("unfunction", 0, bin_unhash, 1, -1, 0, "m", "f"),
@@ -201,29 +207,49 @@ freebuiltinnode(HashNode hn)
     }
 }
 
-static char *auxdata;
-static int auxlen;
+/* Make sure we have space for a new option and increment. */
 
-/* execute a builtin handler function after parsing the arguments */
+#define OPT_ALLOC_CHUNK 16
 
-#define MAX_OPS 128
+/**/
+static int
+new_optarg(Options ops)
+{
+    /* Argument index must be a non-zero 6-bit number. */
+    if (ops->argscount == 63)
+	return 1;
+    if (ops->argsalloc == ops->argscount) {
+	char **newptr = 
+	    (char **)zhalloc((ops->argsalloc + OPT_ALLOC_CHUNK) *
+			     sizeof(char *));
+	if (ops->argsalloc)
+	    memcpy(newptr, ops->args, ops->argsalloc * sizeof(char *));
+	ops->args = newptr;
+	ops->argsalloc += OPT_ALLOC_CHUNK;
+    }
+    ops->argscount++;
+    return 0;
+}
+
+
+/* execute a builtin handler function after parsing the arguments */
 
 /**/
 int
 execbuiltin(LinkList args, Builtin bn)
 {
     LinkNode n;
-    char ops[MAX_OPS], *arg, *pp, *name, *optstr;
+    char *arg, *pp, *name, *optstr;
     char *oxarg, *xarg = NULL;
-    char typenumstr[] = TYPESET_OPTNUM;
     int flags, sense, argc = 0, execop, xtr = isset(XTRACE), lxarg = 0;
+    struct options ops;
 
-    /* initialise some static variables */
-    auxdata = NULL;
-    auxlen = 0;
+    /* initialise options structure */
+    memset(ops.ind, 0, MAX_OPS*sizeof(unsigned char));
+    ops.args = NULL;
+    ops.argscount = ops.argsalloc = 0;
 
     /* initialize some local variables */
-    memset(ops, 0, MAX_OPS);
     name = (char *) ugetnode(args);
 
     arg = (char *) ugetnode(args);
@@ -239,7 +265,7 @@ execbuiltin(LinkList args, Builtin bn)
 
     /* Sort out the options. */
     if ((flags & BINF_ECHOPTS) && isset(BSDECHO))
-	ops['E'] = 1;
+	ops.ind['E'] = 1;
     if (optstr)
 	/* while arguments look like options ... */
 	while (arg &&
@@ -269,26 +295,62 @@ execbuiltin(LinkList args, Builtin bn)
 		    lxarg = strlen(xarg);
 		}
 	    }
-	    /* handle -- or - (ops['-']), and + (ops['-'] and ops['+']) */
+	    /* handle -- or - (ops.ind['-']), and +
+	     * (ops.ind['-'] and ops.ind['+']) */
 	    if (arg[1] == '-')
 		arg++;
 	    if (!arg[1]) {
-		ops['-'] = 1;
+		ops.ind['-'] = 1;
 		if (!sense)
-		    ops['+'] = 1;
+		    ops.ind['+'] = 1;
 	    }
 	    /* save options in ops, as long as they are in bn->optstr */
 	    execop = -1;
-	    while (*++arg)
-		if (strchr(optstr, execop = (int)*arg))
-		    ops[(int)*arg] = (sense) ? 1 : 2;
-		else
+	    while (*++arg) {
+		char *optptr;
+		if ((optptr = strchr(optstr, execop = (int)*arg))) {
+		    ops.ind[(int)*arg] = (sense) ? 1 : 2;
+		    if (optptr[1] == ':') {
+			char *argptr = NULL;
+			if (optptr[2] == ':') {
+			    if (arg[1])
+				argptr = arg+1;
+			    /* Optional argument in same word*/
+			} else if (optptr[2] == '%') {
+			    /* Optional numeric argument in same
+			     * or next word. */
+			    if (arg[1] && idigit(arg[1]))
+				argptr = arg+1;
+			    else if (firstnode(args) &&
+				     idigit(*(char *)peekfirst(args)))
+				argptr = arg = (char *)ugetnode(args);
+			} else {
+			    /* Mandatory argument */
+			    if (arg[1])
+				argptr = arg+1;
+			    else if ((arg = (char *)ugetnode(args)))
+				argptr = arg;
+			    else {
+				zwarnnam(name, "argument expected: -%c", NULL,
+					 execop);
+				return 1;
+			    }
+			}
+			if (argptr) {
+			    if (new_optarg(&ops)) {
+				zwarnnam(name, 
+					 "too many option arguments", NULL, 0);
+				return 1;
+			    }
+			    ops.ind[execop] |= ops.argscount << 2;
+			    ops.args[ops.argscount-1] = argptr;
+			    while (arg[1])
+				arg++;
+			}
+		    }
+		} else
 		    break;
-	    /* "typeset" may take a numeric argument *
-	     * at the tail of the options            */
-	    if (idigit(*arg) && (flags & BINF_TYPEOPT) &&
-		strchr(typenumstr, arg[-1]))
-		auxlen = (int)zstrtol(arg, &arg, 10);
+	    }
 	    /* The above loop may have exited on an invalid option.  (We  *
 	     * assume that any option requiring metafication is invalid.) */
 	    if (*arg) {
@@ -299,41 +361,24 @@ execbuiltin(LinkList args, Builtin bn)
 	    }
 	    arg = (char *) ugetnode(args);
 	    /* for the "print" builtin, the options after -R are treated as
-	       options to "echo" and -f takes an extra argument */
-	    if (flags & BINF_PRINTOPTS) {
-		if (ops['R'] && !ops['f']) {
-		    optstr = "ne";
-		    flags |= BINF_ECHOPTS;
-		} else if (execop == 'f') {
-		    if (!arg) {
-			zwarnnam(name, "-f: format argument expected", NULL, 0);
-			return 1;
-		    }
-		    auxdata = arg;
-		    arg = (char *) ugetnode(args);
-		}
+	       options to "echo" */
+	    if ((flags & BINF_PRINTOPTS) && ops.ind['R'] && !ops.ind['f']) {
+		optstr = "ne";
+		flags |= BINF_ECHOPTS;
 	    }
 	    /* the option -- indicates the end of the options */
-	    if (ops['-'])
+	    if (ops.ind['-'])
 		break;
-	    /* for "fc", -e takes an extra argument */
-	    if ((flags & BINF_FCOPTS) && execop == 'e') {
-		auxdata = arg;
-		arg = (char *) ugetnode(args);
-	    }
-	    /* some "typeset" options take a numeric extra argument */
-	    if ((flags & BINF_TYPEOPT) && strchr(typenumstr, execop) &&
-		arg && idigit(*arg)) {
-		auxlen = atoi(arg);
-		arg = (char *) ugetnode(args);
-	    }
 	}
-    if (flags & BINF_R)
-	auxdata = "-";
     /* handle built-in options, for overloaded handler functions */
-    if ((pp = bn->defopts))
-	while (*pp)
-	    ops[(int)*pp++] = 1;
+    if ((pp = bn->defopts)) {
+	while (*pp) {
+	    /* only if not already set */
+	    if (!ops.ind[(int)*pp])
+		ops.ind[(int)*pp] = 1;
+	    pp++;
+	}
+    }
 
     /* Set up the argument list. */
     if (arg) {
@@ -381,7 +426,7 @@ execbuiltin(LinkList args, Builtin bn)
 	    fflush(xtrerr);
 	}
 	/* call the handler function, and return its return value */
-	return (*(bn->handlerfunc)) (name, argv, ops, bn->funcid);
+	return (*(bn->handlerfunc)) (name, argv, &ops, bn->funcid);
     }
 }
 
@@ -391,7 +436,7 @@ execbuiltin(LinkList args, Builtin bn)
 
 /**/
 int
-bin_enable(char *name, char **argv, char *ops, int func)
+bin_enable(char *name, char **argv, Options ops, int func)
 {
     HashTable ht;
     HashNode hn;
@@ -401,11 +446,11 @@ bin_enable(char *name, char **argv, char *ops, int func)
     int match = 0, returnval = 0;
 
     /* Find out which hash table we are working with. */
-    if (ops['f'])
+    if (OPT_ISSET(ops,'f'))
 	ht = shfunctab;
-    else if (ops['r'])
+    else if (OPT_ISSET(ops,'r'))
 	ht = reswdtab;
-    else if (ops['a'])
+    else if (OPT_ISSET(ops,'a'))
 	ht = aliastab;
     else
 	ht = builtintab;
@@ -431,7 +476,7 @@ bin_enable(char *name, char **argv, char *ops, int func)
     }
 
     /* With -m option, treat arguments as glob patterns. */
-    if (ops['m']) {
+    if (OPT_ISSET(ops,'m')) {
 	for (; *argv; argv++) {
 	    /* parse pattern */
 	    tokenize(*argv);
@@ -471,7 +516,7 @@ bin_enable(char *name, char **argv, char *ops, int func)
 
 /**/
 int
-bin_set(char *nam, char **args, char *ops, int func)
+bin_set(char *nam, char **args, Options ops, int func)
 {
     int action, optno, array = 0, hadopt = 0,
 	hadplus = 0, hadend = 0, sort = 0;
@@ -590,9 +635,10 @@ int doprintdir = 0;		/* set in exec.c (for autocd) */
 
 /**/
 int
-bin_pwd(char *name, char **argv, char *ops, int func)
+bin_pwd(char *name, char **argv, Options ops, int func)
 {
-    if (ops['r'] || ops['P'] || (isset(CHASELINKS) && !ops['L']))
+    if (OPT_ISSET(ops,'r') || OPT_ISSET(ops,'P') ||
+	(isset(CHASELINKS) && !OPT_ISSET(ops,'L')))
 	printf("%s\n", zgetcwd());
     else {
 	zputs(pwd, stdout);
@@ -610,33 +656,34 @@ mod_export LinkList dirstack;
 
 /**/
 int
-bin_dirs(char *name, char **argv, char *ops, int func)
+bin_dirs(char *name, char **argv, Options ops, int func)
 {
     LinkList l;
 
     queue_signals();
     /* with -v, -p or no arguments display the directory stack */
-    if (!(*argv || ops['c']) || ops['v'] || ops ['p']) {
+    if (!(*argv || OPT_ISSET(ops,'c')) || OPT_ISSET(ops,'v') || 
+	OPT_ISSET(ops,'p')) {
 	LinkNode node;
 	char *fmt;
 	int pos = 1;
 
 	/* with the -v option, display a numbered list, starting at zero */
-	if (ops['v']) {
+	if (OPT_ISSET(ops,'v')) {
 	    printf("0\t");
 	    fmt = "\n%d\t";
 	/* with the -p option, display entries one per line */
-	} else if (ops['p'])
+	} else if (OPT_ISSET(ops,'p'))
 	    fmt = "\n";
 	else
 	    fmt = " ";
-	if (ops['l'])
+	if (OPT_ISSET(ops,'l'))
 	    fputs(pwd, stdout);
 	else
 	    fprintdir(pwd, stdout);
 	for (node = firstnode(dirstack); node; incnode(node)) {
 	    printf(fmt, pos++);
-	    if (ops['l'])
+	    if (OPT_ISSET(ops,'l'))
 		fputs(getdata(node), stdout);
 	    else
 		fprintdir(getdata(node), stdout);
@@ -704,7 +751,7 @@ static int chasinglinks;
 
 /**/
 int
-bin_cd(char *nam, char **argv, char *ops, int func)
+bin_cd(char *nam, char **argv, Options ops, int func)
 {
     LinkNode dir;
     struct stat st1, st2;
@@ -728,10 +775,11 @@ bin_cd(char *nam, char **argv, char *ops, int func)
 		goto brk;
 	    }
 	} while (*++s);
-	for (s = *argv; *++s; ops[STOUC(*s)] = 1);
+	for (s = *argv; *++s; ops->ind[STOUC(*s)] = 1);
     }
  brk:
-    chasinglinks = ops['P'] || (isset(CHASELINKS) && !ops['L']);
+    chasinglinks = OPT_ISSET(ops,'P') || 
+	(isset(CHASELINKS) && !OPT_ISSET(ops,'L'));
     queue_signals();
     zpushnode(dirstack, ztrdup(pwd));
     if (!(dir = cd_get_dest(nam, argv, ops, func))) {
@@ -764,7 +812,7 @@ bin_cd(char *nam, char **argv, char *ops, int func)
 
 /**/
 static LinkNode
-cd_get_dest(char *nam, char **argv, char *ops, int func)
+cd_get_dest(char *nam, char **argv, Options ops, int func)
 {
     LinkNode dir = NULL;
     LinkNode target;
@@ -834,7 +882,7 @@ cd_get_dest(char *nam, char **argv, char *ops, int func)
     if (!dir) {
 	dir = firstnode(dirstack);
     }
-    if (!(dest = cd_do_chdir(nam, getdata(dir), ops['s']))) {
+    if (!(dest = cd_do_chdir(nam, getdata(dir), OPT_ISSET(ops,'s')))) {
 	if (!target)
 	    zsfree(getlinknode(dirstack));
 	if (func == BIN_POPD)
@@ -1232,7 +1280,7 @@ printif(char *str, int c)
 
 /**/
 int
-bin_fc(char *nam, char **argv, char *ops, int func)
+bin_fc(char *nam, char **argv, Options ops, int func)
 {
     int first = -1, last = -1, retval;
     char *s;
@@ -1246,7 +1294,7 @@ bin_fc(char *nam, char **argv, char *ops, int func)
     }
     /* with the -m option, the first argument is taken *
      * as a pattern that history lines have to match   */
-    if (*argv && ops['m']) {
+    if (*argv && OPT_ISSET(ops,'m')) {
 	tokenize(*argv);
 	if (!(pprog = patcompile(*argv++, 0, NULL))) {
 	    zwarnnam(nam, "invalid match pattern", NULL, 0);
@@ -1254,21 +1302,22 @@ bin_fc(char *nam, char **argv, char *ops, int func)
 	}
     }
     queue_signals();
-    if (ops['R']) {
+    if (OPT_ISSET(ops,'R')) {
 	/* read history from a file */
-	readhistfile(*argv, 1, ops['I'] ? HFILE_SKIPOLD : 0);
+	readhistfile(*argv, 1, OPT_ISSET(ops,'I') ? HFILE_SKIPOLD : 0);
 	unqueue_signals();
 	return 0;
     }
-    if (ops['W']) {
+    if (OPT_ISSET(ops,'W')) {
 	/* write history to a file */
-	savehistfile(*argv, 1, ops['I'] ? HFILE_SKIPOLD : 0);
+	savehistfile(*argv, 1, OPT_ISSET(ops,'I') ? HFILE_SKIPOLD : 0);
 	unqueue_signals();
 	return 0;
     }
-    if (ops['A']) {
+    if (OPT_ISSET(ops,'A')) {
 	/* append history to a file */
-	savehistfile(*argv, 1, HFILE_APPEND | (ops['I'] ? HFILE_SKIPOLD : 0));
+	savehistfile(*argv, 1, HFILE_APPEND | 
+		     (OPT_ISSET(ops,'I') ? HFILE_SKIPOLD : 0));
 	unqueue_signals();
 	return 0;
     }
@@ -1318,7 +1367,7 @@ bin_fc(char *nam, char **argv, char *ops, int func)
     }
     /* default values of first and last, and range checking */
     if (last == -1) {
-	if (ops['l'] && first < curhist) {
+	if (OPT_ISSET(ops,'l') && first < curhist) {
 	    last = addhistnum(curline.histnum,-1,0);
 	    if (last < firsthist())
 		last = firsthist();
@@ -1327,18 +1376,16 @@ bin_fc(char *nam, char **argv, char *ops, int func)
 	    last = first;
     }
     if (first == -1) {
-	first = ops['l']? addhistnum(curline.histnum,-16,0)
+	first = OPT_ISSET(ops,'l')? addhistnum(curline.histnum,-16,0)
 			: addhistnum(curline.histnum,-1,0);
 	if (first < 1)
 	    first = 1;
 	if (last < first)
 	    last = first;
     }
-    if (ops['l']) {
+    if (OPT_ISSET(ops,'l')) {
 	/* list the required part of the history */
-	retval = fclist(stdout, !ops['n'], ops['r'], ops['D'],
-			ops['d'] + ops['f'] * 2 + ops['E'] * 4 + ops['i'] * 8,
-			first, last, asgf, pprog);
+	retval = fclist(stdout, ops, first, last, asgf, pprog);
 	unqueue_signals();
     }
     else {
@@ -1355,10 +1402,16 @@ bin_fc(char *nam, char **argv, char *ops, int func)
 	    unqueue_signals();
 	    zwarnnam("fc", "can't open temp file: %e", NULL, errno);
 	} else {
-	    if (!fclist(out, 0, ops['r'], 0, 0, first, last, asgf, pprog)) {
+	    ops->ind['n'] = 1;	/* No line numbers here. */
+	    if (!fclist(out, ops, first, last, asgf, pprog)) {
 		char *editor;
 
-		editor = auxdata ? auxdata : getsparam("FCEDIT");
+		if (func == BIN_R)
+		    editor = "-";
+		else if (OPT_HASARG(ops, 'e'))
+		    editor = OPT_ARG(ops, 'e');
+		else
+		    editor = getsparam("FCEDIT");
 		if (!editor)
 		    editor = DEFAULT_FCEDIT;
 
@@ -1456,17 +1509,17 @@ fcsubs(char **sp, struct asgment *sub)
 
 /**/
 static int
-fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment *subs, Patprog pprog)
+fclist(FILE *f, Options ops, int first, int last, struct asgment *subs, Patprog pprog)
 {
-    int fclistdone = 0;
+    int fclistdone = 0, tmp;
     char *s;
     Histent ent;
 
     /* reverse range if required */
-    if (r) {
-	r = last;
+    if (OPT_ISSET(ops,'r')) {
+	tmp = last;
 	last = first;
-	first = r;
+	first = tmp;
     }
     /* suppress "no substitution" warning if no substitution is requested */
     if (!subs)
@@ -1489,34 +1542,33 @@ fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment
 	    fclistdone |= fcsubs(&s, subs);
 
 	    /* do numbering */
-	    if (n) {
+	    if (!OPT_ISSET(ops,'n')) {
 		fprintf(f, "%5d%c ", ent->histnum,
 			ent->flags & HIST_FOREIGN? '*' : ' ');
 	    }
 	    /* output actual time (and possibly date) of execution of the
 	       command, if required */
-	    if (d) {
+	    if (OPT_ISSET(ops,'d') || OPT_ISSET(ops,'f') ||
+		OPT_ISSET(ops,'E') || OPT_ISSET(ops,'i')) {
 		struct tm *ltm;
 		ltm = localtime(&ent->stim);
-		if (d >= 2) {
-		    if (d >= 8) {
-			fprintf(f, "%d-%02d-%02d ",
-				ltm->tm_year + 1900,
-				ltm->tm_mon + 1, ltm->tm_mday);
-		    } else if (d >= 4) {
-			fprintf(f, "%d.%d.%d ",
-				ltm->tm_mday, ltm->tm_mon + 1,
-				ltm->tm_year + 1900);
-		    } else {
-			fprintf(f, "%d/%d/%d ",
-				ltm->tm_mon + 1, ltm->tm_mday,
-				ltm->tm_year + 1900);
-		    }
+		if (OPT_ISSET(ops,'i')) {
+		    fprintf(f, "%d-%02d-%02d ",
+			    ltm->tm_year + 1900,
+			    ltm->tm_mon + 1, ltm->tm_mday);
+		} else if (OPT_ISSET(ops,'E')) {
+		    fprintf(f, "%d.%d.%d ",
+			    ltm->tm_mday, ltm->tm_mon + 1,
+			    ltm->tm_year + 1900);
+		} else if (OPT_ISSET(ops,'f')) {
+		    fprintf(f, "%d/%d/%d ",
+			    ltm->tm_mon + 1, ltm->tm_mday,
+			    ltm->tm_year + 1900);
 		}
 		fprintf(f, "%02d:%02d  ", ltm->tm_hour, ltm->tm_min);
 	    }
 	    /* display the time taken by the command, if required */
-	    if (D) {
+	    if (OPT_ISSET(ops,'D')) {
 		long diff;
 		diff = (ent->ftim) ? ent->ftim - ent->stim : 0;
 		fprintf(f, "%ld:%02ld  ", diff / 60, diff % 60);
@@ -1609,10 +1661,10 @@ getasg(char *s)
 /* function to set a single parameter */
 
 /**/
-Param
+static Param
 typeset_single(char *cname, char *pname, Param pm, int func,
 	       int on, int off, int roff, char *value, Param altpm,
-	       char *ops)
+	       Options ops, int auxlen)
 {
     int usepm, tc, keeplocal = 0, newspecial = 0;
     char *subscript;
@@ -1693,9 +1745,9 @@ typeset_single(char *cname, char *pname, Param pm, int func,
     if (usepm) {
 	on &= ~PM_LOCAL;
 	if (!on && !roff && !value) {
-	    if (ops['p'])
+	    if (OPT_ISSET(ops,'p'))
 		paramtab->printnode((HashNode)pm, PRINT_TYPESET);
-	    else if (unset(TYPESETSILENT) || ops['m'])
+	    else if (unset(TYPESETSILENT) || OPT_ISSET(ops,'m'))
 		paramtab->printnode((HashNode)pm, PRINT_INCLUDEVALUE);
 	    return pm;
 	}
@@ -1741,7 +1793,7 @@ typeset_single(char *cname, char *pname, Param pm, int func,
 	    return NULL;
 	}
 	pm->flags |= (on & PM_READONLY);
-	if (ops['p'])
+	if (OPT_ISSET(ops,'p'))
 	    paramtab->printnode((HashNode)pm, PRINT_TYPESET);
 	return pm;
     }
@@ -1931,7 +1983,7 @@ typeset_single(char *cname, char *pname, Param pm, int func,
 	return NULL;
     }
 
-    if (ops['p'])
+    if (OPT_ISSET(ops,'p'))
 	paramtab->printnode((HashNode)pm, PRINT_TYPESET);
 
     return pm;
@@ -1941,7 +1993,7 @@ typeset_single(char *cname, char *pname, Param pm, int func,
 
 /**/
 int
-bin_typeset(char *name, char **argv, char *ops, int func)
+bin_typeset(char *name, char **argv, Options ops, int func)
 {
     Param pm;
     Asgment asg;
@@ -1949,20 +2001,38 @@ bin_typeset(char *name, char **argv, char *ops, int func)
     char *optstr = TYPESET_OPTSTR;
     int on = 0, off = 0, roff, bit = PM_ARRAY;
     int i;
-    int returnval = 0, printflags = 0;
+    int returnval = 0, printflags = 0, auxlen = 0;
 
     /* hash -f is really the builtin `functions' */
-    if (ops['f'])
+    if (OPT_ISSET(ops,'f'))
 	return bin_functions(name, argv, ops, func);
 
     /* Translate the options into PM_* flags.   *
      * Unfortunately, this depends on the order *
      * these flags are defined in zsh.h         */
     for (; *optstr; optstr++, bit <<= 1)
-	if (ops[STOUC(*optstr)] == 1)
+    {
+	int optval = STOUC(*optstr);
+	if (OPT_MINUS(ops,optval))
 	    on |= bit;
-	else if (ops[STOUC(*optstr)] == 2)
+	else if (OPT_PLUS(ops,optval))
 	    off |= bit;
+	/*
+	 * There is only a single field in struct param for widths,
+	 * precisions and bases.  Until this gets fixed, we can therefore
+	 * bundle all optional arguments up into a single word.  You
+	 * may think this is very nasty, but then you should have seen the
+	 * code before option arguments were handled properly.
+	 */
+	if (OPT_HASARG(ops,optval)) {
+	    char *eptr, *arg = OPT_ARG(ops,optval);
+	    auxlen = (int)zstrtol(arg, &eptr, 10);
+	    if (*eptr) {
+		zwarnnam(name, "bad integer value: %s", arg, 0);
+		return 1;
+	    }
+	}
+    }
     roff = off;
 
     /* Sanity checks on the options.  Remove conflicting options. */
@@ -1998,13 +2068,13 @@ bin_typeset(char *name, char **argv, char *ops, int func)
     queue_signals();
 
     /* Given no arguments, list whatever the options specify. */
-    if (ops['p'])
+    if (OPT_ISSET(ops,'p'))
 	printflags |= PRINT_TYPESET;
     if (!*argv) {
-	if (!ops['p']) {
+	if (!OPT_ISSET(ops,'p')) {
 	    if (!(on|roff))
 		printflags |= PRINT_TYPE;
-	    if (roff || ops['+'])
+	    if (roff || OPT_ISSET(ops,'+'))
 		printflags |= PRINT_NAMEONLY;
 	}
 	scanhashtable(paramtab, 1, on|roff, 0, paramtab->printnode, printflags);
@@ -2012,8 +2082,9 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 	return 0;
     }
 
-    if (!(ops['g'] || ops['x'] || ops['m']) || ops['g'] == 2 || *name == 'l' ||
-	(!isset(GLOBALEXPORT) && !ops['g']))
+    if (!(OPT_ISSET(ops,'g') || OPT_ISSET(ops,'x') || OPT_ISSET(ops,'m')) || 
+	OPT_ISSET(ops,'g') == 2 || *name == 'l' ||
+	(!isset(GLOBALEXPORT) && !OPT_ISSET(ops,'g')))
 	on |= PM_LOCAL;
 
     if (on & PM_TIED) {
@@ -2021,7 +2092,7 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 	struct asgment asg0;
 	char *oldval = NULL;
 
-	if (ops['m']) {
+	if (OPT_ISSET(ops,'m')) {
 	    zwarnnam(name, "incompatible options for -T", NULL, 0);
 	    unqueue_signals();
 	    return 1;
@@ -2072,7 +2143,7 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 				 (Param)paramtab->getnode(paramtab,
 							  asg->name),
 				 func, (on | PM_ARRAY) & ~PM_EXPORTED,
-				 off, roff, asg->value, NULL, ops))) {
+				 off, roff, asg->value, NULL, ops, auxlen))) {
 	    unqueue_signals();
 	    return 1;
 	}
@@ -2084,7 +2155,7 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 				(Param)paramtab->getnode(paramtab,
 							 asg0.name),
 				func, on, off, roff, asg0.value, apm,
-				ops))) {
+				ops, auxlen))) {
 	    if (oldval)
 		zsfree(oldval);
 	    unsetparam_pm(apm, 1, 1);
@@ -2106,8 +2177,8 @@ bin_typeset(char *name, char **argv, char *ops, int func)
     }
 
     /* With the -m option, treat arguments as glob patterns */
-    if (ops['m']) {
-	if (!ops['p']) {
+    if (OPT_ISSET(ops,'m')) {
+	if (!OPT_ISSET(ops,'p')) {
 	    if (!(on|roff))
 		printflags |= PRINT_TYPE;
 	    if (!on)
@@ -2125,7 +2196,7 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 		returnval = 1;
 		continue;
 	    }
-	    if (ops['m'] == 2 && !asg->value) {
+	    if (OPT_PLUS(ops,'m') == 2 && !asg->value) {
 		scanmatchtable(paramtab, pprog, on|roff, 0,
 			       paramtab->printnode, printflags);
 		continue;
@@ -2151,7 +2222,7 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 	    for (pmnode = firstnode(pmlist); pmnode; incnode(pmnode)) {
 		pm = (Param) getdata(pmnode);
 		if (!typeset_single(name, pm->nam, pm, func, on, off, roff,
-				    asg->value, NULL, ops))
+				    asg->value, NULL, ops, auxlen))
 		    returnval = 1;
 	    }
 	}
@@ -2166,7 +2237,7 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 				     gethashnode2(paramtab, asg->name) :
 				     paramtab->getnode(paramtab, asg->name)),
 			    func, on, off, roff, asg->value, NULL,
-			    ops))
+			    ops, auxlen))
 	    returnval = 1;
     }
     unqueue_signals();
@@ -2177,7 +2248,7 @@ bin_typeset(char *name, char **argv, char *ops, int func)
 
 /**/
 int
-eval_autoload(Shfunc shf, char *name, char *ops, int func)
+eval_autoload(Shfunc shf, char *name, Options ops, int func)
 {
     if (!(shf->flags & PM_UNDEFINED))
 	return 1;
@@ -2186,7 +2257,7 @@ eval_autoload(Shfunc shf, char *name, char *ops, int func)
 	freeeprog(shf->funcdef);
 	shf->funcdef = &dummy_eprog;
     }
-    if (ops['X'] == 1) {
+    if (OPT_MINUS(ops,'X')) {
 	char *fargv[3];
 	fargv[0] = name;
 	fargv[1] = "\"$@\"";
@@ -2195,7 +2266,8 @@ eval_autoload(Shfunc shf, char *name, char *ops, int func)
 	return bin_eval(name, fargv, ops, func);
     }
 
-    return !loadautofn(shf, (ops['k'] ? 2 : (ops['z'] ? 0 : 1)), 1);
+    return !loadautofn(shf, (OPT_ISSET(ops,'k') ? 2 : 
+			     (OPT_ISSET(ops,'z') ? 0 : 1)), 1);
 }
 
 /* Display or change the attributes of shell functions.   *
@@ -2204,7 +2276,7 @@ eval_autoload(Shfunc shf, char *name, char *ops, int func)
 
 /**/
 int
-bin_functions(char *name, char **argv, char *ops, int func)
+bin_functions(char *name, char **argv, Options ops, int func)
 {
     Patprog pprog;
     Shfunc shf;
@@ -2212,27 +2284,27 @@ bin_functions(char *name, char **argv, char *ops, int func)
     int on = 0, off = 0, pflags = 0;
 
     /* Do we have any flags defined? */
-    if (ops['u'] == 2)
+    if (OPT_ISSET(ops,'u') == 2)
 	off |= PM_UNDEFINED;
-    else if (ops['u'] == 1 || ops['X'])
+    else if (OPT_MINUS(ops,'u') || OPT_ISSET(ops,'X'))
 	on |= PM_UNDEFINED;
-    if (ops['U'] == 1)
+    if (OPT_MINUS(ops,'U'))
 	on |= PM_UNALIASED|PM_UNDEFINED;
-    else if (ops['U'] == 2)
+    else if (OPT_PLUS(ops,'U'))
 	off |= PM_UNALIASED;
-    if (ops['t'] == 1)
+    if (OPT_MINUS(ops,'t'))
 	on |= PM_TAGGED;
-    else if (ops['t'] == 2)
+    else if (OPT_PLUS(ops,'t'))
 	off |= PM_TAGGED;
 
-    if ((off & PM_UNDEFINED) || (ops['k'] && ops['z']) ||
-	(ops['X'] != 2 && (ops['k'] || ops['z'])) ||
-	(ops['X'] == 1 && (ops['m'] || *argv || !scriptname))) {
+    if ((off & PM_UNDEFINED) || (OPT_ISSET(ops,'k') && OPT_ISSET(ops,'z')) ||
+	(!OPT_PLUS(ops,'X') && (OPT_ISSET(ops,'k') || OPT_ISSET(ops,'z'))) ||
+	(OPT_MINUS(ops,'X') && (OPT_ISSET(ops,'m') || *argv || !scriptname))) {
 	zwarnnam(name, "invalid option(s)", NULL, 0);
 	return 1;
     }
 
-    if (ops['f'] == 2 || ops['+'])
+    if (OPT_PLUS(ops,'f') || OPT_ISSET(ops,'+'))
 	pflags |= PRINT_NAMEONLY;
 
     /* If no arguments given, we will print functions.  If flags *
@@ -2242,7 +2314,7 @@ bin_functions(char *name, char **argv, char *ops, int func)
 	int ret = 0;
 
 	queue_signals();
-	if (ops['X'] == 1) {
+	if (OPT_MINUS(ops,'X')) {
 	    if ((shf = (Shfunc) shfunctab->getnode(shfunctab, scriptname))) {
 		DPUTS(!shf->funcdef,
 		      "BUG: Calling autoload from empty function");
@@ -2253,7 +2325,7 @@ bin_functions(char *name, char **argv, char *ops, int func)
 	    shf->flags = on;
 	    ret = eval_autoload(shf, scriptname, ops, func);
 	} else {
-	    if (ops['U'] && !ops['u'])
+	    if (OPT_ISSET(ops,'U') && !OPT_ISSET(ops,'u'))
 		on &= ~PM_UNDEFINED;
 	    scanhashtable(shfunctab, 1, on|off, DISABLED, shfunctab->printnode,
 			  pflags);
@@ -2263,7 +2335,7 @@ bin_functions(char *name, char **argv, char *ops, int func)
     }
 
     /* With the -m option, treat arguments as glob patterns */
-    if (ops['m']) {
+    if (OPT_ISSET(ops,'m')) {
 	on &= ~PM_UNDEFINED;
 	for (; *argv; argv++) {
 	    /* expand argument */
@@ -2283,7 +2355,7 @@ bin_functions(char *name, char **argv, char *ops, int func)
 				!(shf->flags & DISABLED)) {
 				shf->flags = (shf->flags |
 					      (on & ~PM_UNDEFINED)) & ~off;
-				if (ops['X'] &&
+				if (OPT_ISSET(ops,'X') &&
 				    eval_autoload(shf, shf->nam, ops, func)) {
 				    returnval = 1;
 				}
@@ -2303,14 +2375,15 @@ bin_functions(char *name, char **argv, char *ops, int func)
     /* Take the arguments literally -- do not glob */
     queue_signals();
     for (; *argv; argv++) {
-	if (ops['w'])
+	if (OPT_ISSET(ops,'w'))
 	    returnval = dump_autoload(name, *argv, on, ops, func);
 	else if ((shf = (Shfunc) shfunctab->getnode(shfunctab, *argv))) {
 	    /* if any flag was given */
 	    if (on|off) {
 		/* turn on/off the given flags */
 		shf->flags = (shf->flags | (on & ~PM_UNDEFINED)) & ~off;
-		if (ops['X'] && eval_autoload(shf, shf->nam, ops, func))
+		if (OPT_ISSET(ops,'X') && 
+		    eval_autoload(shf, shf->nam, ops, func))
 		    returnval = 1;
 	    } else
 		/* no flags, so just print */
@@ -2322,7 +2395,7 @@ bin_functions(char *name, char **argv, char *ops, int func)
 	    shf->flags = on;
 	    shf->funcdef = mkautofn(shf);
 	    shfunctab->addnode(shfunctab, ztrdup(*argv), shf);
-	    if (ops['X'] && eval_autoload(shf, shf->nam, ops, func))
+	    if (OPT_ISSET(ops,'X') && eval_autoload(shf, shf->nam, ops, func))
 		returnval = 1;
 	} else
 	    returnval = 1;
@@ -2361,7 +2434,7 @@ mkautofn(Shfunc shf)
 
 /**/
 int
-bin_unset(char *name, char **argv, char *ops, int func)
+bin_unset(char *name, char **argv, Options ops, int func)
 {
     Param pm, next;
     Patprog pprog;
@@ -2370,11 +2443,11 @@ bin_unset(char *name, char **argv, char *ops, int func)
     int i;
 
     /* unset -f is the same as unfunction */
-    if (ops['f'])
+    if (OPT_ISSET(ops,'f'))
 	return bin_unhash(name, argv, ops, func);
 
     /* with -m option, treat arguments as glob patterns */
-    if (ops['m']) {
+    if (OPT_ISSET(ops,'m')) {
 	while ((s = *argv++)) {
 	    /* expand */
 	    tokenize(s);
@@ -2453,7 +2526,7 @@ bin_unset(char *name, char **argv, char *ops, int func)
 
 /**/
 int
-bin_whence(char *nam, char **argv, char *ops, int func)
+bin_whence(char *nam, char **argv, Options ops, int func)
 {
     HashNode hn;
     Patprog pprog;
@@ -2464,24 +2537,24 @@ bin_whence(char *nam, char **argv, char *ops, int func)
     char *cnam;
 
     /* Check some option information */
-    csh = ops['c'];
-    v   = ops['v'];
-    all = ops['a'];
-    wd  = ops['w'];
+    csh = OPT_ISSET(ops,'c');
+    v   = OPT_ISSET(ops,'v');
+    all = OPT_ISSET(ops,'a');
+    wd  = OPT_ISSET(ops,'w');
 
-    if (ops['w'])
+    if (OPT_ISSET(ops,'w'))
 	printflags |= PRINT_WHENCE_WORD;
-    else if (ops['c'])
+    else if (OPT_ISSET(ops,'c'))
 	printflags |= PRINT_WHENCE_CSH;
-    else if (ops['v'])
+    else if (OPT_ISSET(ops,'v'))
 	printflags |= PRINT_WHENCE_VERBOSE;
     else
 	printflags |= PRINT_WHENCE_SIMPLE;
-    if (ops['f'])
+    if (OPT_ISSET(ops,'f'))
 	printflags |= PRINT_WHENCE_FUNCDEF;
 
     /* With -m option -- treat arguments as a glob patterns */
-    if (ops['m']) {
+    if (OPT_ISSET(ops,'m')) {
 	for (; *argv; argv++) {
 	    /* parse the pattern */
 	    tokenize(*argv);
@@ -2492,7 +2565,7 @@ bin_whence(char *nam, char **argv, char *ops, int func)
 		continue;
 	    }
 	    queue_signals();
-	    if (!ops['p']) {
+	    if (!OPT_ISSET(ops,'p')) {
 		/* -p option is for path search only.    *
 		 * We're not using it, so search for ... */
 
@@ -2528,7 +2601,7 @@ bin_whence(char *nam, char **argv, char *ops, int func)
     for (; *argv; argv++) {
 	informed = 0;
 
-	if (!ops['p']) {
+	if (!OPT_ISSET(ops,'p')) {
 	    /* Look for alias */
 	    if ((hn = aliastab->getnode(aliastab, *argv))) {
 		aliastab->printnode(hn, printflags);
@@ -2585,7 +2658,7 @@ bin_whence(char *nam, char **argv, char *ops, int func)
 			if (v && !csh)
 			    zputs(*argv, stdout), fputs(" is ", stdout);
 			zputs(buf, stdout);
-			if (ops['s'])
+			if (OPT_ISSET(ops,'s'))
 			    print_if_link(buf);
 			fputc('\n', stdout);
 		    }
@@ -2606,7 +2679,7 @@ bin_whence(char *nam, char **argv, char *ops, int func)
 		if (v && !csh)
 		    zputs(*argv, stdout), fputs(" is ", stdout);
 		zputs(cnam, stdout);
-		if (ops['s'])
+		if (OPT_ISSET(ops,'s'))
 		    print_if_link(cnam);
 		fputc('\n', stdout);
 	    }
@@ -2643,7 +2716,7 @@ bin_whence(char *nam, char **argv, char *ops, int func)
 
 /**/
 int
-bin_hash(char *name, char **argv, char *ops, int func)
+bin_hash(char *name, char **argv, Options ops, int func)
 {
     HashTable ht;
     Patprog pprog;
@@ -2651,12 +2724,12 @@ bin_hash(char *name, char **argv, char *ops, int func)
     int returnval = 0;
     int printflags = 0;
 
-    if (ops['d'])
+    if (OPT_ISSET(ops,'d'))
 	ht = nameddirtab;
     else
 	ht = cmdnamtab;
 
-    if (ops['r'] || ops['f']) {
+    if (OPT_ISSET(ops,'r') || OPT_ISSET(ops,'f')) {
 	/* -f and -r can't be used with any arguments */
 	if (*argv) {
 	    zwarnnam("hash", "too many arguments", NULL, 0);
@@ -2664,17 +2737,17 @@ bin_hash(char *name, char **argv, char *ops, int func)
 	}
 
 	/* empty the hash table */
-	if (ops['r'])
+	if (OPT_ISSET(ops,'r'))
 	    ht->emptytable(ht);
 
 	/* fill the hash table in a standard way */
-	if (ops['f'])
+	if (OPT_ISSET(ops,'f'))
 	    ht->filltable(ht);
 
 	return 0;
     }
 
-    if (ops['L']) printflags |= PRINT_LIST;
+    if (OPT_ISSET(ops,'L')) printflags |= PRINT_LIST;
 
     /* Given no arguments, display current hash table. */
     if (!*argv) {
@@ -2687,7 +2760,7 @@ bin_hash(char *name, char **argv, char *ops, int func)
     queue_signals();
     while (*argv) {
 	void *hn;
-	if (ops['m']) {
+	if (OPT_ISSET(ops,'m')) {
 	    /* with the -m option, treat the argument as a glob pattern */
 	    tokenize(*argv);  /* expand */
 	    if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) {
@@ -2705,7 +2778,7 @@ bin_hash(char *name, char **argv, char *ops, int func)
 	    } else {
 		/* The argument is of the form foo=bar, *
 		 * so define an entry for the table.    */
-		if(ops['d']) {
+		if(OPT_ISSET(ops,'d')) {
 		    Nameddir nd = hn = zcalloc(sizeof *nd);
 		    nd->flags = 0;
 		    nd->dir = ztrdup(asg->value);
@@ -2715,13 +2788,13 @@ bin_hash(char *name, char **argv, char *ops, int func)
 		    cn->u.cmd = ztrdup(asg->value);
 		}
 		ht->addnode(ht, ztrdup(asg->name), hn);
-		if(ops['v'])
+		if(OPT_ISSET(ops,'v'))
 		    ht->printnode(hn, 0);
 	    }
 	} else if (!(hn = ht->getnode2(ht, asg->name))) {
 	    /* With no `=value' part to the argument, *
 	     * work out what it ought to be.          */
-	    if(ops['d']) {
+	    if(OPT_ISSET(ops,'d')) {
 		if(!getnameddir(asg->name)) {
 		    zwarnnam(name, "no such directory name: %s", asg->name, 0);
 		    returnval = 1;
@@ -2732,9 +2805,9 @@ bin_hash(char *name, char **argv, char *ops, int func)
 		    returnval = 1;
 		}
 	    }
-	    if(ops['v'] && (hn = ht->getnode2(ht, asg->name)))
+	    if(OPT_ISSET(ops,'v') && (hn = ht->getnode2(ht, asg->name)))
 		ht->printnode(hn, 0);
-	} else if(ops['v'])
+	} else if(OPT_ISSET(ops,'v'))
 	    ht->printnode(hn, 0);
 	argv++;
     }
@@ -2746,7 +2819,7 @@ bin_hash(char *name, char **argv, char *ops, int func)
 
 /**/
 int
-bin_unhash(char *name, char **argv, char *ops, int func)
+bin_unhash(char *name, char **argv, Options ops, int func)
 {
     HashTable ht;
     HashNode hn, nhn;
@@ -2755,18 +2828,18 @@ bin_unhash(char *name, char **argv, char *ops, int func)
     int i;
 
     /* Check which hash table we are working with. */
-    if (ops['d'])
+    if (OPT_ISSET(ops,'d'))
 	ht = nameddirtab;	/* named directories */
-    else if (ops['f'])
+    else if (OPT_ISSET(ops,'f'))
 	ht = shfunctab;		/* shell functions   */
-    else if (ops['a'])
+    else if (OPT_ISSET(ops,'a'))
 	ht = aliastab;		/* aliases           */
     else
 	ht = cmdnamtab;		/* external commands */
 
     /* With -m option, treat arguments as glob patterns. *
      * "unhash -m '*'" is legal, but not recommended.    */
-    if (ops['m']) {
+    if (OPT_ISSET(ops,'m')) {
 	for (; *argv; argv++) {
 	    /* expand argument */
 	    tokenize(*argv);
@@ -2816,7 +2889,7 @@ bin_unhash(char *name, char **argv, char *ops, int func)
 
 /**/
 int
-bin_alias(char *name, char **argv, char *ops, int func)
+bin_alias(char *name, char **argv, Options ops, int func)
 {
     Alias a;
     Patprog pprog;
@@ -2826,21 +2899,22 @@ bin_alias(char *name, char **argv, char *ops, int func)
     int printflags = 0;
 
     /* Did we specify the type of alias? */
-    if (ops['r'] || ops['g']) {
-	if (ops['r'] && ops['g']) {
+    if (OPT_ISSET(ops,'r') || OPT_ISSET(ops,'g')) {
+	if (OPT_ISSET(ops,'r') && OPT_ISSET(ops,'g')) {
 	    zwarnnam(name, "illegal combination of options", NULL, 0);
 	    return 1;
 	}
 	haveflags = 1;
-	if (ops['g'])
+	if (OPT_ISSET(ops,'g'))
 	    flags1 |= ALIAS_GLOBAL;
 	else
 	    flags2 |= ALIAS_GLOBAL;
     }
 
-    if (ops['L'])
+    if (OPT_ISSET(ops,'L'))
 	printflags |= PRINT_LIST;
-    else if (ops['r'] == 2 || ops['g'] == 2 || ops['m'] == 2 || ops['+'])
+    else if (OPT_PLUS(ops,'r') || OPT_PLUS(ops,'g')|| OPT_PLUS(ops,'m') ||
+	     OPT_ISSET(ops,'+'))
 	printflags |= PRINT_NAMEONLY;
 
     /* In the absence of arguments, list all aliases.  If a command *
@@ -2854,7 +2928,7 @@ bin_alias(char *name, char **argv, char *ops, int func)
 
     /* With the -m option, treat the arguments as *
      * glob patterns of aliases to display.       */
-    if (ops['m']) {
+    if (OPT_ISSET(ops,'m')) {
 	for (; *argv; argv++) {
 	    tokenize(*argv);  /* expand argument */
 	    if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) {
@@ -2875,7 +2949,7 @@ bin_alias(char *name, char **argv, char *ops, int func)
     /* Take arguments literally.  Don't glob */
     queue_signals();
     while ((asg = getasg(*argv++))) {
-	if (asg->value && !ops['L']) {
+	if (asg->value && !OPT_ISSET(ops,'L')) {
 	    /* The argument is of the form foo=bar and we are not *
 	     * forcing a listing with -L, so define an alias      */
 	    aliastab->addnode(aliastab, ztrdup(asg->name),
@@ -2883,8 +2957,8 @@ bin_alias(char *name, char **argv, char *ops, int func)
 	} else if ((a = (Alias) aliastab->getnode(aliastab, asg->name))) {
 	    /* display alias if appropriate */
 	    if (!haveflags ||
-		(ops['r'] && !(a->flags & ALIAS_GLOBAL)) ||
-		(ops['g'] &&  (a->flags & ALIAS_GLOBAL)))
+		(OPT_ISSET(ops,'r') && !(a->flags & ALIAS_GLOBAL)) ||
+		(OPT_ISSET(ops,'g') &&  (a->flags & ALIAS_GLOBAL)))
 		aliastab->printnode((HashNode) a, printflags);
 	} else
 	    returnval = 1;
@@ -2900,7 +2974,7 @@ bin_alias(char *name, char **argv, char *ops, int func)
 
 /**/
 int
-bin_true(char *name, char **argv, char *ops, int func)
+bin_true(char *name, char **argv, Options ops, int func)
 {
     return 0;
 }
@@ -2909,7 +2983,7 @@ bin_true(char *name, char **argv, char *ops, int func)
 
 /**/
 int
-bin_false(char *name, char **argv, char *ops, int func)
+bin_false(char *name, char **argv, Options ops, int func)
 {
     return 1;
 }
@@ -2929,7 +3003,7 @@ mod_export LinkList bufstack;
 
 /**/
 int
-bin_print(char *name, char **args, char *ops, int func)
+bin_print(char *name, char **args, Options ops, int func)
 {
     int flen, width, prec, type, argc, n, narg;
     int nnl = 0, ret = 0, maxarg = 0;
@@ -2950,14 +3024,18 @@ bin_print(char *name, char **args, char *ops, int func)
     zulong zulongval;
     char *stringval;
     
-    if (func == BIN_PRINTF) auxdata = *args++;
-    if (auxdata)
-	fmt = getkeystring(auxdata, &flen, ops['b'] ? 2 : 0, &nnl);
+    if (func == BIN_PRINTF)
+	fmt = *args++;
+    else if (OPT_HASARG(ops,'f'))
+	fmt = OPT_ARG(ops,'f');
+    if (fmt)
+	fmt = getkeystring(fmt, &flen, OPT_ISSET(ops,'b') ? 2 : 0, &nnl);
+
     first = args;
     
     /* -m option -- treat the first argument as a pattern and remove
      * arguments not matching */
-    if (ops['m']) {
+    if (OPT_ISSET(ops,'m')) {
 	Patprog pprog;
 	char **t, **p;
 	
@@ -2979,13 +3057,16 @@ bin_print(char *name, char **args, char *ops, int func)
     len = (int *) hcalloc(argc * sizeof(int));
     for(n = 0; n < argc; n++) {
 	/* first \ sequences */
-	if (fmt || (!ops['e'] && (ops['R'] || ops['r'] || ops['E'])))
+	if (fmt || 
+	    (!OPT_ISSET(ops,'e') && 
+	     (OPT_ISSET(ops,'R') || OPT_ISSET(ops,'r') || OPT_ISSET(ops,'E'))))
 	    unmetafy(args[n], &len[n]);
 	else
-	    args[n] = getkeystring(args[n], &len[n], ops['b'] ? 2 :
-				   (func != BIN_ECHO && !ops['e']), &nnl);
+	    args[n] = getkeystring(args[n], &len[n], OPT_ISSET(ops,'b') ? 2 :
+				   (func != BIN_ECHO && !OPT_ISSET(ops,'e')),
+				   &nnl);
 	/* -P option -- interpret as a prompt sequence */
-	if(ops['P']) {
+	if(OPT_ISSET(ops,'P')) {
 	    /*
 	     * promptexpand uses permanent storage: to avoid
 	     * messy memory management, stick it on the heap
@@ -2997,7 +3078,7 @@ bin_print(char *name, char **args, char *ops, int func)
 	    free(str);
 	}
 	/* -D option -- interpret as a directory, and use ~ */
-	if(ops['D']) {
+	if(OPT_ISSET(ops,'D')) {
 	    Nameddir d;
 	    
 	    queue_signals();
@@ -3014,48 +3095,57 @@ bin_print(char *name, char **args, char *ops, int func)
     }
     
     /* -u and -p -- output to other than standard output */
-    if (ops['u'] || ops['p']) {
+    if (OPT_HASARG(ops,'u') || OPT_ISSET(ops,'p')) {
 	int fd;
 
-	if (ops['u']) {
-	    for (fd = 0; fd < 10; fd++)
-		if (ops[fd + '0'])
-		    break;
-	    if (fd == 10)
-		fd = 0;
-	} else
+	if (OPT_ISSET(ops, 'p'))
 	    fd = coprocout;
+	else {
+	    char *argptr = OPT_ARG(ops,'u'), *eptr;
+	    /* Handle undocumented feature that -up worked */
+	    if (!strcmp(argptr, "p")) {
+		fd = coprocout;
+	    } else {
+		fd = (int)zstrtol(argptr, &eptr, 10);
+		if (*eptr) {
+		    zwarnnam(name, "number expected after -u: %s", argptr, 0);
+		    return 1;
+		}
+	    }
+	}
+
 	if ((fd = dup(fd)) < 0) {
-	    zwarnnam(name, "bad file number", NULL, 0);
+	    zwarnnam(name, "bad file number: %d", NULL, fd);
 	    return 1;
 	}
 	if ((fout = fdopen(fd, "w")) == 0) {
-	    zwarnnam(name, "bad mode on fd", NULL, 0);
+	    close(fd);
+	    zwarnnam(name, "bad mode on fd %d", NULL, fd);
 	    return 1;
 	}
     }
 
     /* -o and -O -- sort the arguments */
-    if (ops['o']) {
+    if (OPT_ISSET(ops,'o')) {
 	if (fmt && !*args) return 0;
-	if (ops['i'])
+	if (OPT_ISSET(ops,'i'))
 	    qsort(args, arrlen(args), sizeof(char *), cstrpcmp);
 	else
 	    qsort(args, arrlen(args), sizeof(char *), strpcmp);
-    } else if (ops['O']) {
+    } else if (OPT_ISSET(ops,'O')) {
 	if (fmt && !*args) return 0;
-	if (ops['i'])
+	if (OPT_ISSET(ops,'i'))
 	    qsort(args, arrlen(args), sizeof(char *), invcstrpcmp);
 	else
 	    qsort(args, arrlen(args), sizeof(char *), invstrpcmp);
     }
     /* after sorting arguments, recalculate lengths */
-    if(ops['o'] || ops['O'])
+    if(OPT_ISSET(ops,'o') || OPT_ISSET(ops,'O'))
 	for(n = 0; n < argc; n++)
 	    len[n] = strlen(args[n]);
 
     /* -c -- output in columns */
-    if (ops['c']) {
+    if (OPT_ISSET(ops,'c')) {
 	int l, nc, nr, sc, n, t, i;
 	char **ap;
 
@@ -3079,7 +3169,7 @@ bin_print(char *name, char **args, char *ops, int func)
 		    for (; l < sc; l++)
 			fputc(' ', fout);
 	    } while (*ap);
-	    fputc(ops['N'] ? '\0' : '\n', fout);
+	    fputc(OPT_ISSET(ops,'N') ? '\0' : '\n', fout);
 	}
 	/* Testing EBADF special-cases >&- redirections */
 	if ((fout != stdout) ? (fclose(fout) != 0) :
@@ -3093,14 +3183,14 @@ bin_print(char *name, char **args, char *ops, int func)
     /* normal output */
     if (!fmt) {
 	/* -z option -- push the arguments onto the editing buffer stack */
-	if (ops['z']) {
+	if (OPT_ISSET(ops,'z')) {
 	    queue_signals();
 	    zpushnode(bufstack, sepjoin(args, NULL, 0));
 	    unqueue_signals();
 	    return 0;
 	}
 	/* -s option -- add the arguments to the history list */
-	if (ops['s']) {
+	if (OPT_ISSET(ops,'s')) {
 	    int nwords = 0, nlen, iwords;
 	    char **pargs = args;
 
@@ -3130,10 +3220,11 @@ bin_print(char *name, char **args, char *ops, int func)
 	for (; *args; args++, len++) {
 	    fwrite(*args, *len, 1, fout);
 	    if (args[1])
-		fputc(ops['l'] ? '\n' : ops['N'] ? '\0' : ' ', fout);
+		fputc(OPT_ISSET(ops,'l') ? '\n' : 
+		      OPT_ISSET(ops,'N') ? '\0' : ' ', fout);
 	}
-	if (!(ops['n'] || nnl))
-	    fputc(ops['N'] ? '\0' : '\n', fout);
+	if (!(OPT_ISSET(ops,'n') || nnl))
+	    fputc(OPT_ISSET(ops,'N') ? '\0' : '\n', fout);
 	/* Testing EBADF special-cases >&- redirections */
 	if ((fout != stdout) ? (fclose(fout) != 0) :
 	    (fflush(fout) != 0 && errno != EBADF)) {
@@ -3143,7 +3234,7 @@ bin_print(char *name, char **args, char *ops, int func)
 	return ret;
     }
     
-    if (ops['z'] || ops['s']) {
+    if (OPT_ISSET(ops,'z') || OPT_ISSET(ops,'s')) {
 #ifdef HAVE_OPEN_MEMSTREAM
     	if ((fout = open_memstream(&buf, &mcount)) == NULL)
 	    zwarnnam(name, "open_memstream failed", NULL, 0);
@@ -3291,7 +3382,8 @@ bin_print(char *name, char **args, char *ops, int func)
 	    case 'b':
 		if (curarg) {
 		    int l;
-		    char *b = getkeystring(curarg, &l, ops['b'] ? 2 : 0, &nnl);
+		    char *b = getkeystring(curarg, &l, 
+					   OPT_ISSET(ops,'b') ? 2 : 0, &nnl);
 		    /* handle width/precision here and use fwrite so that
 		     * nul characters can be output */
 		    if (prec >= 0 && prec < l) l = prec;
@@ -3402,9 +3494,9 @@ bin_print(char *name, char **args, char *ops, int func)
 
     	if (maxarg) args = first + maxarg;
 	/* if there are remaining args, reuse format string */
-    } while (*args && args != first && !ops['r']);
+    } while (*args && args != first && !OPT_ISSET(ops,'r'));
 
-    if (ops['z'] || ops['s']) {
+    if (OPT_ISSET(ops,'z') || OPT_ISSET(ops,'s')) {
 #ifdef HAVE_OPEN_MEMSTREAM
 	putc(0, fout);
 	fflush(fout);
@@ -3416,7 +3508,7 @@ bin_print(char *name, char **args, char *ops, int func)
 	buf[count] = '\0';
 #endif
 	queue_signals();
-	if (ops['z']) {
+	if (OPT_ISSET(ops,'z')) {
 	    zpushnode(bufstack, buf);
 	} else {
 	    ent = prepnexthistent();
@@ -3442,7 +3534,7 @@ bin_print(char *name, char **args, char *ops, int func)
 
 /**/
 int
-bin_shift(char *name, char **argv, char *ops, int func)
+bin_shift(char *name, char **argv, Options ops, int func)
 {
     int num = 1, l, ret = 0;
     char **s;
@@ -3493,7 +3585,7 @@ int optcind;
 
 /**/
 int
-bin_getopts(char *name, char **argv, char *ops, int func)
+bin_getopts(char *name, char **argv, Options ops, int func)
 {
     int lenstr, lenoptstr, quiet, lenoptbuf;
     char *optstr = unmetafy(*argv++, &lenoptstr), *var = *argv++;
@@ -3600,7 +3692,7 @@ exit_pending;
 
 /**/
 int
-bin_break(char *name, char **argv, char *ops, int func)
+bin_break(char *name, char **argv, Options ops, int func)
 {
     int num = lastval, nump = 0;
 
@@ -3746,7 +3838,7 @@ zexit(int val, int from_where)
 
 /**/
 int
-bin_dot(char *name, char **argv, char *ops, int func)
+bin_dot(char *name, char **argv, Options ops, int func)
 {
     char **old, *old0 = NULL;
     int ret, diddot = 0, dotdot = 0;
@@ -3824,10 +3916,10 @@ bin_dot(char *name, char **argv, char *ops, int func)
 
 /**/
 int
-bin_emulate(char *nam, char **argv, char *ops, int func)
+bin_emulate(char *nam, char **argv, Options ops, int func)
 {
-    emulate(*argv, ops['R']);
-    if (ops['L'])
+    emulate(*argv, OPT_ISSET(ops,'R'));
+    if (OPT_ISSET(ops,'L'))
 	opts[LOCALOPTIONS] = opts[LOCALTRAPS] = 1;
     return 0;
 }
@@ -3836,7 +3928,7 @@ bin_emulate(char *nam, char **argv, char *ops, int func)
 
 /**/
 int
-bin_eval(char *nam, char **argv, char *ops, int func)
+bin_eval(char *nam, char **argv, Options ops, int func)
 {
     Eprog prog;
     char *oscriptname = scriptname;
@@ -3873,7 +3965,7 @@ file/buffer. */
 
 /**/
 int
-bin_read(char *name, char **args, char *ops, int func)
+bin_read(char *name, char **args, Options ops, int func)
 {
     char *reply, *readpmpt;
     int bsiz, c = 0, gotnl = 0, al = 0, first, nchars = 1, bslash, keys = 0;
@@ -3887,26 +3979,30 @@ bin_read(char *name, char **args, char *ops, int func)
     char d;
 
 
-    if ((ops['k'] || ops['b']) && *args && idigit(**args)) {
-	if (!(nchars = atoi(*args)))
-	    nchars = 1;
-	args++;
+    if ((OPT_HASARG(ops,c='k') || OPT_HASARG(ops,c='b'))) {
+	char *eptr, *optarg = OPT_ARG(ops,c);
+	nchars = (int)zstrtol(optarg, &eptr, 10);
+	if (*eptr) {
+	    zwarnnam(name, "number expected after -%c: %s", optarg, c);
+	    return 1;
+	}
     }
     /* This `*args++ : *args' looks a bit weird, but it works around a bug
      * in gcc-2.8.1 under DU 4.0. */
     firstarg = (*args && **args == '?' ? *args++ : *args);
-    reply = *args ? *args++ : ops['A'] ? "reply" : "REPLY";
+    reply = *args ? *args++ : OPT_ISSET(ops,'A') ? "reply" : "REPLY";
 
-    if (ops['A'] && *args) {
+    if (OPT_ISSET(ops,'A') && *args) {
 	zwarnnam(name, "only one array argument allowed", NULL, 0);
 	return 1;
     }
 
     /* handle compctl case */
-    if(ops['l'] || ops['c'])
+    if(OPT_ISSET(ops,'l') || OPT_ISSET(ops,'c'))
 	return compctlread(name, args, ops, reply);
 
-    if ((ops['k'] && !ops['u'] && !ops['p']) || ops['q']) {
+    if ((OPT_ISSET(ops,'k') && !OPT_ISSET(ops,'u') && 
+	 !OPT_ISSET(ops,'p')) || OPT_ISSET(ops,'q')) {
 	if (!zleactive) {
 	    if (SHTTY == -1) {
 		/* need to open /dev/tty specially */
@@ -3930,24 +4026,38 @@ bin_read(char *name, char **args, char *ops, int func)
 		gettyinfo(&shttyinfo);
 	    /* attach to the tty */
 	    attachtty(mypgrp);
-	    if (!isem && ops['k'])
+	    if (!isem && OPT_ISSET(ops,'k'))
 		setcbreak();
 	    readfd = SHTTY;
 	}
 	keys = 1;
-    } else if (ops['u'] && !ops['p']) {
-	/* -u means take input from the specified file descriptor. *
-	 * -up means take input from the coprocess.                */
-	for (readfd = 9; readfd && !ops[readfd + '0']; --readfd);
+    } else if (OPT_HASARG(ops,'u') && !OPT_ISSET(ops,'p')) {
+	/* -u means take input from the specified file descriptor. */
+	char *eptr, *argptr = OPT_ARG(ops,'u');
+	/* The old code handled -up, but that was never documented. Still...*/
+	if (!strcmp(argptr, "p")) {
+	    readfd = coprocin;
+	} else {
+	    readfd = (int)zstrtol(argptr, &eptr, 10);
+	    if (*eptr) {
+		zwarnnam(name, "number expected after -u: %s", argptr, 0);
+		return 1;
+	    }
+	}
+#if 0
+	/* This code is left as a warning to future generations --- pws. */
+	for (readfd = 9; readfd && !OPT_ISSET(ops,readfd + '0'); --readfd);
+#endif
 	izle = 0;
-    } else if (ops['p']) {
+    } else if (OPT_ISSET(ops,'p')) {
 	readfd = coprocin;
 	izle = 0;
     } else
 	readfd = izle = 0;
 
-    if (ops['t'] && !read_poll(readfd, &readchar, keys && !zleactive)) {
-	if (ops['k'] && !zleactive && !isem)
+    if (OPT_ISSET(ops,'t') && 
+	!read_poll(readfd, &readchar, keys && !zleactive)) {
+	if (OPT_ISSET(ops,'k') && !zleactive && !isem)
 	    settyinfo(&shttyinfo);
 	if (haso) {
 	    fclose(shout);
@@ -3956,7 +4066,7 @@ bin_read(char *name, char **args, char *ops, int func)
 	}
 	return 1;
     }
-    if (ops['s'] && SHTTY != -1) {
+    if (OPT_ISSET(ops,'s') && SHTTY != -1) {
 	struct ttyinfo ti;
 	gettyinfo(&ti);
 	saveti = ti;
@@ -3983,7 +4093,7 @@ bin_read(char *name, char **args, char *ops, int func)
     }
 
     /* option -k means read only a given number of characters (default 1) */
-    if (ops['k']) {
+    if (OPT_ISSET(ops,'k')) {
 	/* allocate buffer space for result */
 	bptr = buf = (char *)zalloc(nchars+1);
 
@@ -4010,7 +4120,7 @@ bin_read(char *name, char **args, char *ops, int func)
 	    }
 	} while (nchars > 0);
 	
-	if (!izle && !ops['u'] && !ops['p']) {
+	if (!izle && !OPT_ISSET(ops,'u') && !OPT_ISSET(ops,'p')) {
 	    /* dispose of result appropriately, etc. */
 	    if (isem)
 		while (val > 0 && read(SHTTY, &d, 1) == 1 && d != '\n');
@@ -4025,9 +4135,9 @@ bin_read(char *name, char **args, char *ops, int func)
 	    }
 	}
 
-	if (ops['e'] || ops['E'])
+	if (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E'))
 	    fwrite(buf, bptr - buf, 1, stdout);
-	if (!ops['e'])
+	if (!OPT_ISSET(ops,'e'))
 	    setsparam(reply, metafy(buf, bptr - buf, META_REALLOC));
 	else
 	    zfree(buf, bptr - buf + 1);
@@ -4037,7 +4147,7 @@ bin_read(char *name, char **args, char *ops, int func)
     }
 
     /* option -q means get one character, and interpret it as a Y or N */
-    if (ops['q']) {
+    if (OPT_ISSET(ops,'q')) {
 	char readbuf[2];
 
 	/* set up the buffer */
@@ -4058,9 +4168,9 @@ bin_read(char *name, char **args, char *ops, int func)
 		SHTTY = -1;
 	    }
 	}
-	if (ops['e'] || ops['E'])
+	if (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E'))
 	    printf("%s\n", readbuf);
-	if (!ops['e'])
+	if (!OPT_ISSET(ops,'e'))
 	    setsparam(reply, ztrdup(readbuf));
 
 	if (resettty && SHTTY != -1)
@@ -4073,11 +4183,11 @@ bin_read(char *name, char **args, char *ops, int func)
        onto the last parameter.  If an array is specified, all the words become
        separate elements of the array. */
 
-    zbuforig = zbuf = (!ops['z']) ? NULL :
+    zbuforig = zbuf = (!OPT_ISSET(ops,'z')) ? NULL :
 	(nonempty(bufstack)) ? (char *) getlinknode(bufstack) : ztrdup("");
     first = 1;
     bslash = 0;
-    while (*args || (ops['A'] && !gotnl)) {
+    while (*args || (OPT_ISSET(ops,'A') && !gotnl)) {
 	sigset_t s = child_unblock();
 	buf = bptr = (char *)zalloc(bsiz = 64);
 	/* get input, a character at a time */
@@ -4108,7 +4218,7 @@ bin_read(char *name, char **args, char *ops, int func)
 		first |= !iwsep(c);
 		continue;
 	    }
-	    bslash = c == '\\' && !bslash && !ops['r'];
+	    bslash = c == '\\' && !bslash && !OPT_ISSET(ops,'r');
 	    if (bslash)
 		continue;
 	    first = 0;
@@ -4130,19 +4240,19 @@ bin_read(char *name, char **args, char *ops, int func)
 	    gotnl = 1;
 	*bptr = '\0';
 	/* dispose of word appropriately */
-	if (ops['e'] || ops['E']) {
+	if (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E')) {
 	    zputs(buf, stdout);
 	    putchar('\n');
 	}
-	if (!ops['e'] && (*buf || first)) {
-	    if (ops['A']) {
+	if (!OPT_ISSET(ops,'e') && (*buf || first)) {
+	    if (OPT_ISSET(ops,'A')) {
 		addlinknode(readll, buf);
 		al++;
 	    } else
 		setsparam(reply, buf);
 	} else
 	    free(buf);
-	if (!ops['A'])
+	if (!OPT_ISSET(ops,'A'))
 	    reply = *args++;
     }
     /* handle EOF */
@@ -4154,15 +4264,15 @@ bin_read(char *name, char **args, char *ops, int func)
 	}
     }
     /* final assignment (and display) of array parameter */
-    if (ops['A']) {
+    if (OPT_ISSET(ops,'A')) {
 	char **pp, **p = NULL;
 	LinkNode n;
 
-	p = (ops['e'] ? (char **)NULL
+	p = (OPT_ISSET(ops,'e') ? (char **)NULL
 	     : (char **)zalloc((al + 1) * sizeof(char *)));
 
 	for (pp = p, n = firstnode(readll); n; incnode(n)) {
-	    if (ops['e'] || ops['E']) {
+	    if (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E')) {
 		zputs((char *) getdata(n), stdout);
 		putchar('\n');
 	    }
@@ -4202,7 +4312,7 @@ bin_read(char *name, char **args, char *ops, int func)
 		    continue;
 		}
 	    }
-	    bslash = c == '\\' && !bslash && !ops['r'];
+	    bslash = c == '\\' && !bslash && !OPT_ISSET(ops,'r');
 	    if (bslash)
 		continue;
 	    if (imeta(c)) {
@@ -4226,11 +4336,11 @@ bin_read(char *name, char **args, char *ops, int func)
     if (resettty && SHTTY != -1)
 	settyinfo(&saveti);
     /* final assignment of reply, etc. */
-    if (ops['e'] || ops['E']) {
+    if (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E')) {
 	zputs(buf, stdout);
 	putchar('\n');
     }
-    if (!ops['e'])
+    if (!OPT_ISSET(ops,'e'))
 	setsparam(reply, buf);
     else
 	zsfree(buf);
@@ -4345,7 +4455,7 @@ testlex(void)
 
 /**/
 int
-bin_test(char *name, char **argv, char *ops, int func)
+bin_test(char *name, char **argv, Options ops, int func)
 {
     char **s;
     Eprog prog;
@@ -4400,7 +4510,7 @@ bin_test(char *name, char **argv, char *ops, int func)
 
 /**/
 int
-bin_times(char *name, char **argv, char *ops, int func)
+bin_times(char *name, char **argv, Options ops, int func)
 {
     struct tms buf;
 
@@ -4422,7 +4532,7 @@ bin_times(char *name, char **argv, char *ops, int func)
 
 /**/
 int
-bin_trap(char *name, char **argv, char *ops, int func)
+bin_trap(char *name, char **argv, Options ops, int func)
 {
     Eprog prog;
     char *arg, *s;
@@ -4498,11 +4608,11 @@ bin_trap(char *name, char **argv, char *ops, int func)
 
 /**/
 int
-bin_ttyctl(char *name, char **argv, char *ops, int func)
+bin_ttyctl(char *name, char **argv, Options ops, int func)
 {
-    if (ops['f'])
+    if (OPT_ISSET(ops,'f'))
 	ttyfrozen = 1;
-    else if (ops['u'])
+    else if (OPT_ISSET(ops,'u'))
 	ttyfrozen = 0;
     else
 	printf("tty is %sfrozen\n", ttyfrozen ? "" : "not ");
@@ -4513,7 +4623,7 @@ bin_ttyctl(char *name, char **argv, char *ops, int func)
 
 /**/
 int
-bin_let(char *name, char **argv, char *ops, int func)
+bin_let(char *name, char **argv, Options ops, int func)
 {
     zlong val = 0;
 
@@ -4531,7 +4641,7 @@ bin_let(char *name, char **argv, char *ops, int func)
 
 /**/
 int
-bin_umask(char *nam, char **args, char *ops, int func)
+bin_umask(char *nam, char **args, Options ops, int func)
 {
     mode_t um;
     char *s = *args;
@@ -4541,7 +4651,7 @@ bin_umask(char *nam, char **args, char *ops, int func)
     umask(um);
     /* No arguments means to display the current setting. */
     if (!s) {
-	if (ops['S']) {
+	if (OPT_ISSET(ops,'S')) {
 	    char *who = "ugo";
 
 	    while (*who) {
@@ -4642,7 +4752,7 @@ bin_umask(char *nam, char **args, char *ops, int func)
 
 /**/
 mod_export int
-bin_notavail(char *nam, char **argv, char *ops, int func)
+bin_notavail(char *nam, char **argv, Options ops, int func)
 {
     zwarnnam(nam, "not available on this system", NULL, 0);
     return 1;