Bash-4.2 distribution sources and documentation
[platform/upstream/bash.git] / subst.c
diff --git a/subst.c b/subst.c
index 81a3256..0e83e56 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -4,7 +4,7 @@
 /* ``Have a little faith, there's magic in the night.  You ain't a
      beauty, but, hey, you're alright.'' */
 
-/* Copyright (C) 1987-2009 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2010 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -42,6 +42,7 @@
 #include "bashintl.h"
 
 #include "shell.h"
+#include "parser.h"
 #include "flags.h"
 #include "jobs.h"
 #include "execute_cmd.h"
@@ -51,6 +52,7 @@
 #include "mailcheck.h"
 
 #include "shmbutil.h"
+#include "typemax.h"
 
 #include "builtins/getopt.h"
 #include "builtins/common.h"
@@ -107,7 +109,7 @@ extern int errno;
 /* Evaluates to 1 if C is one of the shell's special parameters for which an
    indirect variable reference may be made. */
 #define VALID_INDIR_PARAM(c) \
-  ((c) == '#' || (c) == '?' || (c) == '@' || (c) == '*')
+  ((posixly_correct == 0 && (c) == '#') || (posixly_correct == 0 && (c) == '?') || (c) == '@' || (c) == '*')
 
 /* Evaluates to 1 if C is one of the OP characters that follows the parameter
    in ${parameter[:]OPword}. */
@@ -244,10 +246,8 @@ static wchar_t *remove_wpattern __P((wchar_t *, size_t, wchar_t *, int));
 #endif
 static char *remove_pattern __P((char *, char *, int));
 
-static int match_pattern_char __P((char *, char *));
 static int match_upattern __P((char *, char *, int, char **, char **));
 #if defined (HANDLE_MULTIBYTE)
-static int match_pattern_wchar __P((wchar_t *, wchar_t *));
 static int match_wpattern __P((wchar_t *, char **, size_t, wchar_t *, int, char **, char **));
 #endif
 static int match_pattern __P((char *, char *, int, char **, char **));
@@ -259,7 +259,7 @@ static char *parameter_list_remove_pattern __P((int, char *, int, int));
 #ifdef ARRAY_VARS
 static char *array_remove_pattern __P((SHELL_VAR *, char *, int, char *, int));
 #endif
-static char *parameter_brace_remove_pattern __P((char *, char *, char *, int, int));
+static char *parameter_brace_remove_pattern __P((char *, char *, int, char *, int, int, int));
 
 static char *process_substitute __P((char *, int));
 
@@ -273,7 +273,7 @@ static int valid_brace_expansion_word __P((char *, int));
 static int chk_atstar __P((char *, int, int *, int *));
 static int chk_arithsub __P((const char *, int));
 
-static WORD_DESC *parameter_brace_expand_word __P((char *, int, int, int));
+static WORD_DESC *parameter_brace_expand_word __P((char *, int, int, int, arrayind_t *));
 static WORD_DESC *parameter_brace_expand_indir __P((char *, int, int, int *, int *));
 static WORD_DESC *parameter_brace_expand_rhs __P((char *, char *, int, int, int *, int *));
 static void parameter_brace_expand_error __P((char *, char *));
@@ -283,16 +283,18 @@ static intmax_t parameter_brace_expand_length __P((char *));
 
 static char *skiparith __P((char *, int));
 static int verify_substring_values __P((SHELL_VAR *, char *, char *, int, intmax_t *, intmax_t *));
-static int get_var_and_type __P((char *, char *, int, SHELL_VAR **, char **));
+static int get_var_and_type __P((char *, char *, arrayind_t, int, int, SHELL_VAR **, char **));
 static char *mb_substring __P((char *, int, int));
-static char *parameter_brace_substring __P((char *, char *, char *, int));
+static char *parameter_brace_substring __P((char *, char *, int, char *, int, int));
+
+static int shouldexp_replacement __P((char *));
 
 static char *pos_params_pat_subst __P((char *, char *, char *, int));
 
-static char *parameter_brace_patsub __P((char *, char *, char *, int));
+static char *parameter_brace_patsub __P((char *, char *, int, char *, int, int));
 
 static char *pos_params_casemod __P((char *, char *, int, int));
-static char *parameter_brace_casemod __P((char *, char *, int, char *, int));
+static char *parameter_brace_casemod __P((char *, char *, int, int, char *, int, int));
 
 static WORD_DESC *parameter_brace_expand __P((char *, int *, int, int, int *, int *));
 static WORD_DESC *param_expand __P((char *, int *, int, int *, int *, int *, int *, int));
