]> git.saurik.com Git - apple/shell_cmds.git/blobdiff - sh/expand.c
shell_cmds-203.tar.gz
[apple/shell_cmds.git] / sh / expand.c
index 193d65177e74c81fb64fa7f5d04a00ed9bbeb7eb..d29237be21fd14a000f613d2e8dc9e49e63f909f 100644 (file)
@@ -3,6 +3,8 @@
  *     The Regents of the University of California.  All rights reserved.
  * Copyright (c) 1997-2005
  *     Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ * Copyright (c) 2010-2015
+ *     Jilles Tjoelker <jilles@stack.nl>.  All rights reserved.
  *
  * This code is derived from software contributed to Berkeley by
  * Kenneth Almquist.
@@ -38,7 +40,7 @@ static char sccsid[] = "@(#)expand.c  8.5 (Berkeley) 5/15/95";
 #endif
 #endif /* not lint */
 #include <sys/cdefs.h>
-__FBSDID("$FreeBSD$");
+__FBSDID("$FreeBSD: head/bin/sh/expand.c 303586 2016-07-31 13:11:34Z jilles $");
 
 #include <sys/types.h>
 #include <sys/time.h>
@@ -79,46 +81,63 @@ __FBSDID("$FreeBSD$");
 #include "show.h"
 #include "builtins.h"
 
-/*
- * Structure specifying which parts of the string should be searched
- * for IFS characters.
- */
+enum wordstate { WORD_IDLE, WORD_WS_DELIMITED, WORD_QUOTEMARK };
 
-struct ifsregion {
-       struct ifsregion *next; /* next region in list */
-       int begoff;             /* offset of start of region */
-       int endoff;             /* offset of end of region */
-       int inquotes;           /* search for nul bytes only */
+struct worddest {
+       struct arglist *list;
+       enum wordstate state;
 };
 
-
 static char *expdest;                  /* output of current string */
 static struct nodelist *argbackq;      /* list of back quote expressions */
-static struct ifsregion ifsfirst;      /* first struct in list of ifs regions */
-static struct ifsregion *ifslastp;     /* last struct in list */
-static struct arglist exparg;          /* holds expanded arg list */
-
-static char *argstr(char *, int);
-static char *exptilde(char *, int);
-static char *expari(char *);
-static void expbackq(union node *, int, int);
-static int subevalvar(char *, char *, int, int, int, int, int);
-static char *evalvar(char *, int);
+
+static const char *argstr(const char *, int, struct worddest *);
+static const char *exptilde(const char *, int);
+static const char *expari(const char *, int, struct worddest *);
+static void expbackq(union node *, int, int, struct worddest *);
+static void subevalvar_trim(const char *, int, int, int);
+static int subevalvar_misc(const char *, const char *, int, int, int);
+static const char *evalvar(const char *, int, struct worddest *);
 static int varisset(const char *, int);
-static void strtodest(const char *, int, int, int);
-static void varvalue(const char *, int, int, int);
-static void recordregion(int, int, int);
-static void removerecordregions(int);
-static void ifsbreakup(char *, struct arglist *);
-static void expandmeta(struct strlist *);
-static void expmeta(char *, char *);
-static void addfname(char *);
-static struct strlist *expsort(struct strlist *);
-static struct strlist *msort(struct strlist *, int);
-static int patmatch(const char *, const char *, int);
-static char *cvtnum(int, char *);
+static void strtodest(const char *, int, int, int, struct worddest *);
+static void reprocess(int, int, int, int, struct worddest *);
+static void varvalue(const char *, int, int, int, struct worddest *);
+static void expandmeta(char *, struct arglist *);
+static void expmeta(char *, char *, struct arglist *);
+static int expsortcmp(const void *, const void *);
+static int patmatch(const char *, const char *);
+static void cvtnum(int, char *);
 static int collate_range_cmp(wchar_t, wchar_t);
 
+void
+emptyarglist(struct arglist *list)
+{
+
+       list->args = list->smallarg;
+       list->count = 0;
+       list->capacity = sizeof(list->smallarg) / sizeof(list->smallarg[0]);
+}
+
+void
+appendarglist(struct arglist *list, char *str)
+{
+       char **newargs;
+       int newcapacity;
+
+       if (list->count >= list->capacity) {
+               newcapacity = list->capacity * 2;
+               if (newcapacity < 16)
+                       newcapacity = 16;
+               if (newcapacity > INT_MAX / (int)sizeof(newargs[0]))
+                       error("Too many entries in arglist");
+               newargs = stalloc(newcapacity * sizeof(newargs[0]));
+               memcpy(newargs, list->args, list->count * sizeof(newargs[0]));
+               list->args = newargs;
+               list->capacity = newcapacity;
+       }
+       list->args[list->count++] = str;
+}
+
 static int
 collate_range_cmp(wchar_t c1, wchar_t c2)
 {
@@ -142,6 +161,53 @@ stputs_quotes(const char *data, const char *syntax, char *p)
 }
 #define STPUTS_QUOTES(data, syntax, p) p = stputs_quotes((data), syntax, p)
 
+static char *
+nextword(char c, int flag, char *p, struct worddest *dst)
+{
+       int is_ws;
+
+       is_ws = c == '\t' || c == '\n' || c == ' ';
+       if (p != stackblock() || (is_ws ? dst->state == WORD_QUOTEMARK :
+           dst->state != WORD_WS_DELIMITED) || c == '\0') {
+               STPUTC('\0', p);
+               if (flag & EXP_GLOB)
+                       expandmeta(grabstackstr(p), dst->list);
+               else
+                       appendarglist(dst->list, grabstackstr(p));
+               dst->state = is_ws ? WORD_WS_DELIMITED : WORD_IDLE;
+       } else if (!is_ws && dst->state == WORD_WS_DELIMITED)
+               dst->state = WORD_IDLE;
+       /* Reserve space while the stack string is empty. */
+       appendarglist(dst->list, NULL);
+       dst->list->count--;
+       STARTSTACKSTR(p);
+       return p;
+}
+#define NEXTWORD(c, flag, p, dstlist) p = nextword(c, flag, p, dstlist)
+
+static char *
+stputs_split(const char *data, const char *syntax, int flag, char *p,
+    struct worddest *dst)
+{
+       const char *ifs;
+       char c;
+
+       ifs = ifsset() ? ifsval() : " \t\n";
+       while (*data) {
+               CHECKSTRSPACE(2, p);
+               c = *data++;
+               if (strchr(ifs, c) != NULL) {
+                       NEXTWORD(c, flag, p, dst);
+                       continue;
+               }
+               if (flag & EXP_GLOB && syntax[(int)c] == CCTL)
+                       USTPUTC(CTLESC, p);
+               USTPUTC(c, p);
+       }
+       return (p);
+}
+#define STPUTS_SPLIT(data, syntax, flag, p, dst) p = stputs_split((data), syntax, flag, p, dst)
+
 /*
  * Perform expansions on an argument, placing the resulting list of arguments
  * in arglist.  Parameter expansion, command substitution and arithmetic
@@ -157,45 +223,31 @@ stputs_quotes(const char *data, const char *syntax, char *p)
 void
 expandarg(union node *arg, struct arglist *arglist, int flag)
 {
-       struct strlist *sp;
-       char *p;
+       struct worddest exparg;
 
+       if (fflag)
+               flag &= ~EXP_GLOB;
        argbackq = arg->narg.backquote;
+       exparg.list = arglist;
+       exparg.state = WORD_IDLE;
        STARTSTACKSTR(expdest);
-       ifsfirst.next = NULL;
-       ifslastp = NULL;
-       argstr(arg->narg.text, flag);
+       argstr(arg->narg.text, flag, &exparg);
        if (arglist == NULL) {
                STACKSTRNUL(expdest);
                return;                 /* here document expanded */
        }
