Bash-4.3 distribution sources and documentation
[platform/upstream/bash.git] / subst.c
diff --git a/subst.c b/subst.c
index 81a3256..e131f4c 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-2013 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"
@@ -87,12 +89,15 @@ extern int errno;
 #define PF_NOCOMSUB    0x01    /* Do not perform command substitution */
 #define PF_IGNUNBOUND  0x02    /* ignore unbound vars even if -u set */
 #define PF_NOSPLIT2    0x04    /* same as W_NOSPLIT2 */
+#define PF_ASSIGNRHS   0x08    /* same as W_ASSIGNRHS */
 
 /* These defs make it easier to use the editor. */
 #define LBRACE         '{'
 #define RBRACE         '}'
 #define LPAREN         '('
 #define RPAREN         ')'
+#define LBRACK         '['
+#define RBRACK         ']'
 
 #if defined (HANDLE_MULTIBYTE)
 #define WLPAREN                L'('
@@ -107,7 +112,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}. */
@@ -132,6 +137,7 @@ pid_t current_command_subst_pid = NO_PID;
 SHELL_VAR *ifs_var;
 char *ifs_value;
 unsigned char ifs_cmap[UCHAR_MAX + 1];
+int ifs_is_set, ifs_is_null;
 
 #if defined (HANDLE_MULTIBYTE)
 unsigned char ifs_firstc[MB_LEN_MAX];
@@ -163,6 +169,7 @@ extern struct fd_bitmap *current_fds_to_close;
 extern int wordexp_only;
 extern int expanding_redir;
 extern int tempenv_assign_error;
+extern int builtin_ignoring_errexit;
 
 #if !defined (HAVE_WCSDUP) && defined (HANDLE_MULTIBYTE)
 extern wchar_t *wcsdup __P((const wchar_t *));
@@ -213,6 +220,8 @@ static WORD_LIST *expand_string_leave_quoted __P((char *, int));
 static WORD_LIST *expand_string_for_rhs __P((char *, int, int *, int *));
 
 static WORD_LIST *list_quote_escapes __P((WORD_LIST *));
+static WORD_LIST *list_dequote_escapes __P((WORD_LIST *));
+
 static char *make_quoted_char __P((int));
 static WORD_LIST *quote_list __P((WORD_LIST *));
 
@@ -244,10 +253,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 +266,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 +280,8 @@ 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 char *parameter_brace_find_indir __P((char *, int, int, int));
 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 +291,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));
@@ -334,6 +344,11 @@ dump_word_flags (flags)
       f &= ~W_ASSIGNASSOC;
       fprintf (stderr, "W_ASSIGNASSOC%s", f ? "|" : "");
     }
+  if (f & W_ASSIGNARRAY)
+    {
+      f &= ~W_ASSIGNARRAY;
+      fprintf (stderr, "W_ASSIGNARRAY%s", f ? "|" : "");
+    }
   if (f & W_HASCTLESC)
     {
       f &= ~W_HASCTLESC;
@@ -364,6 +379,16 @@ dump_word_flags (flags)
       f &= ~W_ASSNBLTIN;
       fprintf (stderr, "W_ASSNBLTIN%s", f ? "|" : "");
     }
+  if (f & W_ASSNGLOBAL)
+    {
+      f &= ~W_ASSNGLOBAL;
+      fprintf (stderr, "W_ASSNGLOBAL%s", f ? "|" : "");
+    }
+  if (f & W_ASSIGNINT)
+    {
+      f &= ~W_ASSIGNINT;
+      fprintf (stderr, "W_ASSIGNINT%s", f ? "|" : "");
+    }
   if (f & W_COMPASSIGN)
     {
       f &= ~W_COMPASSIGN;
@@ -414,20 +439,25 @@ dump_word_flags (flags)
       f &= ~W_NOSPLIT2;
       fprintf (stderr, "W_NOSPLIT2%s", f ? "|" : "");
     }
-  if (f & W_NOGLOB)
-    {
-      f &= ~W_NOGLOB;
-      fprintf (stderr, "W_NOGLOB%s", f ? "|" : "");
-    }
   if (f & W_NOSPLIT)
     {
       f &= ~W_NOSPLIT;
       fprintf (stderr, "W_NOSPLIT%s", f ? "|" : "");
     }
-  if (f & W_GLOBEXP)
+  if (f & W_NOBRACE)
+    {
+      f &= ~W_NOBRACE;
+      fprintf (stderr, "W_NOBRACE%s", f ? "|" : "");
+    }
+  if (f & W_NOGLOB)
+    {
+      f &= ~W_NOGLOB;
+      fprintf (stderr, "W_NOGLOB%s", f ? "|" : "");
+    }
+  if (f & W_SPLITSPACE)
     {
-      f &= ~W_GLOBEXP;
-      fprintf (stderr, "W_GLOBEXP%s", f ? "|" : "");
+      f &= ~W_SPLITSPACE;
+      fprintf (stderr, "W_SPLITSPACE%s", f ? "|" : "");
     }
   if (f & W_ASSIGNMENT)
     {
@@ -608,7 +638,6 @@ unquoted_substring (substr, string)
        {
        case '\\':
          sindex++;
-
          if (string[sindex])
            ADVANCE_CHAR (string, slen, sindex);
          break;
@@ -785,6 +814,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 +888,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 +990,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;
@@ -1167,7 +1197,7 @@ extract_process_subst (string, starter, sindex)
      char *starter;
      int *sindex;
 {
-  return (extract_delimited_string (string, sindex, starter, "(", ")", 0));
+  return (extract_delimited_string (string, sindex, starter, "(", ")", SX_COMMAND));
 }
 #endif /* PROCESS_SUBSTITUTION */
 
@@ -1264,17 +1294,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))
@@ -1330,8 +1358,8 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
     {
       if (no_longjmp_on_fatal_error == 0)
        {
-         report_error (_("bad substitution: no closing `%s' in %s"), closer, string);
          last_command_exit_value = EXECUTION_FAILURE;
+         report_error (_("bad substitution: no closing `%s' in %s"), closer, string);
          exp_jump_to_top_level (DISCARD);
        }
       else
@@ -1370,7 +1398,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 +1406,14 @@ 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().  The different initial value is to handle the
+     case where this function is called to parse the word in
+     ${param op word} (SX_WORD). */
+  dolbrace_state = (flags & SX_WORD) ? DOLBRACE_WORD : DOLBRACE_PARAM;
+  if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && (flags & SX_POSIXEXP))
+    dolbrace_state = DOLBRACE_QUOTE;
+
   i = *sindex;
   while (c = string[i])
     {
@@ -1432,27 +1468,56 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
          continue;
        }
 
-      /* Pass the contents of single-quoted and double-quoted strings
-        through verbatim. */
-      if (c == '\'' || c == '"')
+      /* Pass the contents of double-quoted strings through verbatim. */
+      if (c == '"')
        {
          si = i + 1;
-         i = (c == '\'') ? skip_single_quoted (string, slen, si)
-                         : skip_double_quoted (string, slen, si);
+         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 > 42 && 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;
+       }
+
       /* 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_QUOTE2;      /* XXX */