@@ -608,7 +610,6 @@ unquoted_substring (substr, string)
        {
        case '\\':
          sindex++;
-
          if (string[sindex])
            ADVANCE_CHAR (string, slen, sindex);
          break;
@@ -785,6 +786,7 @@ string_extract_double_quoted (string, sindex, stripdq)
       /* Process a character that was quoted by a backslash. */
       if (pass_next)
        {
+         /* XXX - take another look at this in light of Interp 221 */
          /* Posix.2 sez:
 
             ``The backslash shall retain its special meaning as an escape
@@ -858,7 +860,7 @@ add_one_character:
          if (string[i + 1] == LPAREN)
            ret = extract_command_subst (string, &si, 0);
          else
-           ret = extract_dollar_brace_string (string, &si, 1, 0);
+           ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, 0);
 
          temp[j++] = '$';
          temp[j++] = string[i + 1];
@@ -960,7 +962,7 @@ skip_double_quoted (string, slen, sind)
          if (string[i + 1] == LPAREN)
            ret = extract_command_subst (string, &si, SX_NOALLOC);
          else
-           ret = extract_dollar_brace_string (string, &si, 1, SX_NOALLOC);
+           ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, SX_NOALLOC);
 
          i = si + 1;
          continue;
@@ -1264,17 +1266,15 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
          continue;
        }
 
-#if 0
-      /* Process a nested command substitution, but only if we're parsing a
-        command substitution.  XXX - for bash-4.2 */
+      /* Process a nested command substitution, but only if we're parsing an
+        arithmetic substitution. */
       if ((flags & SX_COMMAND) && string[i] == '$' && string[i+1] == LPAREN)
         {
           si = i + 2;
-          t = extract_command_subst (string, &si, flags);
+          t = extract_command_subst (string, &si, flags|SX_NOALLOC);
           i = si + 1;
           continue;
         }
-#endif
 
       /* Process a nested OPENER. */
       if (STREQN (string + i, opener, len_opener))
@@ -1370,7 +1370,7 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
 {
   register int i, c;
   size_t slen;
-  int pass_character, nesting_level, si;
+  int pass_character, nesting_level, si, dolbrace_state;
   char *result, *t;
   DECLARE_MBSTATE;
 
@@ -1378,6 +1378,12 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
   nesting_level = 1;
   slen = strlen (string + *sindex) + *sindex;
 
+  /* The handling of dolbrace_state needs to agree with the code in parse.y:
+     parse_matched_pair() */
+  dolbrace_state = 0;
+  if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+    dolbrace_state = (flags & SX_POSIXEXP) ? DOLBRACE_QUOTE : DOLBRACE_PARAM;
+
   i = *sindex;
   while (c = string[i])
     {
@@ -1432,6 +1438,7 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
          continue;
        }
 
+#if 0
       /* Pass the contents of single-quoted and double-quoted strings
         through verbatim. */
       if (c == '\'' || c == '"')
@@ -1442,9 +1449,50 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
          /* skip_XXX_quoted leaves index one past close quote */
          continue;
        }
+#else  /* XXX - bash-4.2 */
+      /* Pass the contents of double-quoted strings through verbatim. */
+      if (c == '"')
+       {
+         si = i + 1;
+         i = skip_double_quoted (string, slen, si);
+         /* skip_XXX_quoted leaves index one past close quote */
+         continue;
+       }
+
+      if (c == '\'')
+       {
+/*itrace("extract_dollar_brace_string: c == single quote flags = %d quoted = %d dolbrace_state = %d", flags, quoted, dolbrace_state);*/
+         if (posixly_correct && shell_compatibility_level > 41 && dolbrace_state != DOLBRACE_QUOTE && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+           ADVANCE_CHAR (string, slen, i);
+         else
+           {
+             si = i + 1;
+             i = skip_single_quoted (string, slen, si);
+           }
+
+          continue;
+       }
+#endif
 
       /* move past this character, which was not special. */
       ADVANCE_CHAR (string, slen, i);
+
+      /* This logic must agree with parse.y:parse_matched_pair, since they
+        share the same defines. */
+      if (dolbrace_state == DOLBRACE_PARAM && c == '%' && (i - *sindex) > 1)
+       dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '#' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '/' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '^' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == ',' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", c) != 0)
+       dolbrace_state = DOLBRACE_OP;
+      else if (dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", c) == 0)
+       dolbrace_state = DOLBRACE_WORD;
     }
 
   if (c == 0 && nesting_level)
@@ -1645,7 +1693,7 @@ skip_to_delim (string, start, delims, flags)
 {
   int i, pass_next, backq, si, c, invert, skipquote, skipcmd;
   size_t slen;
-  char *temp;
+  char *temp, open[3];
   DECLARE_MBSTATE;
 
   slen = strlen (string + start) + start;
@@ -1728,6 +1776,25 @@ skip_to_delim (string, start, delims, flags)
          continue;
        }
 #endif /* PROCESS_SUBSTITUTION */
+#if defined (EXTENDED_GLOB)
+      else if ((flags & SD_EXTGLOB) && extended_glob && string[i+1] == LPAREN && member (c, "?*+!@"))
+       {
+         si = i + 2;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+
+         open[0] = c;
+         open[1] = LPAREN;
+         open[2] = '\0';
+         temp = extract_delimited_string (string, &si, open, "(", ")", SX_NOALLOC); /* ) */
+
+         i = si;
+         if (string[i] == '\0')        /* don't increment i past EOS in loop */
+           break;
+         i++;
+         continue;
+       }
+#endif
       else if ((skipquote || invert) && (member (c, delims) == 0))
        break;
       else
@@ -2230,11 +2297,7 @@ string_list_dollar_at (list, quoted)
 
   /* XXX -- why call quote_list if ifs == 0?  we can get away without doing
      it now that quote_escapes quotes spaces */
-#if 0
-  tlist = ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (ifs && *ifs == 0))
-#else
   tlist = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE))
-#endif
                ? quote_list (list)
                : list_quote_escapes (list);
 
@@ -2661,8 +2724,8 @@ do_assignment_internal (word, expand)
      const WORD_DESC *word;
      int expand;
 {
-  int offset, tlen, appendop, assign_list, aflags, retval;
-  char *name, *value;
+  int offset, appendop, assign_list, aflags, retval;
+  char *name, *value, *temp;
   SHELL_VAR *entry;
 #if defined (ARRAY_VARS)
   char *t;
@@ -2681,8 +2744,6 @@ do_assignment_internal (word, expand)
 
   if (name[offset] == '=')
     {
-      char *temp;
-
       if (name[offset - 1] == '+')
        {
          appendop = 1;
@@ -2691,7 +2752,6 @@ do_assignment_internal (word, expand)
 
       name[offset] = 0;                /* might need this set later */
       temp = name + offset + 1;
-      tlen = STRLEN (temp);
 
 #if defined (ARRAY_VARS)
       if (expand && (word->flags & W_COMPASSIGN))
@@ -2792,8 +2852,9 @@ do_assignment (string)
 }
 
 int
-do_word_assignment (word)
+do_word_assignment (word, flags)
      WORD_DESC *word;
+     int flags;
 {
   return do_assignment_internal (word, 1);
 }
@@ -3720,6 +3781,7 @@ mb_getcharlens (string, len)
 #define RP_LONG_RIGHT  3
 #define RP_SHORT_RIGHT 4
 
+/* Returns its first argument if nothing matched; new memory otherwise */
 static char *
 remove_upattern (param, pattern, op)
      char *param, *pattern;
@@ -3788,10 +3850,11 @@ remove_upattern (param, pattern, op)
        break;
     }
 
-  return (savestring (param)); /* no match, return original string */
+  return (param);      /* no match, return original string */
 }
 
 #if defined (HANDLE_MULTIBYTE)
+/* Returns its first argument if nothing matched; new memory otherwise */
 static wchar_t *
 remove_wpattern (wparam, wstrlen, wpattern, op)
      wchar_t *wparam;
@@ -3857,7 +3920,7 @@ remove_wpattern (wparam, wstrlen, wpattern, op)
        break;
     }
 
-  return (wcsdup (wparam));    /* no match, return original string */
+  return (wparam);     /* no match, return original string */
 }
 #endif /* HANDLE_MULTIBYTE */
 
@@ -3866,6 +3929,8 @@ remove_pattern (param, pattern, op)
      char *param, *pattern;
      int op;
 {
+  char *xret;
+
   if (param == NULL)
     return (param);
   if (*param == '\0' || pattern == NULL || *pattern == '\0')   /* minor optimization */
@@ -3878,18 +3943,29 @@ remove_pattern (param, pattern, op)
       size_t n;
       wchar_t *wparam, *wpattern;
       mbstate_t ps;
-      char *xret;
 
       n = xdupmbstowcs (&wpattern, NULL, pattern);
       if (n == (size_t)-1)
-       return (remove_upattern (param, pattern, op));
+       {
+         xret = remove_upattern (param, pattern, op);
+         return ((xret == param) ? savestring (param) : xret);
+       }
       n = xdupmbstowcs (&wparam, NULL, param);
       if (n == (size_t)-1)
        {
          free (wpattern);
-         return (remove_upattern (param, pattern, op));
+         xret = remove_upattern (param, pattern, op);
+         return ((xret == param) ? savestring (param) : xret);
        }
       oret = ret = remove_wpattern (wparam, n, wpattern, op);
+      /* Don't bother to convert wparam back to multibyte string if nothing
+        matched; just return copy of original string */
+      if (ret == wparam)
+        {
+          free (wparam);
+          free (wpattern);
+          return (savestring (param));
+        }
 
       free (wparam);
       free (wpattern);
@@ -3904,36 +3980,9 @@ remove_pattern (param, pattern, op)
     }
   else
 #endif