-       STPUTC('\0', expdest);
-       p = grabstackstr(expdest);
-       exparg.lastp = &exparg.list;
-       if (flag & EXP_FULL) {
-               ifsbreakup(p, &exparg);
-               *exparg.lastp = NULL;
-               exparg.lastp = &exparg.list;
-               expandmeta(exparg.list);
-       } else {
-               sp = (struct strlist *)stalloc(sizeof (struct strlist));
-               sp->text = p;
-               *exparg.lastp = sp;
-               exparg.lastp = &sp->next;
-       }
-       while (ifsfirst.next != NULL) {
-               struct ifsregion *ifsp;
-               INTOFF;
-               ifsp = ifsfirst.next->next;
-               ckfree(ifsfirst.next);
-               ifsfirst.next = ifsp;
-               INTON;
-       }
-       *exparg.lastp = NULL;
-       if (exparg.list) {
-               *arglist->lastp = exparg.list;
-               arglist->lastp = exparg.lastp;
+       if ((flag & EXP_SPLIT) == 0 || expdest != stackblock() ||
+           exparg.state == WORD_QUOTEMARK) {
+               STPUTC('\0', expdest);
+               if (flag & EXP_SPLIT) {
+                       if (flag & EXP_GLOB)
+                               expandmeta(grabstackstr(expdest), exparg.list);
+                       else
+                               appendarglist(exparg.list, grabstackstr(expdest));
+               }
        }
+       if ((flag & EXP_SPLIT) == 0)
+               appendarglist(arglist, grabstackstr(expdest));
 }
 
 
@@ -205,15 +257,16 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
  * expansion, and tilde expansion if requested via EXP_TILDE/EXP_VARTILDE.
  * Processing ends at a CTLENDVAR or CTLENDARI character as well as '\0'.
  * This is used to expand word in ${var+word} etc.
- * If EXP_FULL or EXP_CASE are set, keep and/or generate CTLESC
+ * If EXP_GLOB or EXP_CASE are set, keep and/or generate CTLESC
  * characters to allow for further processing.
- * If EXP_FULL is set, also preserve CTLQUOTEMARK characters.
+ *
+ * If EXP_SPLIT is set, dst receives any complete words produced.
  */
-static char *
-argstr(char *p, int flag)
+static const char *
+argstr(const char *p, int flag, struct worddest *dst)
 {
        char c;
-       int quotes = flag & (EXP_FULL | EXP_CASE);      /* do CTLESC */
+       int quotes = flag & (EXP_GLOB | EXP_CASE);      /* do CTLESC */
        int firsteq = 1;
        int split_lit;
        int lit_quoted;
@@ -234,34 +287,36 @@ argstr(char *p, int flag)
                case CTLQUOTEMARK:
                        lit_quoted = 1;
                        /* "$@" syntax adherence hack */
-                       if (p[0] == CTLVAR && p[2] == '@' && p[3] == '=')
+                       if (p[0] == CTLVAR && (p[1] & VSQUOTE) != 0 &&
+                           p[2] == '@' && p[3] == '=')
                                break;
-                       if ((flag & EXP_FULL) != 0)
-                               USTPUTC(c, expdest);
+                       if ((flag & EXP_SPLIT) != 0 && expdest == stackblock())
+                               dst->state = WORD_QUOTEMARK;
                        break;
                case CTLQUOTEEND:
                        lit_quoted = 0;
                        break;
                case CTLESC:
-                       if (quotes)
-                               USTPUTC(c, expdest);
                        c = *p++;
+                       if (split_lit && !lit_quoted &&
+                           strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) {
+                               NEXTWORD(c, flag, expdest, dst);
+                               break;
+                       }
+                       if (quotes)
+                               USTPUTC(CTLESC, expdest);
                        USTPUTC(c, expdest);
-                       if (split_lit && !lit_quoted)
-                               recordregion(expdest - stackblock() -
-                                   (quotes ? 2 : 1),
-                                   expdest - stackblock(), 0);
                        break;
                case CTLVAR:
-                       p = evalvar(p, flag);
+                       p = evalvar(p, flag, dst);
                        break;
                case CTLBACKQ:
                case CTLBACKQ|CTLQUOTE:
-                       expbackq(argbackq->n, c & CTLQUOTE, flag);
+                       expbackq(argbackq->n, c & CTLQUOTE, flag, dst);
                        argbackq = argbackq->next;
                        break;
                case CTLARI:
-                       p = expari(p);
+                       p = expari(p, flag, dst);
                        break;
                case ':':
                case '=':
@@ -269,10 +324,12 @@ argstr(char *p, int flag)
                         * sort of a hack - expand tildes in variable
                         * assignments (after the first '=' and after ':'s).
                         */
+                       if (split_lit && !lit_quoted &&
+                           strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) {
+                               NEXTWORD(c, flag, expdest, dst);
+                               break;
+                       }
                        USTPUTC(c, expdest);
-                       if (split_lit && !lit_quoted)
-                               recordregion(expdest - stackblock() - 1,
-                                   expdest - stackblock(), 0);
                        if (flag & EXP_VARTILDE && *p == '~' &&
                            (c != '=' || firsteq)) {
                                if (c == '=')
@@ -281,10 +338,12 @@ argstr(char *p, int flag)
                        }
                        break;
                default:
+                       if (split_lit && !lit_quoted &&
+                           strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) {
+                               NEXTWORD(c, flag, expdest, dst);
+                               break;
+                       }
                        USTPUTC(c, expdest);
-                       if (split_lit && !lit_quoted)
-                               recordregion(expdest - stackblock() - 1,
-                                   expdest - stackblock(), 0);
                }
        }
 }