+      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)
     {
       if (no_longjmp_on_fatal_error == 0)
        {                       /* { */
-         report_error (_("bad substitution: no closing `%s' in %s"), "}", string);
          last_command_exit_value = EXECUTION_FAILURE;
+         report_error (_("bad substitution: no closing `%s' in %s"), "}", string);
          exp_jump_to_top_level (DISCARD);
        }
       else
@@ -1645,7 +1710,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;
@@ -1721,6 +1786,7 @@ skip_to_delim (string, start, delims, flags)
          if (string[si] == '\0')
            CQ_RETURN(si);
          temp = extract_process_subst (string, (c == '<') ? "<(" : ">(", &si);
+         free (temp);          /* no SX_ALLOC here */
          i = si;
          if (string[i] == '\0')
            break;
@@ -1728,6 +1794,39 @@ 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 ((flags & SD_GLOB) && c == LBRACK)
+       {
+         si = i + 1;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+
+         temp = extract_delimited_string (string, &si, "[", "[", "]", SX_NOALLOC); /* ] */
+
+         i = si;
+         if (string[i] == '\0')        /* don't increment i past EOS in loop */
+           break;
+         i++;
+         continue;
+       }
       else if ((skipquote || invert) && (member (c, delims) == 0))
        break;
       else
@@ -2000,6 +2099,8 @@ split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp)
   if (cwp)
     *cwp = cw;
 
+  FREE (d2);
+
   return (REVERSE_LIST (ret, WORD_LIST *));
 }
 #endif /* READLINE */
@@ -2230,11 +2331,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);
 
@@ -2245,7 +2342,7 @@ string_list_dollar_at (list, quoted)
   return ret;
 }
 
-/* Turn the positional paramters into a string, understanding quoting and
+/* Turn the positional parameters into a string, understanding quoting and
    the various subtleties of using the first character of $IFS as the
    separator.  Calls string_list_dollar_at, string_list_dollar_star, and
    string_list as appropriate. */
@@ -2628,11 +2725,12 @@ do_compound_assignment (name, value, flags)
      int flags;
 {
   SHELL_VAR *v;
-  int mklocal, mkassoc;
+  int mklocal, mkassoc, mkglobal;
   WORD_LIST *list;
 
   mklocal = flags & ASS_MKLOCAL;
   mkassoc = flags & ASS_MKASSOC;
+  mkglobal = flags & ASS_MKGLOBAL;
 
   if (mklocal && variable_context)
     {
@@ -2641,8 +2739,25 @@ do_compound_assignment (name, value, flags)
       if (mkassoc)
        v = make_local_assoc_variable (name);
       else if (v == 0 || (array_p (v) == 0 && assoc_p (v) == 0) || v->context != variable_context)
-        v = make_local_array_variable (name);
-      assign_compound_array_list (v, list, flags);
+        v = make_local_array_variable (name, 0);
+      if (v)
+       assign_compound_array_list (v, list, flags);
+    }
+  /* In a function but forcing assignment in global context */
+  else if (mkglobal && variable_context)
+    {
+      v = find_global_variable (name);
+      list = expand_compound_array_assignment (v, value, flags);
+      if (v == 0 && mkassoc)
+       v = make_new_assoc_variable (name);
+      else if (v && mkassoc && assoc_p (v) == 0)
+       v = convert_var_to_assoc (v);
+      else if (v == 0)
+       v = make_new_array_variable (name);
+      else if (v && mkassoc == 0 && array_p (v) == 0)
+       v = convert_var_to_array (v);
+      if (v)
+       assign_compound_array_list (v, list, flags);
     }
   else
     v = assign_array_from_string (name, value, flags);
@@ -2661,8 +2776,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 +2796,6 @@ do_assignment_internal (word, expand)
 
   if (name[offset] == '=')
     {
-      char *temp;
-
       if (name[offset - 1] == '+')
        {
          appendop = 1;
@@ -2691,7 +2804,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))
@@ -2741,8 +2853,10 @@ do_assignment_internal (word, expand)
     }
   else if (assign_list)
     {
-      if (word->flags & W_ASSIGNARG)
+      if ((word->flags & W_ASSIGNARG) && (word->flags & W_ASSNGLOBAL) == 0)
        aflags |= ASS_MKLOCAL;
+      if ((word->flags & W_ASSIGNARG) && (word->flags & W_ASSNGLOBAL))
+       aflags |= ASS_MKGLOBAL;
       if (word->flags & W_ASSIGNASSOC)
        aflags |= ASS_MKASSOC;
       entry = do_compound_assignment (name, value, aflags);
@@ -2753,7 +2867,6 @@ do_assignment_internal (word, expand)
 
   stupidly_hack_special_variables (name);
 
-#if 1
   /* Return 1 if the assignment seems to have been performed correctly. */
   if (entry == 0 || readonly_p (entry))
     retval = 0;                /* assignment failure */
@@ -2769,12 +2882,6 @@ do_assignment_internal (word, expand)
     VUNSETATTR (entry, att_invisible);
 
   ASSIGN_RETURN (retval);
-#else
-  if (entry)
-    VUNSETATTR (entry, att_invisible);
-
-  ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0);
-#endif
 }
 
 /* Perform the assignment statement in STRING, and expand the
@@ -2792,8 +2899,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);
 }
@@ -3045,7 +3153,58 @@ expand_arith_string (string, quoted)
      char *string;
      int quoted;
 {
-  return (expand_string_if_necessary (string, quoted, expand_string));
+  WORD_DESC td;
+  WORD_LIST *list, *tlist;
+  size_t slen;
+  int i, saw_quote;
+  char *ret;
+  DECLARE_MBSTATE;
+
+  /* Don't need string length for ADVANCE_CHAR unless multibyte chars possible. */
+  slen = (MB_CUR_MAX > 1) ? strlen (string) : 0;
+  i = saw_quote = 0;
+  while (string[i])
+    {
+      if (EXP_CHAR (string[i]))
+       break;
+      else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"')
+       saw_quote = 1;
+      ADVANCE_CHAR (string, slen, i);
+    }
+
+  if (string[i])
+    {
+      /* This is expanded version of expand_string_internal as it's called by
+        expand_string_leave_quoted  */
+      td.flags = W_NOPROCSUB;  /* don't want process substitution */
+      td.word = savestring (string);
+      list = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+      /* This takes care of the calls from expand_string_leave_quoted and
+        expand_string */
+      if (list)
+       {
+         tlist = word_list_split (list);
+         dispose_words (list);
+         list = tlist;
+         if (list)
+           dequote_list (list);
+       }
+      /* This comes from expand_string_if_necessary */
+      if (list)
+       {
+         ret = string_list (list);
+         dispose_words (list);
+       }
+      else
+       ret = (char *)NULL;
+      FREE (td.word);
+    }
+  else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+    ret = string_quote_removal (string, quoted);
+  else
+    ret = savestring (string);
+
+  return ret;
 }
 
 #if defined (COND_COMMAND)
@@ -3093,13 +3252,16 @@ cond_expand_word (w, special)
   l = call_expand_word_internal (w, 0, 0, (int *)0, (int *)0);
   if (l)
     {
-      if (special == 0)
+      if (special == 0)                        /* LHS */
        {
          dequote_list (l);
          r = string_list (l);
        }
       else
        {
+         /* Need to figure out whether or not we should call dequote_escapes
+            or a new dequote_ctlnul function here, and under what
+            circumstances. */
          qflags = QGLOB_CVTNULL;
          if (special == 2)
            qflags |= QGLOB_REGEXP;
@@ -3137,13 +3299,16 @@ call_expand_word_internal (w, q, i, c, e)
       last_command_exit_value = EXECUTION_FAILURE;
       exp_jump_to_top_level ((result == &expand_word_error) ? DISCARD : FORCE_EOF);
       /* NOTREACHED */
+      return (NULL);
     }
   else
     return (result);
 }
 
 /* Perform parameter expansion, command substitution, and arithmetic
-   expansion on STRING, as if it were a word.  Leave the result quoted. */
+   expansion on STRING, as if it were a word.  Leave the result quoted.
+   Since this does not perform word splitting, it leaves quoted nulls
+   in the result.  */
 static WORD_LIST *
 expand_string_internal (string, quoted)
      char *string;