-    return (remove_upattern (param, pattern, op));
-}
-
-/* Return 1 of the first character of STRING could match the first
-   character of pattern PAT.  Used to avoid n2 calls to strmatch(). */
-static int
-match_pattern_char (pat, string)
-     char *pat, *string;
-{
-  char c;
-
-  if (*string == 0)
-    return (0);
-
-  switch (c = *pat++)
     {
-    default:
-      return (*string == c);
-    case '\\':
-      return (*string == *pat);
-    case '?':
-      return (*pat == LPAREN ? 1 : (*string != '\0'));
-    case '*':
-      return (1);
-    case '+':
-    case '!':
-    case '@':
-      return (*pat == LPAREN ? 1 : (*string == c));
-    case '[':
-      return (*string != '\0');
+      xret = remove_upattern (param, pattern, op);
+      return ((xret == param) ? savestring (param) : xret);
     }
 }
 
@@ -3949,9 +3998,10 @@ match_upattern (string, pat, mtype, sp, ep)
      int mtype;
      char **sp, **ep;
 {
-  int c, len;
+  int c, len, mlen;
   register char *p, *p1, *npat;
   char *end;
+  int n1;
 
   /* If the pattern doesn't match anywhere in the string, go ahead and
      short-circuit right away.  A minor optimization, saves a bunch of
@@ -3985,6 +4035,8 @@ match_upattern (string, pat, mtype, sp, ep)
   len = STRLEN (string);
   end = string + len;
 
+  mlen = umatchlen (pat, len);
+
   switch (mtype)
     {
     case MATCH_ANY:
@@ -3992,7 +4044,22 @@ match_upattern (string, pat, mtype, sp, ep)
        {
          if (match_pattern_char (pat, p))
            {
+#if 0
              for (p1 = end; p1 >= p; p1--)
+#else
+             p1 = (mlen == -1) ? end : p + mlen;
+             /* p1 - p = length of portion of string to be considered
+                p = current position in string
+                mlen = number of characters consumed by match (-1 for entire string)
+                end = end of string
+                we want to break immediately if the potential match len
+                is greater than the number of characters remaining in the
+                string
+             */
+             if (p1 > end)
+               break;
+             for ( ; p1 >= p; p1--)
+#endif
                {
                  c = *p1; *p1 = '\0';
                  if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
@@ -4003,6 +4070,11 @@ match_upattern (string, pat, mtype, sp, ep)
                      return 1;
                    }
                  *p1 = c;
+#if 1
+                 /* If MLEN != -1, we have a fixed length pattern. */
+                 if (mlen != -1)
+                   break;
+#endif
                }
            }
        }
@@ -4013,7 +4085,11 @@ match_upattern (string, pat, mtype, sp, ep)
       if (match_pattern_char (pat, string) == 0)
        return (0);
 
+#if 0
       for (p = end; p >= string; p--)
+#else
+      for (p = (mlen == -1) ? end : string + mlen; p >= string; p--)
+#endif
        {
          c = *p; *p = '\0';
          if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0)
@@ -4024,12 +4100,21 @@ match_upattern (string, pat, mtype, sp, ep)
              return 1;
            }
          *p = c;
+#if 1
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+#endif
        }
 
       return (0);
 
     case MATCH_END:
+#if 0
       for (p = string; p <= end; p++)
+#else
+      for (p = end - ((mlen == -1) ? len : mlen); p <= end; p++)
+#endif
        {
          if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
            {
@@ -4037,7 +4122,11 @@ match_upattern (string, pat, mtype, sp, ep)
              *ep = end;
              return 1;
            }
-
+#if 1
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+#endif
        }
 
       return (0);
@@ -4047,36 +4136,6 @@ match_upattern (string, pat, mtype, sp, ep)
 }
 
 #if defined (HANDLE_MULTIBYTE)
-/* Return 1 of the first character of WSTRING could match the first
-   character of pattern WPAT.  Wide character version. */
-static int
-match_pattern_wchar (wpat, wstring)
-     wchar_t *wpat, *wstring;
-{
-  wchar_t wc;
-
-  if (*wstring == 0)
-    return (0);
-
-  switch (wc = *wpat++)
-    {
-    default:
-      return (*wstring == wc);
-    case L'\\':
-      return (*wstring == *wpat);
-    case L'?':
-      return (*wpat == LPAREN ? 1 : (*wstring != L'\0'));
-    case L'*':
-      return (1);
-    case L'+':
-    case L'!':
-    case L'@':
-      return (*wpat == LPAREN ? 1 : (*wstring == wc));
-    case L'[':
-      return (*wstring != L'\0');
-    }
-}
-
 /* Match WPAT anywhere in WSTRING and return the match boundaries.
    This returns 1 in case of a successful match, 0 otherwise.  Wide
    character version. */