@@ -293,12 +352,15 @@ argstr(char *p, int flag)
  * Perform tilde expansion, placing the result in the stack string and
  * returning the next position in the input string to process.
  */
-static char *
-exptilde(char *p, int flag)
+static const char *
+exptilde(const char *p, int flag)
 {
-       char c, *startp = p;
+       char c;
+       const char *startp = p;
+       const char *user;
        struct passwd *pw;
        char *home;
+       int len;
 
        for (;;) {
                c = *p;
@@ -318,17 +380,20 @@ exptilde(char *p, int flag)
                case '\0':
                case '/':
                case CTLENDVAR:
-                       *p = '\0';
-                       if (*(startp+1) == '\0') {
+                       len = p - startp - 1;
+                       STPUTBIN(startp + 1, len, expdest);
+                       STACKSTRNUL(expdest);
+                       user = expdest - len;
+                       if (*user == '\0') {
                                home = lookupvar("HOME");
                        } else {
-                               pw = getpwnam(startp+1);
+                               pw = getpwnam(user);
                                home = pw != NULL ? pw->pw_dir : NULL;
                        }
-                       *p = c;
+                       STADJUST(-len, expdest);
                        if (home == NULL || *home == '\0')
                                return (startp);
-                       strtodest(home, flag, VSNORMAL, 1);
+                       strtodest(home, flag, VSNORMAL, 1, NULL);
                        return (p);
                }
                p++;
@@ -336,51 +401,11 @@ exptilde(char *p, int flag)
 }
 
 
-static void
-removerecordregions(int endoff)
-{
-       if (ifslastp == NULL)
-               return;
-
-       if (ifsfirst.endoff > endoff) {
-               while (ifsfirst.next != NULL) {
-                       struct ifsregion *ifsp;
-                       INTOFF;
-                       ifsp = ifsfirst.next->next;
-                       ckfree(ifsfirst.next);
-                       ifsfirst.next = ifsp;
-                       INTON;
-               }
-               if (ifsfirst.begoff > endoff)
-                       ifslastp = NULL;
-               else {
-                       ifslastp = &ifsfirst;
-                       ifsfirst.endoff = endoff;
-               }
-               return;
-       }
-
-       ifslastp = &ifsfirst;
-       while (ifslastp->next && ifslastp->next->begoff < endoff)
-               ifslastp=ifslastp->next;
-       while (ifslastp->next != NULL) {
-               struct ifsregion *ifsp;
-               INTOFF;
-               ifsp = ifslastp->next->next;
-               ckfree(ifslastp->next);
-               ifslastp->next = ifsp;
-               INTON;
-       }
-       if (ifslastp->endoff > endoff)
-               ifslastp->endoff = endoff;
-}
-
 /*
  * Expand arithmetic expression.
- * Note that flag is not required as digits never require CTLESC characters.
  */
-static char *
-expari(char *p)
+static const char *
+expari(const char *p, int flag, struct worddest *dst)
 {
        char *q, *start;
        arith_t result;
@@ -390,8 +415,7 @@ expari(char *p)
 
        quoted = *p++ == '"';
        begoff = expdest - stackblock();
-       p = argstr(p, 0);
-       removerecordregions(begoff);
+       p = argstr(p, 0, NULL);
        STPUTC('\0', expdest);
        start = stackblock() + begoff;
 
@@ -408,7 +432,7 @@ expari(char *p)
        adj = strlen(expdest);
        STADJUST(adj, expdest);
        if (!quoted)
-               recordregion(begoff, expdest - stackblock(), 0);
+               reprocess(expdest - adj - stackblock(), flag, VSNORMAL, 0, dst);
        return p;
 }
 
@@ -417,41 +441,40 @@ expari(char *p)
  * Perform command substitution.
  */
 static void
-expbackq(union node *cmd, int quoted, int flag)
+expbackq(union node *cmd, int quoted, int flag, struct worddest *dst)
 {
        struct backcmd in;
        int i;
        char buf[128];
        char *p;
        char *dest = expdest;
-       struct ifsregion saveifs, *savelastp;
        struct nodelist *saveargbackq;
        char lastc;
-       int startloc = dest - stackblock();
        char const *syntax = quoted? DQSYNTAX : BASESYNTAX;
-       int quotes = flag & (EXP_FULL | EXP_CASE);
+       int quotes = flag & (EXP_GLOB | EXP_CASE);
        size_t nnl;
+       const char *ifs;
 
        INTOFF;
-       saveifs = ifsfirst;
-       savelastp = ifslastp;
        saveargbackq = argbackq;
        p = grabstackstr(dest);
        evalbackcmd(cmd, &in);
        ungrabstackstr(p, dest);
-       ifsfirst = saveifs;
-       ifslastp = savelastp;
        argbackq = saveargbackq;
 
        p = in.buf;
-       lastc = '\0';
        nnl = 0;
+       if (!quoted && flag & EXP_SPLIT)
+               ifs = ifsset() ? ifsval() : " \t\n";
+       else
+               ifs = "";
        /* Don't copy trailing newlines */
        for (;;) {
                if (--in.nleft < 0) {
                        if (in.fd < 0)
                                break;
-                       while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR);
+                       while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR)
+                               ;
                        TRACE(("expbackq: read returns %d\n", i));
                        if (i <= 0)
                                break;
@@ -459,15 +482,27 @@ expbackq(union node *cmd, int quoted, int flag)
                        in.nleft = i - 1;
                }
                lastc = *p++;
-               if (lastc != '\0') {
-                       if (lastc == '\n') {
-                               nnl++;
-                       } else {
-                               CHECKSTRSPACE(nnl + 2, dest);
-                               while (nnl > 0) {
-                                       nnl--;
-                                       USTPUTC('\n', dest);
+               if (lastc == '\0')
+                       continue;
+               if (lastc == '\n') {
+                       nnl++;
+               } else {
+                       if (nnl > 0) {
+                               if (strchr(ifs, '\n') != NULL) {
+                                       NEXTWORD('\n', flag, dest, dst);
+                                       nnl = 0;
+                               } else {
+                                       CHECKSTRSPACE(nnl + 2, dest);
+                                       while (nnl > 0) {
+                                               nnl--;
+                                               USTPUTC('\n', dest);
+                                       }
                                }
+                       }
+                       if (strchr(ifs, lastc) != NULL)
+                               NEXTWORD(lastc, flag, dest, dst);
+                       else {
+                               CHECKSTRSPACE(2, dest);
                                if (quotes && syntax[(int)lastc] == CCTL)
                                        USTPUTC(CTLESC, dest);
                                USTPUTC(lastc, dest);
@@ -481,8 +516,6 @@ expbackq(union node *cmd, int quoted, int flag)
                ckfree(in.buf);
        if (in.jp)
                exitstatus = waitforjob(in.jp, (int *)NULL);
-       if (quoted == 0)
-               recordregion(startloc, dest - stackblock(), 0);
        TRACE(("expbackq: size=%td: \"%.*s\"\n",
                ((dest - stackblock()) - startloc),
                (int)((dest - stackblock()) - startloc),
@@ -504,109 +537,108 @@ recordleft(const char *str, const char *loc, char *startp)
                *startp++ = *loc++;
 }
 
-static int
-subevalvar(char *p, char *str, int strloc, int subtype, int startloc,
-  int varflags, int quotes)
+static void
+subevalvar_trim(const char *p, int strloc, int subtype, int startloc)
 {
        char *startp;
        char *loc = NULL;
-       char *q;
+       char *str;
        int c = 0;
        struct nodelist *saveargbackq = argbackq;
        int amount;
 
-       argstr(p, (subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX ||
-           subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX ?
-           EXP_CASE : 0) | EXP_TILDE);
+       argstr(p, EXP_CASE | EXP_TILDE, NULL);
        STACKSTRNUL(expdest);
        argbackq = saveargbackq;
        startp = stackblock() + startloc;
-       if (str == NULL)
-           str = stackblock() + strloc;
+       str = stackblock() + strloc;
 
        switch (subtype) {
-       case VSASSIGN:
-               setvar(str, startp, 0);
-               amount = startp - expdest;
-               STADJUST(amount, expdest);
-               varflags &= ~VSNUL;
-               return 1;
-
-       case VSQUESTION:
-               if (*p != CTLENDVAR) {
-                       outfmt(out2, "%s\n", startp);
-                       error((char *)NULL);
-               }
-               error("%.*s: parameter %snot set", (int)(p - str - 1),
-                     str, (varflags & VSNUL) ? "null or " : "");
-               return 0;
-
        case VSTRIMLEFT:
                for (loc = startp; loc < str; loc++) {
                        c = *loc;
                        *loc = '\0';
-                       if (patmatch(str, startp, quotes)) {
+                       if (patmatch(str, startp)) {
                                *loc = c;
                                recordleft(str, loc, startp);
-                               return 1;
+                               return;
                        }
                        *loc = c;
-                       if (quotes && *loc == CTLESC)
-                               loc++;
                }
-               return 0;
+               break;
 
        case VSTRIMLEFTMAX:
                for (loc = str - 1; loc >= startp;) {
                        c = *loc;
                        *loc = '\0';
-                       if (patmatch(str, startp, quotes)) {
+                       if (patmatch(str, startp)) {
                                *loc = c;
                                recordleft(str, loc, startp);
-                               return 1;
+                               return;
                        }
                        *loc = c;
                        loc--;
-                       if (quotes && loc > startp && *(loc - 1) == CTLESC) {
-                               for (q = startp; q < loc; q++)
-                                       if (*q == CTLESC)
-                                               q++;
-                               if (q > loc)
-                                       loc--;
-                       }
                }
-               return 0;
+               break;
 
        case VSTRIMRIGHT:
                for (loc = str - 1; loc >= startp;) {
-                       if (patmatch(str, loc, quotes)) {
+                       if (patmatch(str, loc)) {
                                amount = loc - expdest;
                                STADJUST(amount, expdest);
-                               return 1;
+                               return;
                        }
                        loc--;
-                       if (quotes && loc > startp && *(loc - 1) == CTLESC) {
-                               for (q = startp; q < loc; q++)
-                                       if (*q == CTLESC)
-                                               q++;
-                               if (q > loc)
-                                       loc--;
-                       }
                }
-               return 0;
+               break;
 
        case VSTRIMRIGHTMAX:
                for (loc = startp; loc < str - 1; loc++) {
-                       if (patmatch(str, loc, quotes)) {
+                       if (patmatch(str, loc)) {
                                amount = loc - expdest;
                                STADJUST(amount, expdest);
-                               return 1;
+                               return;
                        }
-                       if (quotes && *loc == CTLESC)
-                               loc++;
                }
-               return 0;
+               break;
+
+
+       default:
+               abort();
+       }
+       amount = (expdest - stackblock() - strloc) + 1;
+       STADJUST(-amount, expdest);
+}
+
+
+static int
+subevalvar_misc(const char *p, const char *var, int subtype, int startloc,
+  int varflags)
+{
+       char *startp;
+       struct nodelist *saveargbackq = argbackq;
+       int amount;
 
+       argstr(p, EXP_TILDE, NULL);
+       STACKSTRNUL(expdest);
+       argbackq = saveargbackq;
+       startp = stackblock() + startloc;
+
+       switch (subtype) {
+       case VSASSIGN:
+               setvar(var, startp, 0);
+               amount = startp - expdest;
+               STADJUST(amount, expdest);
+               return 1;
+
+       case VSQUESTION:
+               if (*p != CTLENDVAR) {
+                       outfmt(out2, "%s\n", startp);
+                       error((char *)NULL);
+               }
+               error("%.*s: parameter %snot set", (int)(p - var - 1),
+                     var, (varflags & VSNUL) ? "null or " : "");
+               return 0;
 
        default:
                abort();
@@ -619,12 +651,12 @@ subevalvar(char *p, char *str, int strloc, int subtype, int startloc,
  * input string.
  */
 
-static char *
-evalvar(char *p, int flag)
+static const char *
+evalvar(const char *p, int flag, struct worddest *dst)
 {
        int subtype;
        int varflags;
-       char *var;
+       const char *var;
        const char *val;
        int patloc;
        int c;
@@ -633,9 +665,7 @@ evalvar(char *p, int flag)
        int startloc;
        int varlen;
        int varlenb;
-       int easy;
-       int quotes = flag & (EXP_FULL | EXP_CASE);
-       int record = 0;
+       char buf[21];
 
        varflags = (unsigned char)*p++;
        subtype = varflags & VSTYPE;
@@ -677,10 +707,16 @@ again: /* jump here after setting a variable with ${var=text} */
        if (set && subtype != VSPLUS) {
                /* insert the value of the variable */
                if (special) {
-                       if (varflags & VSLINENO)
-                               STPUTBIN(var, p - var - 1, expdest);
-                       else
-                               varvalue(var, varflags & VSQUOTE, subtype, flag);
+                       if (varflags & VSLINENO) {
+                               if (p - var > (ptrdiff_t)sizeof(buf))
+                                       abort();
+                               memcpy(buf, var, p - var - 1);
+                               buf[p - var - 1] = '\0';
+                               strtodest(buf, flag, subtype,
+                                   varflags & VSQUOTE, dst);
+                       } else
+                               varvalue(var, varflags & VSQUOTE, subtype, flag,
+                                   dst);
                        if (subtype == VSLENGTH) {
                                varlenb = expdest - stackblock() - startloc;
                                varlen = varlenb;
@@ -701,35 +737,29 @@ again: /* jump here after setting a variable with ${var=text} */
                        }
                        else
                                strtodest(val, flag, subtype,
-                                   varflags & VSQUOTE);
+                                   varflags & VSQUOTE, dst);
                }
        }
 
        if (subtype == VSPLUS)
                set = ! set;
 
-       easy = ((varflags & VSQUOTE) == 0 ||
-               (*var == '@' && shellparam.nparam != 1));
-
-
        switch (subtype) {
        case VSLENGTH:
-               expdest = cvtnum(varlen, expdest);
-               record = 1;
+               cvtnum(varlen, buf);
+               strtodest(buf, flag, VSNORMAL, varflags & VSQUOTE, dst);
                break;
 
        case VSNORMAL:
-               record = easy;
                break;
 
        case VSPLUS:
        case VSMINUS:
                if (!set) {
-                       argstr(p, flag | (flag & EXP_FULL ? EXP_SPLIT_LIT : 0) |
-                           (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0));
+                       argstr(p, flag | (flag & EXP_SPLIT ? EXP_SPLIT_LIT : 0) |
+                           (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0), dst);
                        break;
                }
-               record = easy;
                break;
 
        case VSTRIMLEFT:
@@ -744,32 +774,22 @@ again: /* jump here after setting a variable with ${var=text} */
                 */
                STPUTC('\0', expdest);
                patloc = expdest - stackblock();
-               if (subevalvar(p, NULL, patloc, subtype,
-                   startloc, varflags, quotes) == 0) {
-                       int amount = (expdest - stackblock() - patloc) + 1;
-                       STADJUST(-amount, expdest);
-               }
-               /* Remove any recorded regions beyond start of variable */
-               removerecordregions(startloc);
-               record = 1;
+               subevalvar_trim(p, patloc, subtype, startloc);
+               reprocess(startloc, flag, VSNORMAL, varflags & VSQUOTE, dst);
+               if (flag & EXP_SPLIT && *var == '@' && varflags & VSQUOTE)
+                       dst->state = WORD_QUOTEMARK;
                break;
 
        case VSASSIGN:
        case VSQUESTION:
                if (!set) {
-                       if (subevalvar(p, var, 0, subtype, startloc, varflags,
-                           quotes)) {
+                       if (subevalvar_misc(p, var, subtype, startloc,
+                           varflags)) {
                                varflags &= ~VSNUL;
-                               /*
-                                * Remove any recorded regions beyond
-                                * start of variable
-                                */
-                               removerecordregions(startloc);
                                goto again;
                        }
                        break;
                }
-               record = easy;
                break;
 
        case VSERROR:
@@ -781,11 +801,6 @@ again: /* jump here after setting a variable with ${var=text} */
                abort();
        }
 
-       if (record)
-               recordregion(startloc, expdest - stackblock(),
-                   varflags & VSQUOTE || (ifsset() && ifsval()[0] == '\0' &&
-                   (*var == '@' || *var == '*')));
-
        if (subtype != VSNORMAL) {      /* skip to end of alternative */
                int nesting = 1;
                for (;;) {
@@ -851,26 +866,80 @@ varisset(const char *name, int nulok)
 }
 
 static void
-strtodest(const char *p, int flag, int subtype, int quoted)
+strtodest(const char *p, int flag, int subtype, int quoted,
+    struct worddest *dst)
 {
-       if (flag & (EXP_FULL | EXP_CASE) && subtype != VSLENGTH)
+       if (subtype == VSLENGTH || subtype == VSTRIMLEFT ||
+           subtype == VSTRIMLEFTMAX || subtype == VSTRIMRIGHT ||
+           subtype == VSTRIMRIGHTMAX)
+               STPUTS(p, expdest);
+       else if (flag & EXP_SPLIT && !quoted && dst != NULL)
+               STPUTS_SPLIT(p, BASESYNTAX, flag, expdest, dst);
+       else if (flag & (EXP_GLOB | EXP_CASE))
                STPUTS_QUOTES(p, quoted ? DQSYNTAX : BASESYNTAX, expdest);
        else
                STPUTS(p, expdest);
 }
 
+static void
+reprocess(int startloc, int flag, int subtype, int quoted,
+    struct worddest *dst)
+{
+       static char *buf = NULL;
+       static size_t buflen = 0;
+       char *startp;
+       size_t len, zpos, zlen;
+
+       startp = stackblock() + startloc;
+       len = expdest - startp;
+       if (len >= SIZE_MAX / 2)
+               abort();
+       INTOFF;
+       if (len >= buflen) {
+               ckfree(buf);
+               buf = NULL;
+       }
+       if (buflen < 128)
+               buflen = 128;
+       while (len >= buflen)
+               buflen <<= 1;
+       if (buf == NULL)
+               buf = ckmalloc(buflen);
+       INTON;
+       memcpy(buf, startp, len);
+       buf[len] = '\0';
+       STADJUST(-len, expdest);
+       for (zpos = 0;;) {
+               zlen = strlen(buf + zpos);
+               strtodest(buf + zpos, flag, subtype, quoted, dst);
+               zpos += zlen + 1;
+               if (zpos == len + 1)
+                       break;
+               if (flag & EXP_SPLIT && (quoted || (zlen > 0 && zpos < len)))
+                       NEXTWORD('\0', flag, expdest, dst);
+       }
+}
+
 /*
  * Add the value of a specialized variable to the stack string.
  */
 
 static void
-varvalue(const char *name, int quoted, int subtype, int flag)
+varvalue(const char *name, int quoted, int subtype, int flag,
+    struct worddest *dst)
 {
        int num;
        char *p;
        int i;
+       int splitlater;
        char sep[2];
        char **ap;
+       char buf[(NSHORTOPTS > 10 ? NSHORTOPTS : 10) + 1];
+
+       if (subtype == VSLENGTH)
+               flag &= ~EXP_FULL;
+       splitlater = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX ||
+               subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX;
 
        switch (*name) {
        case '$':
@@ -886,18 +955,28 @@ varvalue(const char *name, int quoted, int subtype, int flag)
                num = backgndpidval();
                break;
        case '-':
-               for (i = 0 ; i < NOPTS ; i++) {
-                       if (optlist[i].val)
-                               STPUTC(optlist[i].letter, expdest);
+               p = buf;
+               for (i = 0 ; i < NSHORTOPTS ; i++) {
+                       if (optval[i])
+                               *p++ = optletter[i];
                }
+               *p = '\0';
+               strtodest(buf, flag, subtype, quoted, dst);
                return;
        case '@':
-               if (flag & EXP_FULL && quoted) {
+               if (flag & EXP_SPLIT && quoted) {
                        for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
-                               strtodest(p, flag, subtype, quoted);
-                               if (*ap)
-                                       STPUTC('\0', expdest);
+                               strtodest(p, flag, subtype, quoted, dst);
+                               if (*ap) {
+                                       if (splitlater)
+                                               STPUTC('\0', expdest);
+                                       else
+                                               NEXTWORD('\0', flag, expdest,
+                                                   dst);
+                               }
                        }
+                       if (shellparam.nparam > 0)
+                               dst->state = WORD_QUOTEMARK;
                        return;
                }
                /* FALLTHROUGH */
@@ -908,13 +987,17 @@ varvalue(const char *name, int quoted, int subtype, int flag)
                        sep[0] = ' ';
                sep[1] = '\0';
                for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
-                       strtodest(p, flag, subtype, quoted);
+                       strtodest(p, flag, subtype, quoted, dst);
                        if (!*ap)
                                break;
                        if (sep[0])
-                               strtodest(sep, flag, subtype, quoted);
-                       else if (flag & EXP_FULL && !quoted && **ap != '\0')
-                               STPUTC('\0', expdest);
+                               strtodest(sep, flag, subtype, quoted, dst);
+                       else if (flag & EXP_SPLIT && !quoted && **ap != '\0') {
+                               if (splitlater)
+                                       STPUTC('\0', expdest);
+                               else
+                                       NEXTWORD('\0', flag, expdest, dst);
+                       }
                }
                return;
        default:
@@ -926,205 +1009,52 @@ varvalue(const char *name, int quoted, int subtype, int flag)
                                p = shellparam.p[num - 1];
                        else
                                return;
-                       strtodest(p, flag, subtype, quoted);
+                       strtodest(p, flag, subtype, quoted, dst);
                }
                return;
        }
-       expdest = cvtnum(num, expdest);
+       cvtnum(num, buf);
+       strtodest(buf, flag, subtype, quoted, dst);
 }
 
 
 
-/*
- * Record the fact that we have to scan this region of the
- * string for IFS characters.
- */
-
-static void
-recordregion(int start, int end, int inquotes)
-{
-       struct ifsregion *ifsp;
-
-       INTOFF;
-       if (ifslastp == NULL) {
-               ifsp = &ifsfirst;
-       } else {
-               if (ifslastp->endoff == start
-                   && ifslastp->inquotes == inquotes) {
-                       /* extend previous area */
-                       ifslastp->endoff = end;
-                       INTON;
-                       return;
-               }
-               ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion));
-               ifslastp->next = ifsp;
-       }
-       ifslastp = ifsp;
-       ifslastp->next = NULL;
-       ifslastp->begoff = start;
-       ifslastp->endoff = end;
-       ifslastp->inquotes = inquotes;
-       INTON;
-}
-
-
-
-/*
- * Break the argument string into pieces based upon IFS and add the
- * strings to the argument list.  The regions of the string to be
- * searched for IFS characters have been stored by recordregion.
- * CTLESC characters are preserved but have little effect in this pass
- * other than escaping CTL* characters.  In particular, they do not escape
- * IFS characters: that should be done with the ifsregion mechanism.
- * CTLQUOTEMARK characters are used to preserve empty quoted strings.
- * This pass treats them as a regular character, making the string non-empty.
- * Later, they are removed along with the other CTL* characters.
- */
-static void
-ifsbreakup(char *string, struct arglist *arglist)
-{
-       struct ifsregion *ifsp;
-       struct strlist *sp;
-       char *start;
-       char *p;
-       char *q;
-       const char *ifs;
-       const char *ifsspc;
-       int had_param_ch = 0;
-
-       start = string;
-
-       if (ifslastp == NULL) {
-               /* Return entire argument, IFS doesn't apply to any of it */
-               sp = (struct strlist *)stalloc(sizeof *sp);
-               sp->text = start;
-               *arglist->lastp = sp;
-               arglist->lastp = &sp->next;
-               return;
-       }
-
-       ifs = ifsset() ? ifsval() : " \t\n";
-
-       for (ifsp = &ifsfirst; ifsp != NULL; ifsp = ifsp->next) {
-               p = string + ifsp->begoff;
-               while (p < string + ifsp->endoff) {
-                       q = p;
-                       if (*p == CTLESC)
-                               p++;
-                       if (ifsp->inquotes) {
-                               /* Only NULs (should be from "$@") end args */
-                               had_param_ch = 1;
-                               if (*p != 0) {
-                                       p++;
-                                       continue;
-                               }
-                               ifsspc = NULL;
-                       } else {
-                               if (!strchr(ifs, *p)) {
-                                       had_param_ch = 1;
-                                       p++;
-                                       continue;
-                               }
-                               ifsspc = strchr(" \t\n", *p);
-
-                               /* Ignore IFS whitespace at start */
-                               if (q == start && ifsspc != NULL) {
-                                       p++;
-                                       start = p;
-                                       continue;
-                               }
-                               had_param_ch = 0;
-                       }
-
-                       /* Save this argument... */
-                       *q = '\0';
-                       sp = (struct strlist *)stalloc(sizeof *sp);
-                       sp->text = start;
-                       *arglist->lastp = sp;
-                       arglist->lastp = &sp->next;
-                       p++;
-
-                       if (ifsspc != NULL) {
-                               /* Ignore further trailing IFS whitespace */
-                               for (; p < string + ifsp->endoff; p++) {
-                                       q = p;
-                                       if (*p == CTLESC)
-                                               p++;
-                                       if (strchr(ifs, *p) == NULL) {
-                                               p = q;
-                                               break;
-                                       }
-                                       if (strchr(" \t\n", *p) == NULL) {
-                                               p++;
-                                               break;
-                                       }
-                               }
-                       }
-                       start = p;
-               }
-       }
-
-       /*
-        * Save anything left as an argument.
-        * Traditionally we have treated 'IFS=':'; set -- x$IFS' as
-        * generating 2 arguments, the second of which is empty.
-        * Some recent clarification of the Posix spec say that it
-        * should only generate one....
-        */
-       if (had_param_ch || *start != 0) {
-               sp = (struct strlist *)stalloc(sizeof *sp);
-               sp->text = start;
-               *arglist->lastp = sp;
-               arglist->lastp = &sp->next;
-       }
-}
-
-
 static char expdir[PATH_MAX];
 #define expdir_end (expdir + sizeof(expdir))
 
 /*
  * Perform pathname generation and remove control characters.
- * At this point, the only control characters should be CTLESC and CTLQUOTEMARK.
- * The results are stored in the list exparg.
+ * At this point, the only control characters should be CTLESC.
+ * The results are stored in the list dstlist.
  */
 static void
-expandmeta(struct strlist *str)
+expandmeta(char *pattern, struct arglist *dstlist)
 {
        char *p;
-       struct strlist **savelastp;
-       struct strlist *sp;
+       int firstmatch;
        char c;
 
-       while (str) {
-               savelastp = exparg.lastp;
-               if (!fflag) {
-                       p = str->text;
-                       for (; (c = *p) != '\0'; p++) {
-                               /* fast check for meta chars */
-                               if (c == '*' || c == '?' || c == '[') {
-                                       INTOFF;
-                                       expmeta(expdir, str->text);
-                                       INTON;
-                                       break;
-                               }
-                       }
-               }
-               if (exparg.lastp == savelastp) {
-                       /*
-                        * no matches
-                        */
-                       *exparg.lastp = str;
-                       rmescapes(str->text);
-                       exparg.lastp = &str->next;
-               } else {
-                       *exparg.lastp = NULL;
-                       *savelastp = sp = expsort(*savelastp);
-                       while (sp->next != NULL)
-                               sp = sp->next;
-                       exparg.lastp = &sp->next;
+       firstmatch = dstlist->count;
+       p = pattern;
+       for (; (c = *p) != '\0'; p++) {
+               /* fast check for meta chars */
+               if (c == '*' || c == '?' || c == '[') {
+                       INTOFF;
+                       expmeta(expdir, pattern, dstlist);
+                       INTON;
+                       break;
                }
-               str = str->next;
+       }
+       if (dstlist->count == firstmatch) {
+               /*
+                * no matches
+                */
+               rmescapes(pattern);
+               appendarglist(dstlist, pattern);
+       } else {
+               qsort(&dstlist->args[firstmatch],
+                   dstlist->count - firstmatch,
+                   sizeof(dstlist->args[0]), expsortcmp);
        }
 }
 
@@ -1134,7 +1064,7 @@ expandmeta(struct strlist *str)
  */
 
 static void
-expmeta(char *enddir, char *name)
+expmeta(char *enddir, char *name, struct arglist *arglist)
 {
        const char *p;
        const char *q;
@@ -1159,8 +1089,6 @@ expmeta(char *enddir, char *name)
                        if (*q == '!' || *q == '^')
                                q++;
                        for (;;) {
-                               while (*q == CTLQUOTEMARK)
-                                       q++;
                                if (*q == CTLESC)
                                        q++;
                                if (*q == '/' || *q == '\0')
@@ -1172,8 +1100,6 @@ expmeta(char *enddir, char *name)
                        }
                } else if (*p == '\0')
                        break;
-               else if (*p == CTLQUOTEMARK)
-                       continue;
                else {
                        if (*p == CTLESC)
                                esc++;
@@ -1188,8 +1114,6 @@ expmeta(char *enddir, char *name)
                if (enddir != expdir)
                        metaflag++;
                for (p = name ; ; p++) {
-                       if (*p == CTLQUOTEMARK)
-                               continue;
                        if (*p == CTLESC)
                                p++;
                        *enddir++ = *p;
@@ -1199,15 +1123,13 @@ expmeta(char *enddir, char *name)
                                return;
                }
                if (metaflag == 0 || lstat(expdir, &statb) >= 0)
-                       addfname(expdir);
+                       appendarglist(arglist, stsavestr(expdir));
                return;
        }
        endname = name + (p - name);
        if (start != name) {
                p = name;
                while (p < start) {
-                       while (*p == CTLQUOTEMARK)
-                               p++;
                        if (*p == CTLESC)
                                p++;
                        *enddir++ = *p++;
@@ -1236,8 +1158,6 @@ expmeta(char *enddir, char *name)
        }
        matchdot = 0;
        p = start;
-       while (*p == CTLQUOTEMARK)
-               p++;
        if (*p == CTLESC)
                p++;
        if (*p == '.')
@@ -1245,13 +1165,13 @@ expmeta(char *enddir, char *name)
        while (! int_pending() && (dp = readdir(dirp)) != NULL) {
                if (dp->d_name[0] == '.' && ! matchdot)
                        continue;
-               if (patmatch(start, dp->d_name, 0)) {
+               if (patmatch(start, dp->d_name)) {
                        namlen = dp->d_namlen;
                        if (enddir + namlen + 1 > expdir_end)
                                continue;
                        memcpy(enddir, dp->d_name, namlen + 1);
                        if (atend)
-                               addfname(expdir);
+                               appendarglist(arglist, stsavestr(expdir));
                        else {
                                if (dp->d_type != DT_UNKNOWN &&
                                    dp->d_type != DT_DIR &&
@@ -1261,7 +1181,7 @@ expmeta(char *enddir, char *name)
                                        continue;
                                enddir[namlen] = '/';
                                enddir[namlen + 1] = '\0';
-                               expmeta(enddir + namlen + 1, endname);
+                               expmeta(enddir + namlen + 1, endname, arglist);
                        }
                }
        }
@@ -1271,81 +1191,13 @@ expmeta(char *enddir, char *name)
 }
 
 
-/*
- * Add a file name to the list.
- */
-
-static void
-addfname(char *name)
+static int
+expsortcmp(const void *p1, const void *p2)
 {
-       char *p;
-       struct strlist *sp;
+       const char *s1 = *(const char * const *)p1;
+       const char *s2 = *(const char * const *)p2;
 
-       p = stsavestr(name);
-       sp = (struct strlist *)stalloc(sizeof *sp);
-       sp->text = p;
-       *exparg.lastp = sp;
-       exparg.lastp = &sp->next;
-}
-
-
-/*
- * Sort the results of file name expansion.  It calculates the number of
- * strings to sort and then calls msort (short for merge sort) to do the
- * work.
- */
-
-static struct strlist *
-expsort(struct strlist *str)
-{
-       int len;
-       struct strlist *sp;
-
-       len = 0;
-       for (sp = str ; sp ; sp = sp->next)
-               len++;
-       return msort(str, len);
-}
-
-
-static struct strlist *
-msort(struct strlist *list, int len)
-{
-       struct strlist *p, *q = NULL;
-       struct strlist **lpp;
-       int half;
-       int n;
-
-       if (len <= 1)
-               return list;
-       half = len >> 1;
-       p = list;
-       for (n = half ; --n >= 0 ; ) {
-               q = p;
-               p = p->next;
-       }
-       q->next = NULL;                 /* terminate first half of list */
-       q = msort(list, half);          /* sort first half of list */
-       p = msort(p, len - half);               /* sort second half */
-       lpp = &list;
-       for (;;) {
-               if (strcmp(p->text, q->text) < 0) {
-                       *lpp = p;
-                       lpp = &p->next;
-                       if ((p = *lpp) == NULL) {
-                               *lpp = q;
-                               break;
-                       }
-               } else {
-                       *lpp = q;
-                       lpp = &q->next;
-                       if ((q = *lpp) == NULL) {
-                               *lpp = p;
-                               break;
-                       }
-               }
-       }
-       return list;
+       return (strcoll(s1, s2));
 }
 
 
@@ -1403,7 +1255,7 @@ match_charclass(const char *p, wchar_t chr, const char **end)
  */
 
 static int
-patmatch(const char *pattern, const char *string, int squoted)
+patmatch(const char *pattern, const char *string)
 {
        const char *p, *q, *end;
        const char *bt_p, *bt_q;
@@ -1421,16 +1273,10 @@ patmatch(const char *pattern, const char *string, int squoted)
                                goto backtrack;
                        return 1;
                case CTLESC:
-                       if (squoted && *q == CTLESC)
-                               q++;
                        if (*q++ != *p++)
                                goto backtrack;
                        break;
-               case CTLQUOTEMARK:
-                       continue;
                case '?':
-                       if (squoted && *q == CTLESC)
-                               q++;
                        if (*q == '\0')
                                return 0;
                        if (localeisutf8) {
@@ -1442,11 +1288,11 @@ patmatch(const char *pattern, const char *string, int squoted)
                                if (wc == 0)
                                        goto backtrack;
                        } else
-                               wc = (unsigned char)*q++;
+                               q++;
                        break;
                case '*':
                        c = *p;
-                       while (c == CTLQUOTEMARK || c == '*')
+                       while (c == '*')
                                c = *++p;
                        /*
                         * If the pattern ends here, we know the string
@@ -1464,29 +1310,17 @@ patmatch(const char *pattern, const char *string, int squoted)
                        bt_q = q;
                        break;
                case '[': {
-                       const char *endp;
+                       const char *savep, *saveq;
                        int invert, found;
                        wchar_t chr;
 
-                       endp = p;
-                       if (*endp == '!' || *endp == '^')
-                               endp++;
-                       do {
-                               while (*endp == CTLQUOTEMARK)
-                                       endp++;
-                               if (*endp == 0)
-                                       goto dft;               /* no matching ] */
-                               if (*endp == CTLESC)
-                                       endp++;
-                       } while (*++endp != ']');
+                       savep = p, saveq = q;
                        invert = 0;
                        if (*p == '!' || *p == '^') {
                                invert++;
                                p++;
                        }
                        found = 0;
-                       if (squoted && *q == CTLESC)
-                               q++;
                        if (*q == '\0')
                                return 0;
                        if (localeisutf8) {
@@ -1497,8 +1331,11 @@ patmatch(const char *pattern, const char *string, int squoted)
                                chr = (unsigned char)*q++;
                        c = *p++;
                        do {
-                               if (c == CTLQUOTEMARK)
-                                       continue;
+                               if (c == '\0') {
+                                       p = savep, q = saveq;
+                                       c = '[';
+                                       goto dft;
+                               }
                                if (c == '[' && *p == ':') {
                                        found |= match_charclass(p, chr, &end);
                                        if (end != NULL)
@@ -1515,8 +1352,6 @@ patmatch(const char *pattern, const char *string, int squoted)
                                        wc = (unsigned char)c;
                                if (*p == '-' && p[1] != ']') {
                                        p++;
-                                       while (*p == CTLQUOTEMARK)
-                                               p++;
                                        if (*p == CTLESC)
                                                p++;
                                        if (localeisutf8) {
@@ -1539,8 +1374,6 @@ patmatch(const char *pattern, const char *string, int squoted)
                        break;
                }
 dft:           default:
-                       if (squoted && *q == CTLESC)
-                               q++;
                        if (*q == '\0')
                                return 0;
                        if (*q++ == c)
@@ -1553,8 +1386,6 @@ backtrack:
                         */
                        if (bt_p == NULL)
                                return 0;
-                       if (squoted && *bt_q == CTLESC)
-                               bt_q++;
                        if (*bt_q == '\0')
                                return 0;
                        bt_q++;
@@ -1610,11 +1441,10 @@ casematch(union node *pattern, const char *val)
        setstackmark(&smark);
        argbackq = pattern->narg.backquote;
        STARTSTACKSTR(expdest);
-       ifslastp = NULL;
-       argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
+       argstr(pattern->narg.text, EXP_TILDE | EXP_CASE, NULL);
        STPUTC('\0', expdest);
        p = grabstackstr(expdest);
-       result = patmatch(p, val, 0);
+       result = patmatch(p, val);
        popstackmark(&smark);
        return result;
 }
@@ -1623,7 +1453,7 @@ casematch(union node *pattern, const char *val)
  * Our own itoa().
  */
 
-static char *
+static void
 cvtnum(int num, char *buf)
 {
        char temp[32];
@@ -1639,8 +1469,7 @@ cvtnum(int num, char *buf)
        if (neg)
                *--p = '-';
 
-       STPUTS(p, buf);
-       return buf;
+       memcpy(buf, p, temp + 32 - p);
 }
 
 /*
@@ -1661,3 +1490,56 @@ wordexpcmd(int argc, char **argv)
                outbin(argv[i], strlen(argv[i]) + 1, out1);
         return (0);
 }
+
+/*
+ * Do most of the work for wordexp(3), new version.
+ */
+
+int
+freebsd_wordexpcmd(int argc __unused, char **argv __unused)
+{
+       struct arglist arglist;
+       union node *args, *n;
+       size_t len;
+       int ch;
+       int protected = 0;
+       int fd = -1;
+       int i;
+
+       while ((ch = nextopt("f:p")) != '\0') {
+               switch (ch) {
+               case 'f':
+                       fd = number(shoptarg);
+                       break;
+               case 'p':
+                       protected = 1;
+                       break;
+               }
+       }
+       if (*argptr != NULL)
+               error("wrong number of arguments");
+       if (fd < 0)
+               error("missing fd");
+       INTOFF;
+       setinputfd(fd, 1);
+       INTON;
+       args = parsewordexp();
+       popfile(); /* will also close fd */
+       if (protected)
+               for (n = args; n != NULL; n = n->narg.next) {
+                       if (n->narg.backquote != NULL) {
+                               outcslow('C', out1);
+                               error("command substitution disabled");
+                       }
+               }
+       outcslow(' ', out1);
+       emptyarglist(&arglist);
+       for (n = args; n != NULL; n = n->narg.next)
+               expandarg(n, &arglist, EXP_FULL | EXP_TILDE);
+       for (i = 0, len = 0; i < arglist.count; i++)
+               len += strlen(arglist.args[i]);
+       out1fmt("%016x %016zx", arglist.count, len);
+       for (i = 0; i < arglist.count; i++)
+               outbin(arglist.args[i], strlen(arglist.args[i]) + 1, out1);
+       return (0);
+}