@@ -3310,7 +3475,7 @@ expand_string_for_rhs (string, quoted, dollar_at_p, has_dollar_at)
   if (string == 0 || *string == '\0')
     return (WORD_LIST *)NULL;
 
-  td.flags = 0;
+  td.flags = W_NOSPLIT2;               /* no splitting, remove "" and '' */
   td.word = string;
   tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, has_dollar_at);
   return (tresult);
@@ -3384,6 +3549,7 @@ quote_escapes (string)
       COPY_CHAR_P (t, s, send);
     }
   *t = '\0';
+
   return (result);
 }
 
@@ -3449,9 +3615,26 @@ dequote_escapes (string)
       COPY_CHAR_P (t, s, send);
     }
   *t = '\0';
+
   return result;
 }
 
+static WORD_LIST *
+list_dequote_escapes (list)
+     WORD_LIST *list;
+{
+  register WORD_LIST *w;
+  char *t;
+
+  for (w = list; w; w = w->next)
+    {
+      t = w->word->word;
+      w->word->word = dequote_escapes (t);
+      free (t);
+    }
+  return list;
+}
+
 /* Return a new string with the quoted representation of character C.
    This turns "" into QUOTED_NULL, so the W_HASQUOTEDNULL flag needs to be
    set in any resultant WORD_DESC where this value is the word. */
@@ -3643,7 +3826,10 @@ remove_quoted_nulls (string)
            break;
        }
       else if (string[i] == CTLNUL)
-       i++;
+       {
+         i++;
+         continue;
+       }
 
       prev_i = i;
       ADVANCE_CHAR (string, slen, i);
@@ -3720,6 +3906,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 +3975,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 +4045,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 +4054,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 +4068,30 @@ 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 +4106,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 +4124,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 +4161,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 +4170,18 @@ match_upattern (string, pat, mtype, sp, ep)
        {
          if (match_pattern_char (pat, p))
            {
-             for (p1 = end; p1 >= p; p1--)
+             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--)
                {
                  c = *p1; *p1 = '\0';
                  if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
@@ -4003,6 +4192,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 +4207,7 @@ match_upattern (string, pat, mtype, sp, ep)
       if (match_pattern_char (pat, string) == 0)
        return (0);
 
-      for (p = end; p >= string; p--)
+      for (p = (mlen == -1) ? end : string + mlen; p >= string; p--)
        {
          c = *p; *p = '\0';
          if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0)
@@ -4024,12 +4218,15 @@ match_upattern (string, pat, mtype, sp, ep)
              return 1;
            }
          *p = c;
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
        }
 
       return (0);
 
     case MATCH_END:
-      for (p = string; p <= end; p++)
+      for (p = end - ((mlen == -1) ? len : mlen); p <= end; p++)
        {
          if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
            {
@@ -4037,7 +4234,9 @@ match_upattern (string, pat, mtype, sp, ep)
              *ep = end;
              return 1;
            }
-
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
        }
 
       return (0);
@@ -4047,36 +4246,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 +4259,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 +4275,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 +4296,22 @@ 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))
+         n2 = simple ? (*wpat == wstring[n]) : match_pattern_wchar (wpat, wstring + n);
+         if (n2)
            {
-             for (n1 = wstrlen; n1 >= n; n1--)
+             n1 = (mlen == -1) ? wstrlen : n + mlen;
+             if (n1 > wstrlen)
+               break;
+
+             for ( ; n1 >= n; n1--)
                {
                  wc = wstring[n1]; wstring[n1] = L'\0';
                  if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
@@ -4144,6 +4322,9 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
                      return 1;
                    }
                  wstring[n1] = wc;
+                 /* If MLEN != -1, we have a fixed length pattern. */
+                 if (mlen != -1)
+                   break;
                }
            }
        }
@@ -4154,7 +4335,7 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
       if (match_pattern_wchar (wpat, wstring) == 0)
        return (0);
 
-      for (n = wstrlen; n >= 0; n--)
+      for (n = (mlen == -1) ? wstrlen : mlen; n >= 0; n--)
        {
          wc = wstring[n]; wstring[n] = L'\0';
          if (wcsmatch (wpat, wstring, FNMATCH_EXTFLAG) == 0)
@@ -4165,12 +4346,15 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
              return 1;
            }
          wstring[n] = wc;
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
        }
 
       return (0);
 
     case MATCH_END:
-      for (n = 0; n <= wstrlen; n++)
+      for (n = wstrlen - ((mlen == -1) ? wstrlen : mlen); n <= wstrlen; n++)
        {
          if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
            {
@@ -4178,6 +4362,9 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
              *ep = indices[wstrlen];
              return 1;
            }
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
        }
 
       return (0);
@@ -4198,6 +4385,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 +4394,9 @@ match_pattern (string, pat, mtype, sp, ep)
 #if defined (HANDLE_MULTIBYTE)
   if (MB_CUR_MAX > 1)
     {
+      if (mbsmbchar (string) == 0 && mbsmbchar (pat) == 0)
+        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));
@@ -4365,6 +4556,11 @@ array_remove_pattern (var, pattern, patspec, varname, quoted)
 
   /* compute itype from varname here */
   v = array_variable_part (varname, &ret, 0);
+
+  /* XXX */
+  if (v && invisible_p (var))
+    return ((char *)NULL);
+
   itype = ret[0];
 
   a = (v && array_p (v)) ? array_cell (v) : 0;
@@ -4381,9 +4577,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 +4592,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);
 
@@ -4494,6 +4692,7 @@ expand_word_unsplit (word, quoted)
   if (ifs_firstc == 0)
 #endif
     word->flags |= W_NOSPLIT;
+  word->flags |= W_NOSPLIT2;
   result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL);
   expand_no_split_dollar_star = 0;
 
@@ -4548,6 +4747,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 +4772,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 +4821,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 +4885,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 +4933,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 +4960,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 +5096,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;
     }
@@ -4839,7 +5150,7 @@ process_substitute (string, open_for_read_in_child)
 
 #if !defined (HAVE_DEV_FD)
   /* Open the named pipe in the child. */
-  fd = open (pathname, open_for_read_in_child ? O_RDONLY|O_NONBLOCK : O_WRONLY);
+  fd = open (pathname, open_for_read_in_child ? O_RDONLY : O_WRONLY);
   if (fd < 0)
     {
       /* Two separate strings for ease of translation. */
@@ -4888,6 +5199,10 @@ process_substitute (string, open_for_read_in_child)
   dev_fd_list[parent_pipe_fd] = 0;
 #endif /* HAVE_DEV_FD */
 
+  /* subshells shouldn't have this flag, which controls using the temporary
+     environment for variable lookups. */
+  expanding_redir = 0;
+
   result = parse_and_execute (string, "process substitution", (SEVAL_NONINT|SEVAL_NOHIST));
 
 #if !defined (HAVE_DEV_FD)
@@ -4895,6 +5210,8 @@ process_substitute (string, open_for_read_in_child)
   close (open_for_read_in_child ? 0 : 1);
 #endif /* !HAVE_DEV_FD */
 
+  last_command_exit_value = result;
+  result = run_exit_trap ();
   exit (result);
   /*NOTREACHED*/
 }
@@ -4921,10 +5238,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 +5391,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 */
@@ -5097,6 +5414,8 @@ command_substitute (string, quoted)
       sys_error (_("cannot make child for command substitution"));
     error_exit:
 