@@ -4090,11 +4149,14 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
      char **sp, **ep;
 {
   wchar_t wc, *wp, *nwpat, *wp1;
-  int len;
-#if 0
-  size_t n, n1;        /* Apple's gcc seems to miscompile this badly */
-#else
-  int n, n1;
+  size_t len;
+  int mlen;
+  int n, n1, n2, simple;
+
+  simple = (wpat[0] != L'\\' && wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'[');
+#if defined (EXTENDED_GLOB)
+  if (extended_glob)
+    simple |= (wpat[1] != L'(' || (wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'+' && wpat[0] != L'!' && wpat[0] != L'@')); /*)*/
 #endif
 
   /* If the pattern doesn't match anywhere in the string, go ahead and
@@ -4103,8 +4165,6 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
      characters) if the match is unsuccessful.  To preserve the semantics
      of the substring matches below, we make sure that the pattern has
      `*' as first and last character, making a new pattern if necessary. */
-  /* XXX - check this later if I ever implement `**' with special meaning,
-     since this will potentially result in `**' at the beginning or end */
   len = wcslen (wpat);
   if (wpat[0] != L'*' || (wpat[0] == L'*' && wpat[1] == WLPAREN && extended_glob) || wpat[len - 1] != L'*')
     {
@@ -4126,14 +4186,30 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
   if (len == FNM_NOMATCH)
     return (0);
 
+  mlen = wmatchlen (wpat, wstrlen);
+
+/* itrace("wmatchlen (%ls) -> %d", wpat, mlen); */
   switch (mtype)
     {
     case MATCH_ANY:
       for (n = 0; n <= wstrlen; n++)
        {
-         if (match_pattern_wchar (wpat, wstring + n))
+#if 1
+         n2 = simple ? (*wpat == wstring[n]) : match_pattern_wchar (wpat, wstring + n);
+#else
+         n2 = match_pattern_wchar (wpat, wstring + n);
+#endif
+         if (n2)
            {
+#if 0
              for (n1 = wstrlen; n1 >= n; n1--)
+#else
+             n1 = (mlen == -1) ? wstrlen : n + mlen;
+             if (n1 > wstrlen)
+               break;
+
+             for ( ; n1 >= n; n1--)
+#endif
                {
                  wc = wstring[n1]; wstring[n1] = L'\0';
                  if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
@@ -4144,6 +4220,11 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
                      return 1;
                    }
                  wstring[n1] = wc;
+#if 1
+                 /* If MLEN != -1, we have a fixed length pattern. */
+                 if (mlen != -1)
+                   break;
+#endif
                }
            }
        }
@@ -4154,7 +4235,11 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
       if (match_pattern_wchar (wpat, wstring) == 0)
        return (0);
 
+#if 0
       for (n = wstrlen; n >= 0; n--)
+#else
+      for (n = (mlen == -1) ? wstrlen : mlen; n >= 0; n--)
+#endif
        {
          wc = wstring[n]; wstring[n] = L'\0';
          if (wcsmatch (wpat, wstring, FNMATCH_EXTFLAG) == 0)
@@ -4165,12 +4250,21 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
              return 1;
            }
          wstring[n] = wc;
+#if 1
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+#endif
        }
 
       return (0);
 
     case MATCH_END:
+#if 0
       for (n = 0; n <= wstrlen; n++)
+#else
+      for (n = wstrlen - ((mlen == -1) ? wstrlen : mlen); n <= wstrlen; n++)
+#endif
        {
          if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
            {
@@ -4178,6 +4272,11 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
              *ep = indices[wstrlen];
              return 1;
            }
+#if 1
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+#endif
        }
 
       return (0);
@@ -4198,6 +4297,7 @@ match_pattern (string, pat, mtype, sp, ep)
   size_t n;
   wchar_t *wstring, *wpat;
   char **indices;
+  size_t slen, plen, mslen, mplen;
 #endif
 
   if (string == 0 || *string == 0 || pat == 0 || *pat == 0)
@@ -4206,6 +4306,17 @@ match_pattern (string, pat, mtype, sp, ep)
 #if defined (HANDLE_MULTIBYTE)
   if (MB_CUR_MAX > 1)
     {
+#if 0
+      slen = STRLEN (string);
+      mslen = MBSLEN (string);
+      plen = STRLEN (pat);
+      mplen = MBSLEN (pat);
+      if (slen == mslen && plen == mplen)
+#else
+      if (mbsmbchar (string) == 0 && mbsmbchar (pat) == 0)
+#endif
+        return (match_upattern (string, pat, mtype, sp, ep));
+
       n = xdupmbstowcs (&wpat, NULL, pat);
       if (n == (size_t)-1)
        return (match_upattern (string, pat, mtype, sp, ep));
@@ -4381,9 +4492,11 @@ array_remove_pattern (var, pattern, patspec, varname, quoted)
 #endif /* ARRAY_VARS */
 
 static char *
-parameter_brace_remove_pattern (varname, value, patstr, rtype, quoted)
-     char *varname, *value, *patstr;
-     int rtype, quoted;
+parameter_brace_remove_pattern (varname, value, ind, patstr, rtype, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *patstr;
+     int rtype, quoted, flags;
 {
   int vtype, patspec, starsub;
   char *temp1, *val, *pattern;
@@ -4394,7 +4507,7 @@ parameter_brace_remove_pattern (varname, value, patstr, rtype, quoted)
 
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, quoted, &v, &val);
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
   if (vtype == -1)
     return ((char *)NULL);
 
@@ -4548,6 +4661,15 @@ static struct temp_fifo *fifo_list = (struct temp_fifo *)NULL;
 static int nfifo;
 static int fifo_list_size;
 
+char *
+copy_fifo_list (sizep)
+     int *sizep;
+{
+  if (sizep)
+    *sizep = 0;
+  return (char *)NULL;
+}
+
 static void
 add_fifo_list (pathname)
      char *pathname;
@@ -4564,6 +4686,19 @@ add_fifo_list (pathname)
 }
 
 void
+unlink_fifo (i)
+     int i;
+{
+  if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1))
+    {
+      unlink (fifo_list[i].file);
+      free (fifo_list[i].file);
+      fifo_list[i].file = (char *)NULL;
+      fifo_list[i].proc = -1;
+    }
+}
+
+void
 unlink_fifo_list ()
 {
   int saved, i, j;
@@ -4600,12 +4735,44 @@ unlink_fifo_list ()
     nfifo = 0;
 }
 
+/* Take LIST, which is a bitmap denoting active FIFOs in fifo_list
+   from some point in the past, and close all open FIFOs in fifo_list
+   that are not marked as active in LIST.  If LIST is NULL, close
+   everything in fifo_list. LSIZE is the number of elements in LIST, in
+   case it's larger than fifo_list_size (size of fifo_list). */
+void
+close_new_fifos (list, lsize)
+     char *list;
+     int lsize;
+{
+  int i;
+
+  if (list == 0)
+    {
+      unlink_fifo_list ();
+      return;
+    }
+
+  for (i = 0; i < lsize; i++)
+    if (list[i] == 0 && i < fifo_list_size && fifo_list[i].proc != -1)
+      unlink_fifo (i);
+
+  for (i = lsize; i < fifo_list_size; i++)
+    unlink_fifo (i);  
+}
+
 int
 fifos_pending ()
 {
   return nfifo;
 }
 
+int
+num_fifos ()
+{
+  return nfifo;
+}
+
 static char *
 make_named_pipe ()
 {
@@ -4632,11 +4799,30 @@ static char *dev_fd_list = (char *)NULL;
 static int nfds;
 static int totfds;     /* The highest possible number of open files. */
 
+char *
+copy_fifo_list (sizep)
+     int *sizep;
+{
+  char *ret;
+
+  if (nfds == 0 || totfds == 0)
+    {
+      if (sizep)
+       *sizep = 0;
+      return (char *)NULL;
+    }
+
+  if (sizep)
+    *sizep = totfds;
+  ret = (char *)xmalloc (totfds);
+  return (memcpy (ret, dev_fd_list, totfds));
+}
+
 static void
 add_fifo_list (fd)
      int fd;
 {
-  if (!dev_fd_list || fd >= totfds)
+  if (dev_fd_list == 0 || fd >= totfds)
     {
       int ofds;
 
@@ -4661,6 +4847,24 @@ fifos_pending ()
   return 0;    /* used for cleanup; not needed with /dev/fd */
 }
 
+int
+num_fifos ()
+{
+  return nfds;
+}
+
+void
+unlink_fifo (fd)
+     int fd;
+{
+  if (dev_fd_list[fd])
+    {
+      close (fd);
+      dev_fd_list[fd] = 0;
+      nfds--;
+    }
+}
+
 void
 unlink_fifo_list ()
 {
@@ -4670,16 +4874,37 @@ unlink_fifo_list ()
     return;
 
   for (i = 0; nfds && i < totfds; i++)
-    if (dev_fd_list[i])
-      {
-       close (i);
-       dev_fd_list[i] = 0;
-       nfds--;
-      }
+    unlink_fifo (i);
 
   nfds = 0;
 }
 
+/* Take LIST, which is a snapshot copy of dev_fd_list from some point in
+   the past, and close all open fds in dev_fd_list that are not marked
+   as open in LIST.  If LIST is NULL, close everything in dev_fd_list.
+   LSIZE is the number of elements in LIST, in case it's larger than
+   totfds (size of dev_fd_list). */
+void
+close_new_fifos (list, lsize)
+     char *list;
+     int lsize;
+{
+  int i;
+
+  if (list == 0)
+    {
+      unlink_fifo_list ();
+      return;
+    }
+
+  for (i = 0; i < lsize; i++)
+    if (list[i] == 0 && i < totfds && dev_fd_list[i])
+      unlink_fifo (i);
+
+  for (i = lsize; i < totfds; i++)
+    unlink_fifo (i);  
+}
+
 #if defined (NOTDEF)
 print_dev_fd_list ()
 {
@@ -4785,7 +5010,7 @@ process_substitute (string, open_for_read_in_child)
       reset_terminating_signals ();    /* XXX */
       free_pushed_string_input ();
       /* Cancel traps, in trap.c. */
-      restore_original_signals ();
+      restore_original_signals ();     /* XXX - what about special builtins? bash-4.2 */
       setup_async_signals ();
       subshell_environment |= SUBSHELL_COMSUB|SUBSHELL_PROCSUB;
     }
@@ -4921,10 +5146,6 @@ read_comsub (fd, quoted, rflag)
   for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
     skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
 
-#ifdef __CYGWIN__
-  setmode (fd, O_TEXT);                /* we don't want CR/LF, we want Unix-style */
-#endif
-
   /* Read the output of the command through the pipe.  This may need to be
      changed to understand multibyte characters in the future. */
   while (1)
@@ -5078,9 +5299,13 @@ command_substitute (string, quoted)
   last_asynchronous_pid = old_async_pid;
 
   if (pid == 0)
-    /* Reset the signal handlers in the child, but don't free the
-       trap strings. */
-    reset_signal_handlers ();
+    {
+      /* Reset the signal handlers in the child, but don't free the
+        trap strings.  Set a flag noting that we have to free the
+        trap strings if we run trap to change a signal disposition. */
+      reset_signal_handlers ();
+      subshell_environment |= SUBSHELL_RESETTRAP;
+    }
 
 #if defined (JOB_CONTROL)
   /* XXX DO THIS ONLY IN PARENT ? XXX */
@@ -5131,6 +5356,13 @@ command_substitute (string, quoted)
          (fildes[0] != fileno (stderr)))
        close (fildes[0]);
 
+#ifdef __CYGWIN__
+      /* Let stdio know the fd may have changed from text to binary mode, and
+        make sure to preserve stdout line buffering. */
+      freopen (NULL, "w", stdout);
+      sh_setlinebuf (stdout);
+#endif /* __CYGWIN__ */
+
       /* The currently executing shell is not interactive. */
       interactive = 0;
 
@@ -5210,11 +5442,7 @@ command_substitute (string, quoted)
         pipline, so what we are concerned about is whether or not that
         pipeline was started in the background.  A pipeline started in
         the background should never get the tty back here. */
-#if 0
-      if (interactive && pipeline_pgrp != (pid_t)0 && pipeline_pgrp != last_asynchronous_pid)
-#else
       if (interactive && pipeline_pgrp != (pid_t)0 && (subshell_environment & SUBSHELL_ASYNC) == 0)
-#endif
        give_terminal_to (pipeline_pgrp, 0);
 #endif /* JOB_CONTROL */
 
@@ -5243,6 +5471,7 @@ array_length_reference (s)
   char *akey;
   char *t, c;
   ARRAY *array;
+  HASH_TABLE *h;
   SHELL_VAR *var;
 
   var = array_variable_part (s, &t, &len);
@@ -5266,15 +5495,16 @@ array_length_reference (s)
      v[*].  Return 0 for everything else. */
 
   array = array_p (var) ? array_cell (var) : (ARRAY *)NULL;
+  h = assoc_p (var) ? assoc_cell (var) : (HASH_TABLE *)NULL;
 
   if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
     {
       if (assoc_p (var))
-       return (assoc_num_elements (assoc_cell (var)));
+       return (h ? assoc_num_elements (h) : 0);
       else if (array_p (var))
-       return (array_num_elements (array));
+       return (array ? array_num_elements (array) : 0);
       else
-       return 1;
+       return (var_isset (var) ? 1 : 0);
     }
 
   if (assoc_p (var))
@@ -5392,20 +5622,25 @@ chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at)
    the shell, e.g., "@", "$", "*", etc.  QUOTED, if non-zero, means that
    NAME was found inside of a double-quoted expression. */
 static WORD_DESC *
-parameter_brace_expand_word (name, var_is_special, quoted, pflags)
+parameter_brace_expand_word (name, var_is_special, quoted, pflags, indp)
      char *name;
      int var_is_special, quoted, pflags;
+     arrayind_t *indp;
 {
   WORD_DESC *ret;
   char *temp, *tt;
   intmax_t arg_index;
   SHELL_VAR *var;
   int atype, rflags;
+  arrayind_t ind;
 
   ret = 0;
   temp = 0;
   rflags = 0;
 
+  if (indp)
+    *indp = INTMAX_MIN;
+
   /* Handle multiple digit arguments, as in ${11}. */  
   if (legal_number (name, &arg_index))
     {
@@ -5432,11 +5667,16 @@ parameter_brace_expand_word (name, var_is_special, quoted, pflags)
 #if defined (ARRAY_VARS)
   else if (valid_array_reference (name))
     {
-      temp = array_value (name, quoted, &atype);
+      temp = array_value (name, quoted, 0, &atype, &ind);
       if (atype == 0 && temp)
-       temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
-                 ? quote_string (temp)
-                 : quote_escapes (temp);
+       {
+         temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+                   ? quote_string (temp)
+                   : quote_escapes (temp);
+         rflags |= W_ARRAYIND;
+         if (indp)
+           *indp = ind;
+       }                 
       else if (atype == 1 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
        rflags |= W_HASQUOTEDNULL;
     }
@@ -5487,7 +5727,7 @@ parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, c
   char *temp, *t;
   WORD_DESC *w;
 
-  w = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND);
+  w = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND, 0);
   t = w->word;
   /* Have to dequote here if necessary */
   if (t)
@@ -5504,7 +5744,7 @@ parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, c
   if (t == 0)
     return (WORD_DESC *)NULL;
 
-  w = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted, 0);
+  w = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted, 0, 0);
   free (t);
 
   return w;
@@ -5588,9 +5828,11 @@ parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat)
   else
 #endif /* ARRAY_VARS */
   bind_variable (name, t1, 0);
-  free (t1);
 
-  w->word = temp;
+  /* From Posix group discussion Feb-March 2010.  Issue 7 0000221 */
+  free (temp);
+
+  w->word = t1;
   return w;
 }
 
@@ -5637,34 +5879,6 @@ valid_length_expression (name)
          legal_identifier (name + 1));                         /* ${#PS1} */
 }
 
-#if defined (HANDLE_MULTIBYTE)
-size_t
-mbstrlen (s)
-     const char *s;
-{
-  size_t clen, nc;
-  mbstate_t mbs, mbsbak;
-
-  nc = 0;
-  memset (&mbs, 0, sizeof (mbs));
-  mbsbak = mbs;
-  while ((clen = mbrlen(s, MB_CUR_MAX, &mbs)) != 0)
-    {
-      if (MB_INVALIDCH(clen))
-        {
-         clen = 1;     /* assume single byte */
-         mbs = mbsbak;
-        }
-
-      s += clen;
-      nc++;
-      mbsbak = mbs;
-    }
-  return nc;
-}
-#endif
-      
-
 /* Handle the parameter brace expansion that requires us to return the
    length of a parameter. */
 static intmax_t