+      last_made_pid = old_pid;
+
       FREE (istring);
       close (fildes[0]);
       close (fildes[1]);
@@ -5131,6 +5450,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;
 
@@ -5140,20 +5466,24 @@ command_substitute (string, quoted)
       /* When not in POSIX mode, command substitution does not inherit
         the -e flag. */
       if (posixly_correct == 0)
-       exit_immediately_on_error = 0;
+        {
+          builtin_ignoring_errexit = 0;
+         change_flag ('e', FLAG_OFF);
+         set_shellopts ();
+        }
 
       remove_quoted_escapes (string);
 
       startup_state = 2;       /* see if we can avoid a fork */
       /* Give command substitution a place to jump back to on failure,
         so we don't go back up to main (). */
-      result = setjmp (top_level);
+      result = setjmp_nosigs (top_level);
 
       /* If we're running a command substitution inside a shell function,
         trap `return' so we don't return from the function in the subshell
         and go off to never-never land. */
       if (result == 0 && return_catch_flag)
-       function_value = setjmp (return_catch);
+       function_value = setjmp_nosigs (return_catch);
       else
        function_value = 0;
 
@@ -5207,14 +5537,10 @@ command_substitute (string, quoted)
       /* wait_for gives the terminal back to shell_pgrp.  If some other
         process group should have it, give it away to that group here.
         pipeline_pgrp is non-zero only while we are constructing a
-        pipline, so what we are concerned about is whether or not that
+        pipeline, 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,13 +5569,14 @@ array_length_reference (s)
   char *akey;
   char *t, c;
   ARRAY *array;
+  HASH_TABLE *h;
   SHELL_VAR *var;
 
   var = array_variable_part (s, &t, &len);
 
   /* If unbound variables should generate an error, report one and return
      failure. */
-  if ((var == 0 || (assoc_p (var) == 0 && array_p (var) == 0)) && unbound_vars_is_error)
+  if ((var == 0 || invisible_p (var) || (assoc_p (var) == 0 && array_p (var) == 0)) && unbound_vars_is_error)
     {
       c = *--t;
       *t = '\0';
@@ -5258,7 +5585,7 @@ array_length_reference (s)
       *t = c;
       return (-1);
     }
-  else if (var == 0)
+  else if (var == 0 || invisible_p (var))
     return 0;
 
   /* We support a couple of expansions for variables that are not arrays.
@@ -5266,15 +5593,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))
@@ -5285,13 +5613,18 @@ array_length_reference (s)
       if (akey == 0 || *akey == 0)
        {
          err_badarraysub (t);
+         FREE (akey);
          return (-1);
        }
       t = assoc_reference (assoc_cell (var), akey);
+      free (akey);
     }
   else
     {
-      ind = array_expand_index (t, len);
+      ind = array_expand_index (var, t, len);
+      /* negative subscripts to indexed arrays count back from end */
+      if (var && array_p (var) && ind < 0)
+       ind = array_max_index (array_cell (var)) + 1 + ind;
       if (ind < 0)
        {
          err_badarraysub (t);
@@ -5392,20 +5725,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 +5770,27 @@ 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);
+expand_arrayref:
+      /* XXX - does this leak if name[@] or name[*]? */
+      if (pflags & PF_ASSIGNRHS)
+        {
+          temp = array_variable_name (name, &tt, (int *)0);
+          if (ALL_ELEMENT_SUB (tt[0]) && tt[1] == ']')
+           temp = array_value (name, quoted|Q_DOUBLE_QUOTES, 0, &atype, &ind);
+         else
+           temp = array_value (name, quoted, 0, &atype, &ind);
+        }
+      else
+       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;
     }
@@ -5464,6 +5818,28 @@ parameter_brace_expand_word (name, var_is_special, quoted, pflags)
       else
        temp = (char *)NULL;
     }
+  else if (var = find_variable_last_nameref (name))
+    {
+      temp = nameref_cell (var);
+#if defined (ARRAY_VARS)
+      /* Handle expanding nameref whose value is x[n] */
+      if (temp && *temp && valid_array_reference (temp))
+       {
+         name = temp;
+         goto expand_arrayref;
+       }
+      else
+#endif
+      /* y=2 ; typeset -n x=y; echo ${x} is not the same as echo ${2} in ksh */
+      if (temp && *temp && legal_identifier (temp) == 0)
+        {
+         last_command_exit_value = EXECUTION_FAILURE;
+         report_error (_("%s: invalid variable name for name reference"), temp);
+         temp = &expand_param_error;
+        }
+      else
+       temp = (char *)NULL;
+    }
   else
     temp = (char *)NULL;
 
@@ -5476,18 +5852,23 @@ parameter_brace_expand_word (name, var_is_special, quoted, pflags)
   return ret;
 }
 
-/* Expand an indirect reference to a variable: ${!NAME} expands to the
-   value of the variable whose name is the value of NAME. */
-static WORD_DESC *
-parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at)
+static char *
+parameter_brace_find_indir (name, var_is_special, quoted, find_nameref)
      char *name;
-     int var_is_special, quoted;
-     int *quoted_dollar_atp, *contains_dollar_at;
+     int var_is_special, quoted, find_nameref;
 {
   char *temp, *t;
   WORD_DESC *w;
+  SHELL_VAR *v;
 
-  w = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND);
+  if (find_nameref && var_is_special == 0 && (v = find_variable_last_nameref (name)) &&
+      nameref_p (v) && (t = nameref_cell (v)) && *t)
+    return (savestring (t));
+
+  /* If var_is_special == 0, and name is not an array reference, this does
+     more expansion than necessary.  It should really look up the variable's
+     value and not try to expand it. */
+  w = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND, 0);
   t = w->word;
   /* Have to dequote here if necessary */
   if (t)
@@ -5500,11 +5881,44 @@ parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, c
     }
   dispose_word_desc (w);
 
+  return t;
+}
+  
+/* Expand an indirect reference to a variable: ${!NAME} expands to the
+   value of the variable whose name is the value of NAME. */
+static WORD_DESC *
+parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at)
+     char *name;
+     int var_is_special, quoted;
+     int *quoted_dollar_atp, *contains_dollar_at;
+{
+  char *temp, *t;
+  WORD_DESC *w;
+  SHELL_VAR *v;
+
+  /* See if it's a nameref first, behave in ksh93-compatible fashion.
+     There is at least one incompatibility: given ${!foo[0]} where foo=bar,
+     bash performs an indirect lookup on foo[0] and expands the result;
+     ksh93 expands bar[0].  We could do that here -- there are enough usable
+     primitives to do that -- but do not at this point. */
+  if (var_is_special == 0 && (v = find_variable_last_nameref (name)))
+    {
+      if (nameref_p (v) && (t = nameref_cell (v)) && *t)
+       {
+         w = alloc_word_desc ();
+         w->word = savestring (t);
+         w->flags = 0;
+         return w;
+       }
+    }
+
+  t = parameter_brace_find_indir (name, var_is_special, quoted, 0);
+
   chk_atstar (t, quoted, quoted_dollar_atp, contains_dollar_at);
   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;
@@ -5558,6 +5972,16 @@ parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat)
         is the only expansion that creates more than one word. */
       if (qdollaratp && ((hasdol && quoted) || l->next))
        *qdollaratp = 1;
+      /* If we have a quoted null result (QUOTED_NULL(temp)) and the word is
+        a quoted null (l->next == 0 && QUOTED_NULL(l->word->word)), the
+        flags indicate it (l->word->flags & W_HASQUOTEDNULL), and the
+        expansion is quoted (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+        (which is more paranoia than anything else), we need to return the
+        quoted null string and set the flags to indicate it. */
+      if (l->next == 0 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && QUOTED_NULL (temp) && QUOTED_NULL (l->word->word) && (l->word->flags & W_HASQUOTEDNULL))
+       {
+         w->flags |= W_HASQUOTEDNULL;
+       }
       dispose_words (l);
     }
   else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && hasdol)
@@ -5588,9 +6012,15 @@ parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat)
   else
 #endif /* ARRAY_VARS */
   bind_variable (name, t1, 0);
-  free (t1);
+#if 0
+  if (STREQ (name, "IFS") == 0)
+#endif
+    stupidly_hack_special_variables (name);
+
+  /* From Posix group discussion Feb-March 2010.  Issue 7 0000221 */
+  free (temp);
 
-  w->word = temp;
+  w->word = t1;
   return w;
 }
 
@@ -5605,6 +6035,7 @@ parameter_brace_expand_error (name, value)
   WORD_LIST *l;
   char *temp;
 
+  last_command_exit_value = EXECUTION_FAILURE; /* ensure it's non-zero */
   if (value && *value)
     {
       l = expand_string (value, 0);
@@ -5637,34 +6068,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 +6101,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 +6123,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 +6135,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 +6150,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 +6309,13 @@ verify_substring_values (v, value, substr, vtype, e1p, e2p)
       free (temp1);
       if (expok == 0)
        return (0);
-      if (*e2p < 0)
+#if 1
+      if ((vtype == VT_ARRAYVAR || vtype == VT_POSPARMS) && *e2p < 0)
+#else
+      /* bash-4.3: allow positional parameter length < 0 to count backwards
+        from end of positional parameters */
+      if (vtype == VT_ARRAYVAR && *e2p < 0)
+#endif
        {
          internal_error (_("%s: substring expression < 0"), t);
          return (0);
@@ -5914,7 +6327,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,37 +6351,61 @@ 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;
 {
-  int vtype;
-  char *temp;
+  int vtype, want_indir;
+  char *temp, *vname;
+  WORD_DESC *wd;
 #if defined (ARRAY_VARS)
   SHELL_VAR *v;
 #endif
+  arrayind_t lind;
 
+  want_indir = *varname == '!' &&
+    (legal_variable_starter ((unsigned char)varname[1]) || DIGIT (varname[1])
+                                       || VALID_INDIR_PARAM (varname[1]));
+  if (want_indir)
+    vname = parameter_brace_find_indir (varname+1, SPECIAL_VAR (varname, 1), quoted, 1);
+  else
+    vname = varname;
+    
   /* This sets vtype to VT_VARIABLE or VT_POSPARMS */
-  vtype = (varname[0] == '@' || varname[0] == '*') && varname[1] == '\0';
-  if (vtype == VT_POSPARMS && varname[0] == '*')
+  vtype = (vname[0] == '@' || vname[0] == '*') && vname[1] == '\0';
+  if (vtype == VT_POSPARMS && vname[0] == '*')
     vtype |= VT_STARSUB;
   *varp = (SHELL_VAR *)NULL;
 
 #if defined (ARRAY_VARS)
-  if (valid_array_reference (varname))
+  if (valid_array_reference (vname))
     {
-      v = array_variable_part (varname, &temp, (int *)0);
+      v = array_variable_part (vname, &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 && invisible_p (v))
+       {
+         vtype = VT_ARRAYMEMBER;
+         *varp = (SHELL_VAR *)NULL;
+         *valp = (char *)NULL;
+       }
       if (v && (array_p (v) || assoc_p (v)))
        { /* [ */
          if (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']')
            {
-             /* Callers have to differentiate betwen indexed and associative */
+             /* Callers have to differentiate between indexed and associative */
              vtype = VT_ARRAYVAR;
              if (temp[0] == '*')
                vtype |= VT_STARSUB;
@@ -5967,7 +6414,7 @@ get_var_and_type (varname, value, quoted, varp, valp)
          else
            {
              vtype = VT_ARRAYMEMBER;
-             *valp = array_value (varname, 1, (int *)NULL);
+             *valp = array_value (vname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
            }
          *varp = v;
        }
@@ -5984,10 +6431,10 @@ get_var_and_type (varname, value, quoted, varp, valp)
        {
          vtype = VT_ARRAYMEMBER;
          *varp = v;
-         *valp = array_value (varname, 1, (int *)NULL);
+         *valp = array_value (vname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
        }
     }
-  else if ((v = find_variable (varname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v)))
+  else if ((v = find_variable (vname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v)))
     {
       vtype = VT_ARRAYMEMBER;
       *varp = v;
@@ -6007,6 +6454,9 @@ get_var_and_type (varname, value, quoted, varp, valp)
        *valp = value;
     }
 
+  if (want_indir)
+    free (vname);
+
   return vtype;
 }
 
@@ -6051,9 +6501,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 +6518,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;
@@ -6079,7 +6531,11 @@ parameter_brace_substring (varname, value, substr, quoted)
   r = verify_substring_values (v, val, substr, vtype, &e1, &e2);
   this_command_name = oname;
   if (r <= 0)
-    return ((r == 0) ? &expand_param_error : (char *)NULL);
+    {
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      return ((r == 0) ? &expand_param_error : (char *)NULL);
+    }
 
   switch (vtype)
     {
@@ -6138,26 +6594,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 +6664,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 +6695,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 +6717,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);
@@ -6252,18 +6755,7 @@ pos_params_pat_subst (string, pat, rep, mflags)
   pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
   qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
 
-#if 0
-  if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB))
-    ret = string_list_dollar_star (quote_list (save));
-  else if ((mflags & MATCH_STARSUB) == MATCH_STARSUB)
-    ret = string_list_dollar_star (save);
-  else if ((mflags & MATCH_QUOTED) == MATCH_QUOTED)
-    ret = string_list_dollar_at (save, qflags);
-  else
-    ret = string_list_dollar_star (save);
-#else
   ret = string_list_pos_params (pchar, save, qflags);
-#endif
 
   dispose_words (save);
 
@@ -6275,9 +6767,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 +6782,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);
 
@@ -6296,7 +6790,8 @@ parameter_brace_patsub (varname, value, patsub, quoted)
   vtype &= ~VT_STARSUB;
 
   mflags = 0;
-  if (patsub && *patsub == '/')
+  /* PATSUB is never NULL when this is called. */
+  if (*patsub == '/')
     {
       mflags |= MATCH_GLOBREP;
       patsub++;
@@ -6314,12 +6809,6 @@ parameter_brace_patsub (varname, value, patsub, quoted)
 
   /* If the pattern starts with a `/', make sure we skip over it when looking
      for the replacement delimiter. */
-#if 0
-  if (rep = quoted_strchr ((*patsub == '/') ? lpatsub+1 : lpatsub, '/', ST_BACKSL))
-    *rep++ = '\0';
-  else
-    rep = (char *)NULL;
-#else
   delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0);
   if (lpatsub[delim] == '/')
     {
@@ -6328,7 +6817,6 @@ parameter_brace_patsub (varname, value, patsub, quoted)
     }
   else
     rep = (char *)NULL;
-#endif
 
   if (rep && *rep == '\0')
     rep = (char *)NULL;
@@ -6339,7 +6827,14 @@ parameter_brace_patsub (varname, value, patsub, quoted)
 
   if (rep)
     {
-      if ((mflags & MATCH_QUOTED) == 0)
+      /* We want to perform quote removal on the expanded replacement even if
+        the entire expansion is double-quoted because the parser and string
+        extraction functions treated quotes in the replacement string as
+        special.  THIS IS NOT BACKWARDS COMPATIBLE WITH BASH-4.2. */
+      if (shell_compatibility_level > 42)
+       rep = expand_string_if_necessary (rep, quoted & ~(Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT), expand_string_unsplit);
+      /* This is the bash-4.2 code. */      
+      else if ((mflags & MATCH_QUOTED) == 0)
        rep = expand_string_if_necessary (rep, quoted, expand_string_unsplit);
       else
        rep = expand_string_to_string_internal (rep, quoted, expand_string_unsplit);
@@ -6463,11 +6958,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 +6973,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 +7124,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,23 +7151,17 @@ 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);
       temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0);
-      name = (char *)xmalloc (3 + (strlen (temp1)));
+      name = (char *)xrealloc (name, 3 + (strlen (temp1)));
       *name = string[sindex];
       if (string[sindex] == '!')
        {
@@ -6702,7 +7192,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
     }
   else if (c == ':' && string[sindex] != RBRACE)
     want_substring = 1;
-  else if (c == '/' && string[sindex] != RBRACE)
+  else if (c == '/' /* && string[sindex] != RBRACE */) /* XXX */
     want_patsub = 1;
 #if defined (CASEMOD_EXPANSIONS)
   else if (c == '^' || c == ',' || c == '~')
@@ -6761,6 +7251,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;
@@ -6782,6 +7279,8 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 
       if (contains_dollar_at)
        *contains_dollar_at = 1;
+
+      tflag |= W_DOLLARAT;
     }
 
   /* Process ${!PREFIX*} expansion. */