@@ -5698,7 +5912,7 @@ parameter_brace_expand_length (name)
          break;
        case '!':
          if (last_asynchronous_pid == NO_PID)
-           t = (char *)NULL;
+           t = (char *)NULL;   /* XXX - error if set -u set? */
          else
            t = itos (last_asynchronous_pid);
          break;
@@ -5720,6 +5934,8 @@ parameter_brace_expand_length (name)
       if (legal_number (name + 1, &arg_index))         /* ${#1} */
        {
          t = get_dollar_var_value (arg_index);
+         if (t == 0 && unbound_vars_is_error)
+           return INTMAX_MIN;
          number = MB_STRLEN (t);
          FREE (t);
        }
@@ -5730,6 +5946,8 @@ parameter_brace_expand_length (name)
            t = assoc_reference (assoc_cell (var), "0");
          else
            t = array_reference (array_cell (var), 0);
+         if (t == 0 && unbound_vars_is_error)
+           return INTMAX_MIN;
          number = MB_STRLEN (t);
        }
 #endif
@@ -5743,7 +5961,7 @@ parameter_brace_expand_length (name)
          if (list)
            dispose_words (list);
 
-         number = MB_STRLEN (t);
+         number = t ? MB_STRLEN (t) : 0;
          FREE (t);
        }
     }
@@ -5902,7 +6120,7 @@ verify_substring_values (v, value, substr, vtype, e1p, e2p)
       free (temp1);
       if (expok == 0)
        return (0);