@@ -6806,14 +7305,19 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
            *quoted_dollar_atp = 1;
          if (contains_dollar_at)
            *contains_dollar_at = 1;
+
+         tflag |= W_DOLLARAT;
        }
       free (x);
       dispose_words (xlist);
       free (temp1);
       *indexp = sindex;
 
+      free (name);
+
       ret = alloc_word_desc ();
       ret->word = temp;
+      ret->flags = tflag;      /* XXX */
       return ret;
     }
 
@@ -6836,6 +7340,8 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
                *quoted_dollar_atp = 1;
              if (contains_dollar_at)
                *contains_dollar_at = 1;
+
+             tflag |= W_DOLLARAT;
            }       
 
          free (temp1);
@@ -6843,6 +7349,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 
          ret = alloc_word_desc ();
          ret->word = temp;
+         ret->flags = tflag;   /* XXX */
          return ret;
        }
 
@@ -6861,7 +7368,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|PF_ASSIGNRHS)), &ind);
 
   if (tdesc)
     {
@@ -6872,6 +7379,13 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
   else
     temp = (char  *)0;
 
+  if (temp == &expand_param_error || temp == &expand_param_fatal)
+    {
+      FREE (name);
+      FREE (value);
+      return (temp == &expand_param_error ? &expand_wdesc_error : &expand_wdesc_fatal);
+    }
+
 #if defined (ARRAY_VARS)
   if (valid_array_reference (name))
     chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at);
@@ -6879,13 +7393,16 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 
   var_is_set = temp != (char *)0;
   var_is_null = check_nullness && (var_is_set == 0 || *temp == 0);
+  /* XXX - this may not need to be restricted to special variables */
+  if (check_nullness)
+    var_is_null |= var_is_set && var_is_special && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && QUOTED_NULL (temp);
 
   /* Get the rest of the stuff inside the braces. */
   if (c && c != RBRACE)
     {
       /* 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 == '#' || c =='/' || c == '^' || c == ',' || c ==':') ? SX_POSIXEXP|SX_WORD : SX_WORD);
       if (string[sindex] == RBRACE)
        sindex++;
       else
@@ -6896,10 +7413,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);
@@ -6911,13 +7443,19 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 
       ret = alloc_word_desc ();
       ret->word = temp1;
-      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+      /* We test quoted_dollar_atp because we want variants with double-quoted
+        "$@" to take a different code path. In fact, we make sure at the end
+        of expand_word_internal that we're only looking at these flags if
+        quoted_dollar_at == 0. */
+      if (temp1 && 
+          (quoted_dollar_atp == 0 || *quoted_dollar_atp == 0) &&
+         QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
        ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
       return ret;
     }
   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);
@@ -6929,16 +7467,16 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 
       ret = alloc_word_desc ();
       ret->word = temp1;
-      ret = alloc_word_desc ();
-      ret->word = temp1;
-      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+      if (temp1 && 
+          (quoted_dollar_atp == 0 || *quoted_dollar_atp == 0) &&
+         QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
        ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
       return ret;
     }
 #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);
@@ -6950,7 +7488,9 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 
       ret = alloc_word_desc ();
       ret->word = temp1;
-      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+      if (temp1 &&
+          (quoted_dollar_atp == 0 || *quoted_dollar_atp == 0) &&
+         QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
        ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
       return ret;
     }
@@ -6962,6 +7502,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
     default:
     case '\0':
     bad_substitution:
+      last_command_exit_value = EXECUTION_FAILURE;
       report_error (_("%s: bad substitution"), string ? string : "??");
       FREE (value);
       FREE (temp);
@@ -6969,15 +7510,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 +7519,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 +7538,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 +7551,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,
@@ -7042,6 +7579,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
          temp = (char *)NULL;
          if (c == '=' && var_is_special)
            {
+             last_command_exit_value = EXECUTION_FAILURE;
              report_error (_("$%s: cannot assign in this way"), name);
              free (name);
              free (value);
@@ -7062,6 +7600,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 +7768,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
        {
@@ -7238,7 +7784,6 @@ param_expand (string, sindex, quoted, expanded_something,
             an assignment statement.  In that case, we don't separate the
             arguments at all.  Otherwise, if the $* is not quoted it is
             identical to $@ */
-#if 1
 #  if defined (HANDLE_MULTIBYTE)
          if (expand_no_split_dollar_star && ifs_firstc[0] == 0)
 #  else
@@ -7246,10 +7791,12 @@ param_expand (string, sindex, quoted, expanded_something,
 #  endif
            temp = string_list_dollar_star (list);
          else
-           temp = string_list_dollar_at (list, quoted);
-#else
-         temp = string_list_dollar_at (list, quoted);
-#endif
+           {
+             temp = string_list_dollar_at (list, quoted);
+             if (quoted == 0 && (ifs_is_set == 0 || ifs_is_null))
+               tflag |= W_SPLITSPACE;
+           }
+
          if (expand_no_split_dollar_star == 0 && contains_dollar_at)
            *contains_dollar_at = 1;
        }
@@ -7295,18 +7842,14 @@ param_expand (string, sindex, quoted, expanded_something,
       if (contains_dollar_at)
        *contains_dollar_at = 1;
 
-#if 0
-      if (pflags & PF_NOSPLIT2)
-       temp = string_list_internal (quoted ? quote_list (list) : list, " ");
-      else
-#endif
       /* We want to separate the positional parameters with the first
         character of $IFS in case $IFS is something other than a space.
         We also want to make sure that splitting is done no matter what --
         according to POSIX.2, this expands to a list of the positional
         parameters no matter what IFS is set to. */
-      temp = string_list_dollar_at (list, quoted);
+      temp = string_list_dollar_at (list, (pflags & PF_ASSIGNRHS) ? (quoted|Q_DOUBLE_QUOTES) : quoted);
 
+      tflag |= W_DOLLARAT;
       dispose_words (list);
       break;
 
@@ -7486,6 +8029,30 @@ comsub:
 
          goto return0;
        }
+      else if (var = find_variable_last_nameref (temp1))
+       {
+         temp = nameref_cell (var);
+#if defined (ARRAY_VARS)
+         if (temp && *temp && valid_array_reference (temp))
+           {
+             tdesc = parameter_brace_expand_word (temp, SPECIAL_VAR (temp, 0), quoted, pflags, (arrayind_t *)NULL);
+             if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal)
+               return (tdesc);
+             ret = tdesc;
+             goto return0;
+           }
+         else
+#endif
+         /* y=2 ; typeset -n x=y; echo $x is not the same as echo $2 in ksh */
+         if (temp && *temp && legal_identifier (temp) == 0)
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+             report_error (_("%s: invalid variable name for name reference"), temp);
+             return (&expand_wdesc_error);     /* XXX */
+           }
+         else
+           temp = (char *)NULL;
+       }
 
       temp = (char *)NULL;
 
@@ -7590,7 +8157,8 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
 
   /* State flags */
   int had_quoted_null;
-  int has_dollar_at;
+  int has_dollar_at, temp_has_dollar_at;
+  int split_on_spaces;
   int tflag;
   int pflags;                  /* flags passed to param_expand */
 
@@ -7606,6 +8174,7 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
   istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE);
   istring[istring_index = 0] = '\0';
   quoted_dollar_at = had_quoted_null = has_dollar_at = 0;