-      if (*e2p < 0)
+      if ((vtype == VT_ARRAYVAR || vtype == VT_POSPARMS) && *e2p < 0)
        {
          internal_error (_("%s: substring expression < 0"), t);
          return (0);
@@ -5914,7 +6132,17 @@ verify_substring_values (v, value, substr, vtype, e1p, e2p)
       if (vtype != VT_ARRAYVAR)
 #endif
        {
-         *e2p += *e1p;         /* want E2 chars starting at E1 */
+         if (*e2p < 0)
+           {
+             *e2p += len;
+             if (*e2p < 0 || *e2p < *e1p)
+               {
+                 internal_error (_("%s: substring expression < 0"), t);
+                 return (0);
+               }
+           }
+         else
+           *e2p += *e1p;               /* want E2 chars starting at E1 */
          if (*e2p > len)
            *e2p = len;
        }
@@ -5928,13 +6156,18 @@ verify_substring_values (v, value, substr, vtype, e1p, e2p)
 /* Return the type of variable specified by VARNAME (simple variable,
    positional param, or array variable).  Also return the value specified
    by VARNAME (value of a variable or a reference to an array element).
+   QUOTED is the standard description of quoting state, using Q_* defines.
+   FLAGS is currently a set of flags to pass to array_value.  If IND is
+   non-null and not INTMAX_MIN, and FLAGS includes AV_USEIND, IND is
+   passed to array_value so the array index is not computed again.
    If this returns VT_VARIABLE, the caller assumes that CTLESC and CTLNUL
    characters in the value are quoted with CTLESC and takes appropriate
    steps.  For convenience, *VALP is set to the dequoted VALUE. */
 static int
-get_var_and_type (varname, value, quoted, varp, valp)
+get_var_and_type (varname, value, ind, quoted, flags, varp, valp)
      char *varname, *value;
-     int quoted;
+     arrayind_t ind;
+     int quoted, flags;
      SHELL_VAR **varp;
      char **valp;
 {
@@ -5943,6 +6176,7 @@ get_var_and_type (varname, value, quoted, varp, valp)
 #if defined (ARRAY_VARS)
   SHELL_VAR *v;
 #endif
+  arrayind_t lind;
 
   /* This sets vtype to VT_VARIABLE or VT_POSPARMS */
   vtype = (varname[0] == '@' || varname[0] == '*') && varname[1] == '\0';
@@ -5954,6 +6188,9 @@ get_var_and_type (varname, value, quoted, varp, valp)
   if (valid_array_reference (varname))
     {
       v = array_variable_part (varname, &temp, (int *)0);
+      /* If we want to signal array_value to use an already-computed index,
+        set LIND to that index */
+      lind = (ind != INTMAX_MIN && (flags & AV_USEIND)) ? ind : 0;
       if (v && (array_p (v) || assoc_p (v)))
        { /* [ */
          if (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']')
@@ -5967,7 +6204,7 @@ get_var_and_type (varname, value, quoted, varp, valp)
          else
            {
              vtype = VT_ARRAYMEMBER;
-             *valp = array_value (varname, 1, (int *)NULL);
+             *valp = array_value (varname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
            }
          *varp = v;
        }
@@ -5984,7 +6221,7 @@ get_var_and_type (varname, value, quoted, varp, valp)
        {
          vtype = VT_ARRAYMEMBER;
          *varp = v;
-         *valp = array_value (varname, 1, (int *)NULL);
+         *valp = array_value (varname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
        }
     }
   else if ((v = find_variable (varname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v)))
@@ -6051,9 +6288,11 @@ mb_substring (string, s, e)
    VARNAME.  If VARNAME is an array variable, use the array elements. */
 
 static char *
-parameter_brace_substring (varname, value, substr, quoted)
-     char *varname, *value, *substr;
-     int quoted;
+parameter_brace_substring (varname, value, ind, substr, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *substr;
+     int quoted, flags;
 {
   intmax_t e1, e2;
   int vtype, r, starsub;
@@ -6066,7 +6305,7 @@ parameter_brace_substring (varname, value, substr, quoted)
   oname = this_command_name;
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, quoted, &v, &val);
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
   if (vtype == -1)
     {
       this_command_name = oname;
@@ -6138,26 +6377,52 @@ parameter_brace_substring (varname, value, substr, quoted)
 /*                                                             */
 /****************************************************************/
 
+static int
+shouldexp_replacement (s)
+     char *s;
+{
+  register char *p;
+
+  for (p = s; p && *p; p++)
+    {
+      if (*p == '\\')
+       p++;
+      else if (*p == '&')
+       return 1;
+    }
+  return 0;
+}
+
 char *
 pat_subst (string, pat, rep, mflags)
      char *string, *pat, *rep;
      int mflags;
 {
-  char *ret, *s, *e, *str;
-  int rsize, rptr, l, replen, mtype;
+  char *ret, *s, *e, *str, *rstr, *mstr;
+  int rsize, rptr, l, replen, mtype, rxpand, rslen, mlen;
+
+  if (string  == 0)
+    return (savestring (""));
 
   mtype = mflags & MATCH_TYPEMASK;
 
+#if 0  /* bash-4.2 ? */
+  rxpand = (rep && *rep) ? shouldexp_replacement (rep) : 0;
+#else
+  rxpand = 0;
+#endif
+
   /* Special cases:
    *   1.  A null pattern with mtype == MATCH_BEG means to prefix STRING
    *       with REP and return the result.
    *   2.  A null pattern with mtype == MATCH_END means to append REP to
    *       STRING and return the result.
+   * These don't understand or process `&' in the replacement string.
    */
   if ((pat == 0 || *pat == 0) && (mtype == MATCH_BEG || mtype == MATCH_END))
     {
       replen = STRLEN (rep);
-      l = strlen (string);
+      l = STRLEN (string);
       ret = (char *)xmalloc (replen + l + 2);
       if (replen == 0)
        strcpy (ret, string);
@@ -6182,7 +6447,25 @@ pat_subst (string, pat, rep, mflags)
       if (match_pattern (str, pat, mtype, &s, &e) == 0)
        break;
       l = s - str;
-      RESIZE_MALLOCED_BUFFER (ret, rptr, (l + replen), rsize, 64);
+
+      if (rxpand)
+        {
+          int x;
+          mlen = e - s;
+          mstr = xmalloc (mlen + 1);
+         for (x = 0; x < mlen; x++)
+           mstr[x] = s[x];
+          mstr[mlen] = '\0';
+          rstr = strcreplace (rep, '&', mstr, 0);
+          rslen = strlen (rstr);
+        }
+      else
+        {
+          rstr = rep;
+          rslen = replen;
+        }
+        
+      RESIZE_MALLOCED_BUFFER (ret, rptr, (l + rslen), rsize, 64);
 
       /* OK, now copy the leading unmatched portion of the string (from
         str to s) to ret starting at rptr (the current offset).  Then copy
@@ -6195,11 +6478,14 @@ pat_subst (string, pat, rep, mflags)
        }
       if (replen)
        {
-         strncpy (ret + rptr, rep, replen);
-         rptr += replen;
+         strncpy (ret + rptr, rstr, rslen);
+         rptr += rslen;
        }
       str = e;         /* e == end of match */
 
+      if (rstr != rep)
+       free (rstr);
+
       if (((mflags & MATCH_GLOBREP) == 0) || mtype != MATCH_ANY)
        break;
 
@@ -6214,7 +6500,7 @@ pat_subst (string, pat, rep, mflags)
     }
 
   /* Now copy the unmatched portion of the input string */
-  if (*str)
+  if (str && *str)
     {
       RESIZE_MALLOCED_BUFFER (ret, rptr, STRLEN(str) + 1, rsize, 64);
       strcpy (ret + rptr, str);
@@ -6275,9 +6561,11 @@ pos_params_pat_subst (string, pat, rep, mflags)
    and the string to substitute.  QUOTED is a flags word containing
    the type of quoting currently in effect. */
 static char *
-parameter_brace_patsub (varname, value, patsub, quoted)
-     char *varname, *value, *patsub;
-     int quoted;
+parameter_brace_patsub (varname, value, ind, patsub, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *patsub;
+     int quoted, flags;
 {
   int vtype, mflags, starsub, delim;
   char *val, *temp, *pat, *rep, *p, *lpatsub, *tt;
@@ -6288,7 +6576,7 @@ parameter_brace_patsub (varname, value, patsub, quoted)
 
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, quoted, &v, &val);
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
   if (vtype == -1)
     return ((char *)NULL);
 
@@ -6463,11 +6751,11 @@ pos_params_modcase (string, pat, modop, mflags)
    to perform.  QUOTED is a flags word containing the type of quoting
    currently in effect. */
 static char *
-parameter_brace_casemod (varname, value, modspec, patspec, quoted)
+parameter_brace_casemod (varname, value, ind, modspec, patspec, quoted, flags)
      char *varname, *value;
-     int modspec;
+     int ind, modspec;
      char *patspec;
-     int quoted;
+     int quoted, flags;
 {
   int vtype, starsub, modop, mflags, x;
   char *val, *temp, *pat, *p, *lpat, *tt;
@@ -6478,7 +6766,7 @@ parameter_brace_casemod (varname, value, modspec, patspec, quoted)
 
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, quoted, &v, &val);
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
   if (vtype == -1)
     return ((char *)NULL);
 
@@ -6629,6 +6917,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
   WORD_DESC *tdesc, *ret;
   int t_index, sindex, c, tflag, modspec;
   intmax_t number;
+  arrayind_t ind;
 
   temp = temp1 = value = (char *)NULL;
   var_is_set = var_is_null = var_is_special = check_nullness = 0;
@@ -6655,18 +6944,13 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
   ret = 0;
   tflag = 0;
 
+  ind = INTMAX_MIN;
+
   /* If the name really consists of a special variable, then make sure
      that we have the entire name.  We don't allow indirect references
      to special variables except `#', `?', `@' and `*'. */
-  if ((sindex == t_index &&
-       (string[t_index] == '-' ||
-        string[t_index] == '?' ||
-        string[t_index] == '#')) ||
-      (sindex == t_index - 1 && string[sindex] == '!' &&
-       (string[t_index] == '#' ||
-        string[t_index] == '?' ||
-        string[t_index] == '@' ||
-        string[t_index] == '*')))
+  if ((sindex == t_index && VALID_SPECIAL_LENGTH_PARAM (string[t_index])) ||
+      (sindex == t_index - 1 && string[sindex] == '!' && VALID_INDIR_PARAM (string[t_index])))
     {
       t_index++;
       free (name);
@@ -6761,6 +7045,13 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
        }
 
       number = parameter_brace_expand_length (name);
+      if (number == INTMAX_MIN && unbound_vars_is_error)
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (name+1);
+         free (name);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
       free (name);
 
       *indexp = sindex;
@@ -6861,7 +7152,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
   if (want_indir)
     tdesc = parameter_brace_expand_indir (name + 1, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at);
   else
-    tdesc = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND|(pflags&PF_NOSPLIT2));
+    tdesc = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND|(pflags&PF_NOSPLIT2), &ind);
 
   if (tdesc)
     {
@@ -6885,7 +7176,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
     {
       /* Extract the contents of the ${ ... } expansion
         according to the Posix.2 rules. */
-      value = extract_dollar_brace_string (string, &sindex, quoted, 0);
+      value = extract_dollar_brace_string (string, &sindex, quoted, (c == '%' || c == '#') ? SX_POSIXEXP : 0);
       if (string[sindex] == RBRACE)
        sindex++;
       else
@@ -6896,10 +7187,25 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 
   *indexp = sindex;
 
+  /* All the cases where an expansion can possibly generate an unbound
+     variable error. */
+  if (want_substring || want_patsub || want_casemod || c == '#' || c == '%' || c == RBRACE)
+    {
+      if (var_is_set == 0 && unbound_vars_is_error && ((name[0] != '@' && name[0] != '*') || name[1]))
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (name);
+         FREE (value);
+         FREE (temp);
+         free (name);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
+    }
+    
   /* If this is a substring spec, process it and add the result. */
   if (want_substring)
     {
-      temp1 = parameter_brace_substring (name, temp, value, quoted);
+      temp1 = parameter_brace_substring (name, temp, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
       FREE (name);
       FREE (value);
       FREE (temp);
@@ -6917,7 +7223,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
     }
   else if (want_patsub)
     {
-      temp1 = parameter_brace_patsub (name, temp, value, quoted);
+      temp1 = parameter_brace_patsub (name, temp, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
       FREE (name);
       FREE (value);
       FREE (temp);
@@ -6938,7 +7244,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 #if defined (CASEMOD_EXPANSIONS)
   else if (want_casemod)
     {
-      temp1 = parameter_brace_casemod (name, temp, modspec, value, quoted);
+      temp1 = parameter_brace_casemod (name, temp, ind, modspec, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
       FREE (name);
       FREE (value);
       FREE (temp);
@@ -6969,15 +7275,6 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
       return &expand_wdesc_error;
 
     case RBRACE:
-      if (var_is_set == 0 && unbound_vars_is_error && ((name[0] != '@' && name[0] != '*') || name[1]))
-       {
-         last_command_exit_value = EXECUTION_FAILURE;
-         err_unboundvar (name);
-         FREE (value);
-         FREE (temp);
-         free (name);
-         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
-       }
       break;
 
     case '#':  /* ${param#[#]pattern} */
@@ -6987,9 +7284,10 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
          FREE (value);
          break;
        }
-      temp1 = parameter_brace_remove_pattern (name, temp, value, c, quoted);
+      temp1 = parameter_brace_remove_pattern (name, temp, ind, value, c, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
       free (temp);
       free (value);
+      free (name);
 
       ret = alloc_word_desc ();
       ret->word = temp1;
@@ -7005,7 +7303,6 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
        {
          /* If the operator is `+', we don't want the value of the named
             variable for anything, just the value of the right hand side. */
-
          if (c == '+')
            {
              /* XXX -- if we're double-quoted and the named variable is "$@",
@@ -7019,6 +7316,11 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
              FREE (temp);
              if (value)
                {
+                 /* From Posix discussion on austin-group list.  Issue 221
+                    requires that backslashes escaping `}' inside
+                    double-quoted ${...} be removed. */
+                 if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+                   quoted |= Q_DOLBRACE;
                  ret = parameter_brace_expand_rhs (name, value, c,
                                                    quoted,
                                                    quoted_dollar_atp,
@@ -7062,6 +7364,11 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
              if (contains_dollar_at)
                *contains_dollar_at = 0;
 
+             /* From Posix discussion on austin-group list.  Issue 221 requires
+                that backslashes escaping `}' inside double-quoted ${...} be
+                removed. */
+             if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+               quoted |= Q_DOLBRACE;
              ret = parameter_brace_expand_rhs (name, value, c, quoted,
                                                quoted_dollar_atp,
                                                contains_dollar_at);
@@ -7225,11 +7532,14 @@ param_expand (string, sindex, quoted, expanded_something,
             is unset, the parameters are separated by ' '; if $IFS is
             null, the parameters are concatenated. */
          temp = (quoted & (Q_DOUBLE_QUOTES|Q_PATQUOTE)) ? string_list_dollar_star (list) : string_list (list);
-         temp1 = quote_string (temp);
-         if (*temp == 0)
-           tflag |= W_HASQUOTEDNULL;
-         free (temp);
-         temp = temp1;
+         if (temp)
+           {
+             temp1 = quote_string (temp);
+             if (*temp == 0)
+               tflag |= W_HASQUOTEDNULL;
+             free (temp);
+             temp = temp1;
+           }
        }
       else
        {
@@ -7878,7 +8188,13 @@ add_string:
          else
            tflag = 0;
 
-         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0))
+         /* From Posix discussion on austin-group list:  Backslash escaping
+            a } in ${...} is removed.  Issue 0000221 */
+         if ((quoted & Q_DOLBRACE) && c == RBRACE)
+           {
+             SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
+           }
+         else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0))
            {
              SCOPY_CHAR_I (twochars, '\\', c, string, sindex, string_size);
            }
@@ -7997,7 +8313,7 @@ add_twochars:
              if (list->next)
                {
 #if 0
-                 if (quoted_dollar_at && word->flags & W_NOSPLIT2)
+                 if (quoted_dollar_at && (word->flags & W_NOSPLIT2))
                    temp = string_list_internal (quote_list (list), " ");
                  else
 #endif
@@ -8037,7 +8353,7 @@ add_twochars:
 
          /* We do not want to add quoted nulls to strings that are only
             partially quoted; we can throw them away. */
-         if (temp == 0 && quoted_state == PARTIALLY_QUOTED)
+         if (temp == 0 && quoted_state == PARTIALLY_QUOTED && (word->flags & (W_NOSPLIT|W_NOSPLIT2)))
            continue;
 
        add_quoted_string:
@@ -8875,7 +9191,7 @@ shell_expand_word_list (tlist, eflags)
          if (tlist->word->flags & W_ASSIGNASSOC)
            make_internal_declare (tlist->word->word, "-A");
 
-         t = do_word_assignment (tlist->word);
+         t = do_word_assignment (tlist->word, 0);
          if (t == 0)
            {
              last_command_exit_value = EXECUTION_FAILURE;
@@ -8975,7 +9291,7 @@ expand_word_list_internal (list, eflags)
              for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
                {
                  this_command_name = (char *)NULL;     /* no arithmetic errors */
-                 tint = do_word_assignment (temp_list->word);
+                 tint = do_word_assignment (temp_list->word, 0);
                  /* Variable assignment errors in non-interactive shells
                     running in Posix.2 mode cause the shell to exit. */
                  if (tint == 0)
@@ -9024,6 +9340,7 @@ expand_word_list_internal (list, eflags)
   if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist)
     {
       sh_wassign_func_t *assign_func;
+      int is_special_builtin, is_builtin_or_func;
 
       /* If the remainder of the words expand to nothing, Posix.2 requires
         that the variable and environment assignments affect the shell's
@@ -9031,11 +9348,16 @@ expand_word_list_internal (list, eflags)
       assign_func = new_list ? assign_in_env : do_word_assignment;
       tempenv_assign_error = 0;
 
+      is_builtin_or_func = (new_list && new_list->word && (find_shell_builtin (new_list->word->word) || find_function (new_list->word->word)));
+      /* Posix says that special builtins exit if a variable assignment error
+        occurs in an assignment preceding it. */
+      is_special_builtin = (posixly_correct && new_list && new_list->word && find_special_builtin (new_list->word->word));
+      
       for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
        {
          this_command_name = (char *)NULL;
          assigning_in_environment = (assign_func == assign_in_env);
-         tint = (*assign_func) (temp_list->word);
+         tint = (*assign_func) (temp_list->word, is_builtin_or_func);
          assigning_in_environment = 0;
          /* Variable assignment errors in non-interactive shells running
             in Posix.2 mode cause the shell to exit. */
@@ -9044,7 +9366,7 @@ expand_word_list_internal (list, eflags)
              if (assign_func == do_word_assignment)
                {
                  last_command_exit_value = EXECUTION_FAILURE;
-                 if (interactive_shell == 0 && posixly_correct)
+                 if (interactive_shell == 0 && posixly_correct && is_special_builtin)
                    exp_jump_to_top_level (FORCE_EOF);
                  else
                    exp_jump_to_top_level (DISCARD);