+  split_on_spaces = 0;
   quoted_state = UNQUOTED;
 
   string = word->word;
@@ -7626,7 +8195,7 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
     {
       c = string[sindex];
 
-      /* Case on toplevel character. */
+      /* Case on top-level character. */
       switch (c)
        {
        case '\0':
@@ -7795,13 +8364,17 @@ add_string:
          if (expanded_something)
            *expanded_something = 1;
 
-         has_dollar_at = 0;
+         temp_has_dollar_at = 0;
          pflags = (word->flags & W_NOCOMSUB) ? PF_NOCOMSUB : 0;
          if (word->flags & W_NOSPLIT2)
            pflags |= PF_NOSPLIT2;
+         if (word->flags & W_ASSIGNRHS)
+           pflags |= PF_ASSIGNRHS;
          tword = param_expand (string, &sindex, quoted, expanded_something,
-                              &has_dollar_at, &quoted_dollar_at,
+                              &temp_has_dollar_at, &quoted_dollar_at,
                               &had_quoted_null, pflags);
+         has_dollar_at += temp_has_dollar_at;
+         split_on_spaces += (tword->flags & W_SPLITSPACE);
 
          if (tword == &expand_wdesc_error || tword == &expand_wdesc_fatal)
            {
@@ -7816,9 +8389,17 @@ add_string:
          if (tword && (tword->flags & W_HASQUOTEDNULL))
            had_quoted_null = 1;
 
-         temp = tword->word;
+         temp = tword ? tword->word : (char *)NULL;
          dispose_word_desc (tword);
 
+         /* Kill quoted nulls; we will add them back at the end of
+            expand_word_internal if nothing else in the string */
+         if (had_quoted_null && temp && QUOTED_NULL (temp))
+           {
+             FREE (temp);
+             temp = (char *)NULL;
+           }
+
          goto add_string;
          break;
 
@@ -7836,6 +8417,7 @@ add_string:
                    sindex = t_index;
                    goto add_character;
                  }
+               last_command_exit_value = EXECUTION_FAILURE;
                report_error (_("bad substitution: no closing \"`\" in %s") , string+t_index);
                free (string);
                free (istring);
@@ -7878,7 +8460,24 @@ 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);
+           }
+         /* This is the fix for " $@\ " */
+         else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0) && isexp == 0 && isifs (c))
+           {
+             RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size,
+                                     DEFAULT_ARRAY_SIZE);
+             istring[istring_index++] = CTLESC;
+             istring[istring_index++] = '\\';
+             istring[istring_index] = '\0';
+
+             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);
            }
@@ -7905,11 +8504,7 @@ add_twochars:
          break;
 
        case '"':
-#if 0
-         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE))
-#else
          if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
-#endif
            goto add_character;
 
          t_index = ++sindex;
@@ -7928,9 +8523,10 @@ add_twochars:
 
              temp = (char *)NULL;
 
-             has_dollar_at = 0;
+             temp_has_dollar_at = 0;   /* XXX */
              /* Need to get W_HASQUOTEDNULL flag through this function. */
-             list = expand_word_internal (tword, Q_DOUBLE_QUOTES, 0, &has_dollar_at, (int *)NULL);
+             list = expand_word_internal (tword, Q_DOUBLE_QUOTES, 0, &temp_has_dollar_at, (int *)NULL);
+             has_dollar_at += temp_has_dollar_at;
 
              if (list == &expand_word_error || list == &expand_word_fatal)
                {
@@ -7966,7 +8562,7 @@ add_twochars:
                dequote_list (list);
 
              if (list && list->word && (list->word->flags & W_HASQUOTEDNULL))
-               had_quoted_null = 1;
+               had_quoted_null = 1;            /* XXX */
 
              if (has_dollar_at)
                {
@@ -7996,11 +8592,6 @@ add_twochars:
            {
              if (list->next)
                {
-#if 0
-                 if (quoted_dollar_at && word->flags & W_NOSPLIT2)
-                   temp = string_list_internal (quote_list (list), " ");
-                 else
-#endif
                  /* Testing quoted_dollar_at makes sure that "$@" is
                     split correctly when $IFS does not contain a space. */
                  temp = quoted_dollar_at
@@ -8036,8 +8627,11 @@ add_twochars:
            temp = (char *)NULL;
 
          /* 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)
+            partially quoted; we can throw them away.  The exception to
+            this is when we are going to be performing word splitting,
+            since we have to preserve a null argument if the next character
+            will cause word splitting. */
+         if (temp == 0 && quoted_state == PARTIALLY_QUOTED && (word->flags & (W_NOSPLIT|W_NOSPLIT2)))
            continue;
 
        add_quoted_string:
@@ -8060,11 +8654,7 @@ add_twochars:
          /* break; */
 
        case '\'':
-#if 0
-         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE))
-#else
          if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
-#endif
            goto add_character;
 
          t_index = ++sindex;
@@ -8213,11 +8803,13 @@ finished_with_string:
        tword->flags |= W_COMPASSIGN;   /* XXX */
       if (word->flags & W_NOGLOB)
        tword->flags |= W_NOGLOB;       /* XXX */
+      if (word->flags & W_NOBRACE)
+       tword->flags |= W_NOBRACE;      /* XXX */
       if (word->flags & W_NOEXPAND)
        tword->flags |= W_NOEXPAND;     /* XXX */
       if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
        tword->flags |= W_QUOTED;
-      if (had_quoted_null)
+      if (had_quoted_null && QUOTED_NULL (istring))
        tword->flags |= W_HASQUOTEDNULL;
       list = make_word_list (tword, (WORD_LIST *)NULL);
     }
@@ -8232,8 +8824,13 @@ finished_with_string:
         positional parameters with a space, so we split on space (we have
         set ifs_chars to " \t\n" above if ifs is unset).  If IFS is set,
         string_list_dollar_at has separated the positional parameters
-        with the first character of $IFS, so we split on $IFS. */
-      if (has_dollar_at && ifs_chars)
+        with the first character of $IFS, so we split on $IFS.  If
+        SPLIT_ON_SPACES is set, we expanded $* (unquoted) with IFS either
+        unset or null, and we want to make sure that we split on spaces
+        regardless of what else has happened to IFS since the expansion. */
+      if (split_on_spaces)
+       list = list_string (istring, " ", 1);   /* XXX quoted == 1? */
+      else if (has_dollar_at && ifs_chars)
        list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1);
       else
        {
@@ -8246,9 +8843,11 @@ finished_with_string:
            tword->flags |= W_COMPASSIGN;
          if (word->flags & W_NOGLOB)
            tword->flags |= W_NOGLOB;
+         if (word->flags & W_NOBRACE)
+           tword->flags |= W_NOBRACE;
          if (word->flags & W_NOEXPAND)
            tword->flags |= W_NOEXPAND;
-         if (had_quoted_null)
+         if (had_quoted_null && QUOTED_NULL (istring))
            tword->flags |= W_HASQUOTEDNULL;    /* XXX */
          list = make_word_list (tword, (WORD_LIST *)NULL);
        }
@@ -8394,6 +8993,9 @@ setifs (v)
   ifs_var = v;
   ifs_value = (v && value_cell (v)) ? value_cell (v) : " \t\n";
 
+  ifs_is_set = ifs_var != 0;
+  ifs_is_null = ifs_is_set && (*ifs_value == 0);
+
   /* Should really merge ifs_cmap with sh_syntaxtab.  XXX - doesn't yet
      handle multibyte chars in IFS */
   memset (ifs_cmap, '\0', sizeof (ifs_cmap));
@@ -8706,7 +9308,6 @@ glob_expand_word_list (tlist, eflags)
          for (glob_index = 0; glob_array[glob_index]; glob_index++)
            {
              tword = make_bare_word (glob_array[glob_index]);
-             tword->flags |= W_GLOBEXP;        /* XXX */
              glob_list = make_word_list (tword, glob_list);
            }
 
@@ -8717,6 +9318,7 @@ glob_expand_word_list (tlist, eflags)
            }
          else if (fail_glob_expansion != 0)
            {
+             last_command_exit_value = EXECUTION_FAILURE;
              report_error (_("no match: %s"), tlist->word->word);
              exp_jump_to_top_level (DISCARD);
            }
@@ -8771,13 +9373,20 @@ brace_expand_word_list (tlist, eflags)
     {
       next = tlist->next;
 
+      if (tlist->word->flags & W_NOBRACE)
+        {
+/*itrace("brace_expand_word_list: %s: W_NOBRACE", tlist->word->word);*/
+         PREPEND_LIST (tlist, output_list);
+         continue;
+        }
+
       if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG))
         {
 /*itrace("brace_expand_word_list: %s: W_COMPASSIGN|W_ASSIGNARG", tlist->word->word);*/
          PREPEND_LIST (tlist, output_list);
          continue;
         }
-          
+
       /* Only do brace expansion if the word has a brace character.  If
         not, just add the word list element to BRACES and continue.  In
         the common case, at least when running shell scripts, this will
@@ -8791,14 +9400,18 @@ brace_expand_word_list (tlist, eflags)
 
          for (eindex = 0; temp_string = expansions[eindex]; eindex++)
            {
-             w = make_word (temp_string);
+             w = alloc_word_desc ();
+             w->word = temp_string;
+
              /* If brace expansion didn't change the word, preserve
                 the flags.  We may want to preserve the flags
                 unconditionally someday -- XXX */
              if (STREQ (temp_string, tlist->word->word))
                w->flags = tlist->word->flags;
+             else
+               w = make_word_flags (w, temp_string);
+
              output_list = make_word_list (w, output_list);
-             free (expansions[eindex]);
            }
          free (expansions);
 
@@ -8871,11 +9484,40 @@ shell_expand_word_list (tlist, eflags)
       if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG))
        {
          int t;
+         char opts[8], opti;
+
+         opti = 0;
+         if (tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL|W_ASSIGNARRAY))
+           opts[opti++] = '-';
+
+         if ((tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL)) == (W_ASSIGNASSOC|W_ASSNGLOBAL))
+           {
+             opts[opti++] = 'g';
+             opts[opti++] = 'A';
+           }
+         else if (tlist->word->flags & W_ASSIGNASSOC)
+           opts[opti++] = 'A';
+         else if ((tlist->word->flags & (W_ASSIGNARRAY|W_ASSNGLOBAL)) == (W_ASSIGNARRAY|W_ASSNGLOBAL))
+           {
+             opts[opti++] = 'g';
+             opts[opti++] = 'a';
+           }
+         else if (tlist->word->flags & W_ASSIGNARRAY)
+           opts[opti++] = 'a';
+         else if (tlist->word->flags & W_ASSNGLOBAL)
+           opts[opti++] = 'g';
+
+#if 0
+         /* If we have special handling note the integer attribute */
+         if (opti > 0 && (tlist->word->flags & W_ASSIGNINT))
+           opts[opti++] = 'i';
+#endif
 
-         if (tlist->word->flags & W_ASSIGNASSOC)
-           make_internal_declare (tlist->word->word, "-A");
+         opts[opti] = '\0';
+         if (opti > 0)
+           make_internal_declare (tlist->word->word, opts);
 
-         t = do_word_assignment (tlist->word);
+         t = do_word_assignment (tlist->word, 0);
          if (t == 0)
            {
              last_command_exit_value = EXECUTION_FAILURE;
@@ -8885,7 +9527,7 @@ shell_expand_word_list (tlist, eflags)
          /* Now transform the word as ksh93 appears to do and go on */
          t = assignment (tlist->word->word, 0);
          tlist->word->word[t] = '\0';
-         tlist->word->flags &= ~(W_ASSIGNMENT|W_NOSPLIT|W_COMPASSIGN|W_ASSIGNARG|W_ASSIGNASSOC);
+         tlist->word->flags &= ~(W_ASSIGNMENT|W_NOSPLIT|W_COMPASSIGN|W_ASSIGNARG|W_ASSIGNASSOC|W_ASSIGNARRAY);
        }
 #endif
 
@@ -8950,7 +9592,9 @@ shell_expand_word_list (tlist, eflags)
    process substitution, word splitting, and pathname expansion, according
    to the bits set in EFLAGS.  Words with the W_QUOTED or W_NOSPLIT bits
    set, or for which no expansion is done, do not undergo word splitting.
-   Words with the W_NOGLOB bit set do not undergo pathname expansion. */
+   Words with the W_NOGLOB bit set do not undergo pathname expansion; words
+   with W_NOBRACE set do not undergo brace expansion (see
+   brace_expand_word_list above). */
 static WORD_LIST *
 expand_word_list_internal (list, eflags)
      WORD_LIST *list;
@@ -8959,6 +9603,7 @@ expand_word_list_internal (list, eflags)
   WORD_LIST *new_list, *temp_list;
   int tint;
 
+  tempenv_assign_error = 0;
   if (list == 0)
     return ((WORD_LIST *)NULL);
 
@@ -8975,7 +9620,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 +9669,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 +9677,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 +9695,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);
@@ -9058,13 +9709,5 @@ expand_word_list_internal (list, eflags)
       subst_assign_varlist = (WORD_LIST *)NULL;
     }
 
-#if 0
-  tint = list_length (new_list) + 1;
-  RESIZE_MALLOCED_BUFFER (glob_argv_flags, 0, tint, glob_argv_flags_size, 16);
-  for (tint = 0, temp_list = new_list; temp_list; temp_list = temp_list->next)
-    glob_argv_flags[tint++] = (temp_list->word->flags & W_GLOBEXP) ? '1' : '0';
-  glob_argv_flags[tint] = '\0';
-#endif
-
   return (new_list);
 }