]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - subst.c
Bash-5.2 patch 26: fix typo when specifying readline's custom color prefix
[thirdparty/bash.git] / subst.c
diff --git a/subst.c b/subst.c
index 8884b4879e55d5a0345b28af98b07fcd6d60e75a..2012e1fd9e3412740a3eae8c6a17e5f3d5c7b959 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-2018 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2022 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -45,6 +45,7 @@
 
 #include "shell.h"
 #include "parser.h"
+#include "redir.h"
 #include "flags.h"
 #include "jobs.h"
 #include "execute_cmd.h"
@@ -143,7 +144,7 @@ extern int errno;
 /* An expansion function that takes a string and a quoted flag and returns
    a WORD_LIST *.  Used as the type of the third argument to
    expand_string_if_necessary(). */
-typedef WORD_LIST *EXPFUNC __P((char *, int));
+typedef WORD_LIST *EXPFUNC PARAMS((char *, int));
 
 /* Process ID of the last command executed within command substitution. */
 pid_t last_command_subst_pid = NO_PID;
@@ -186,16 +187,22 @@ int allow_null_glob_expansion;
 /* Non-zero means to throw an error when globbing fails to match anything. */
 int fail_glob_expansion;
 
+/* If non-zero, perform `&' substitution on the replacement string in the
+   pattern substitution word expansion. */
+int patsub_replacement = 1;
+
 /* Extern functions and variables from different files. */
 extern struct fd_bitmap *current_fds_to_close;
 extern int wordexp_only;
+extern int singlequote_translations;
+extern int extended_quote;
 
 #if defined (JOB_CONTROL) && defined (PROCESS_SUBSTITUTION)
 extern PROCESS *last_procsub_child;
 #endif
 
 #if !defined (HAVE_WCSDUP) && defined (HANDLE_MULTIBYTE)
-extern wchar_t *wcsdup __P((const wchar_t *));
+extern wchar_t *wcsdup PARAMS((const wchar_t *));
 #endif
 
 #if 0
@@ -226,137 +233,145 @@ static int expand_no_split_dollar_star = 0;
    without any leading variable assignments. */
 static WORD_LIST *garglist = (WORD_LIST *)NULL;
 
-static char *quoted_substring __P((char *, int, int));
-static int quoted_strlen __P((char *));
-static char *quoted_strchr __P((char *, int, int));
+static char *quoted_substring PARAMS((char *, int, int));
+static int quoted_strlen PARAMS((char *));
+static char *quoted_strchr PARAMS((char *, int, int));
 
-static char *expand_string_if_necessary __P((char *, int, EXPFUNC *));
-static inline char *expand_string_to_string_internal __P((char *, int, EXPFUNC *));
-static WORD_LIST *call_expand_word_internal __P((WORD_DESC *, int, int, int *, int *));
-static WORD_LIST *expand_string_internal __P((char *, int));
-static WORD_LIST *expand_string_leave_quoted __P((char *, int));
-static WORD_LIST *expand_string_for_rhs __P((char *, int, int, int, int *, int *));
-static WORD_LIST *expand_string_for_pat __P((char *, int, int *, int *));
+static char *expand_string_if_necessary PARAMS((char *, int, EXPFUNC *));
+static inline char *expand_string_to_string_internal PARAMS((char *, int, EXPFUNC *));
+static WORD_LIST *call_expand_word_internal PARAMS((WORD_DESC *, int, int, int *, int *));
+static WORD_LIST *expand_string_internal PARAMS((char *, int));
+static WORD_LIST *expand_string_leave_quoted PARAMS((char *, int));
+static WORD_LIST *expand_string_for_rhs PARAMS((char *, int, int, int, int *, int *));
+static WORD_LIST *expand_string_for_pat PARAMS((char *, int, int *, int *));
 
-static char *quote_escapes_internal __P((const char *, int));
+static char *quote_escapes_internal PARAMS((const char *, int));
 
-static WORD_LIST *list_quote_escapes __P((WORD_LIST *));
-static WORD_LIST *list_dequote_escapes __P((WORD_LIST *));
+static WORD_LIST *list_quote_escapes PARAMS((WORD_LIST *));
+static WORD_LIST *list_dequote_escapes PARAMS((WORD_LIST *));
 
-static char *make_quoted_char __P((int));
-static WORD_LIST *quote_list __P((WORD_LIST *));
+static char *make_quoted_char PARAMS((int));
+static WORD_LIST *quote_list PARAMS((WORD_LIST *));
 
-static int unquoted_substring __P((char *, char *));
-static int unquoted_member __P((int, char *));
+static int unquoted_substring PARAMS((char *, char *));
+static int unquoted_member PARAMS((int, char *));
 
 #if defined (ARRAY_VARS)
-static SHELL_VAR *do_compound_assignment __P((char *, char *, int));
+static SHELL_VAR *do_compound_assignment PARAMS((char *, char *, int));
 #endif
-static int do_assignment_internal __P((const WORD_DESC *, int));
+static int do_assignment_internal PARAMS((const WORD_DESC *, int));
 
-static char *string_extract_verbatim __P((char *, size_t, int *, char *, int));
-static char *string_extract __P((char *, int *, char *, int));
-static char *string_extract_double_quoted __P((char *, int *, int));
-static inline char *string_extract_single_quoted __P((char *, int *));
-static inline int skip_single_quoted __P((const char *, size_t, int, int));
-static int skip_double_quoted __P((char *, size_t, int, int));
-static char *extract_delimited_string __P((char *, int *, char *, char *, char *, int));
-static char *extract_dollar_brace_string __P((char *, int *, int, int));
-static int skip_matched_pair __P((const char *, int, int, int, int));
+static char *string_extract_verbatim PARAMS((char *, size_t, int *, char *, int));
+static char *string_extract PARAMS((char *, int *, char *, int));
+static char *string_extract_double_quoted PARAMS((char *, int *, int));
+static inline char *string_extract_single_quoted PARAMS((char *, int *, int));
+static inline int skip_single_quoted PARAMS((const char *, size_t, int, int));
+static int skip_double_quoted PARAMS((char *, size_t, int, int));
+static char *extract_delimited_string PARAMS((char *, int *, char *, char *, char *, int));
+static char *extract_heredoc_dolbrace_string PARAMS((char *, int *, int, int));
+static char *extract_dollar_brace_string PARAMS((char *, int *, int, int));
+static int skip_matched_pair PARAMS((const char *, int, int, int, int));
 
-static char *pos_params __P((char *, int, int, int));
+static char *pos_params PARAMS((char *, int, int, int, int));
 
-static unsigned char *mb_getcharlens __P((char *, int));
+static unsigned char *mb_getcharlens PARAMS((char *, int));
 
-static char *remove_upattern __P((char *, char *, int));
+static char *remove_upattern PARAMS((char *, char *, int));
 #if defined (HANDLE_MULTIBYTE) 
-static wchar_t *remove_wpattern __P((wchar_t *, size_t, wchar_t *, int));
+static wchar_t *remove_wpattern PARAMS((wchar_t *, size_t, wchar_t *, int));
 #endif
-static char *remove_pattern __P((char *, char *, int));
+static char *remove_pattern PARAMS((char *, char *, int));
 
-static int match_upattern __P((char *, char *, int, char **, char **));
+static int match_upattern PARAMS((char *, char *, int, char **, char **));
 #if defined (HANDLE_MULTIBYTE)
-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 **));
-static int getpatspec __P((int, char *));
-static char *getpattern __P((char *, int, int));
-static char *variable_remove_pattern __P((char *, char *, int, int));
-static char *list_remove_pattern __P((WORD_LIST *, char *, int, int, int));
-static char *parameter_list_remove_pattern __P((int, char *, int, int));
+static int match_wpattern PARAMS((wchar_t *, char **, size_t, wchar_t *, int, char **, char **));
+#endif
+static int match_pattern PARAMS((char *, char *, int, char **, char **));
+static int getpatspec PARAMS((int, char *));
+static char *getpattern PARAMS((char *, int, int));
+static char *variable_remove_pattern PARAMS((char *, char *, int, int));
+static char *list_remove_pattern PARAMS((WORD_LIST *, char *, int, int, int));
+static char *parameter_list_remove_pattern PARAMS((int, char *, int, int));
 #ifdef ARRAY_VARS
-static char *array_remove_pattern __P((SHELL_VAR *, char *, int, char *, int));
+static char *array_remove_pattern PARAMS((SHELL_VAR *, char *, int, int, int));
 #endif
-static char *parameter_brace_remove_pattern __P((char *, char *, int, char *, int, int, int));
+static char *parameter_brace_remove_pattern PARAMS((char *, char *, array_eltstate_t *, char *, int, int, int));
 
-static char *string_var_assignment __P((SHELL_VAR *, char *));
+static char *string_var_assignment PARAMS((SHELL_VAR *, char *));
 #if defined (ARRAY_VARS)
-static char *array_var_assignment __P((SHELL_VAR *, int, int));
+static char *array_var_assignment PARAMS((SHELL_VAR *, int, int, int));
 #endif
-static char *pos_params_assignment __P((WORD_LIST *, int, int));
-static char *string_transform __P((int, SHELL_VAR *, char *));
-static char *list_transform __P((int, SHELL_VAR *, WORD_LIST *, int, int));
-static char *parameter_list_transform __P((int, int, int));
+static char *pos_params_assignment PARAMS((WORD_LIST *, int, int));
+static char *string_transform PARAMS((int, SHELL_VAR *, char *));
+static char *list_transform PARAMS((int, SHELL_VAR *, WORD_LIST *, int, int));
+static char *parameter_list_transform PARAMS((int, int, int));
 #if defined ARRAY_VARS
-static char *array_transform __P((int, SHELL_VAR *, char *, int));
+static char *array_transform PARAMS((int, SHELL_VAR *, int, int));
 #endif
-static char *parameter_brace_transform __P((char *, char *, int, char *, int, int, int, int));
+static char *parameter_brace_transform PARAMS((char *, char *, array_eltstate_t *, char *, int, int, int, int));
+static int valid_parameter_transform PARAMS((char *));
 
-static char *process_substitute __P((char *, int));
+static char *process_substitute PARAMS((char *, int));
 
-static char *read_comsub __P((int, int, int, int *));
+static char *optimize_cat_file PARAMS((REDIRECT *, int, int, int *));
+static char *read_comsub PARAMS((int, int, int, int *));
 
 #ifdef ARRAY_VARS
-static arrayind_t array_length_reference __P((char *));
+static arrayind_t array_length_reference PARAMS((char *));
 #endif
 
-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 int valid_brace_expansion_word PARAMS((char *, int));
+static int chk_atstar PARAMS((char *, int, int, int *, int *));
+static int chk_arithsub PARAMS((const char *, 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 *, int *));
-static void parameter_brace_expand_error __P((char *, char *, int));
+static WORD_DESC *parameter_brace_expand_word PARAMS((char *, int, int, int, array_eltstate_t *));
+static char *parameter_brace_find_indir PARAMS((char *, int, int, int));
+static WORD_DESC *parameter_brace_expand_indir PARAMS((char *, int, int, int, int *, int *));
+static WORD_DESC *parameter_brace_expand_rhs PARAMS((char *, char *, int, int, int, int *, int *));
+static void parameter_brace_expand_error PARAMS((char *, char *, int));
 
-static int valid_length_expression __P((char *));
-static intmax_t parameter_brace_expand_length __P((char *));
+static int valid_length_expression PARAMS((char *));
+static intmax_t parameter_brace_expand_length PARAMS((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 *, arrayind_t, int, int, SHELL_VAR **, char **));
-static char *mb_substring __P((char *, int, int));
-static char *parameter_brace_substring __P((char *, char *, int, char *, int, int, int));
+static char *skiparith PARAMS((char *, int));
+static int verify_substring_values PARAMS((SHELL_VAR *, char *, char *, int, intmax_t *, intmax_t *));
+static int get_var_and_type PARAMS((char *, char *, array_eltstate_t *, int, int, SHELL_VAR **, char **));
+static char *mb_substring PARAMS((char *, int, int));
+static char *parameter_brace_substring PARAMS((char *, char *, array_eltstate_t *, char *, int, int, int));
 
-static int shouldexp_replacement __P((char *));
+static int shouldexp_replacement PARAMS((char *));
 
-static char *pos_params_pat_subst __P((char *, char *, char *, int));
+static char *pos_params_pat_subst PARAMS((char *, char *, char *, int));
 
-static char *parameter_brace_patsub __P((char *, char *, int, char *, int, int, int));
+static char *expand_string_for_patsub PARAMS((char *, int));
+static char *parameter_brace_patsub PARAMS((char *, char *, array_eltstate_t *, char *, int, int, int));
 
-static char *pos_params_casemod __P((char *, char *, int, int));
-static char *parameter_brace_casemod __P((char *, char *, int, int, char *, int, int, int));
+static char *pos_params_casemod PARAMS((char *, char *, int, int));
+static char *parameter_brace_casemod PARAMS((char *, char *, array_eltstate_t *, int, char *, int, 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));
+static WORD_DESC *parameter_brace_expand PARAMS((char *, int *, int, int, int *, int *));
+static WORD_DESC *param_expand PARAMS((char *, int *, int, int *, int *, int *, int *, int));
 
-static WORD_LIST *expand_word_internal __P((WORD_DESC *, int, int, int *, int *));
+static WORD_LIST *expand_word_internal PARAMS((WORD_DESC *, int, int, int *, int *));
 
-static WORD_LIST *word_list_split __P((WORD_LIST *));
+static WORD_LIST *word_list_split PARAMS((WORD_LIST *));
 
-static void exp_jump_to_top_level __P((int));
+static void exp_jump_to_top_level PARAMS((int));
 
-static WORD_LIST *separate_out_assignments __P((WORD_LIST *));
-static WORD_LIST *glob_expand_word_list __P((WORD_LIST *, int));
+static WORD_LIST *separate_out_assignments PARAMS((WORD_LIST *));
+static WORD_LIST *glob_expand_word_list PARAMS((WORD_LIST *, int));
 #ifdef BRACE_EXPANSION
-static WORD_LIST *brace_expand_word_list __P((WORD_LIST *, int));
+static WORD_LIST *brace_expand_word_list PARAMS((WORD_LIST *, int));
 #endif
 #if defined (ARRAY_VARS)
-static int make_internal_declare __P((char *, char *, char *));
+static int make_internal_declare PARAMS((char *, char *, char *));
+static void expand_compound_assignment_word PARAMS((WORD_LIST *, int));
+static WORD_LIST *expand_declaration_argument PARAMS((WORD_LIST *, WORD_LIST *));
 #endif
-static WORD_LIST *shell_expand_word_list __P((WORD_LIST *, int));
-static WORD_LIST *expand_word_list_internal __P((WORD_LIST *, int));
+static WORD_LIST *shell_expand_word_list PARAMS((WORD_LIST *, int));
+static WORD_LIST *expand_word_list_internal PARAMS((WORD_LIST *, int));
+
+static int do_assignment_statements PARAMS((WORD_LIST *, char *, int));
 
 /* **************************************************************** */
 /*                                                                 */
@@ -428,15 +443,10 @@ dump_word_flags (flags)
       f &= ~W_COMPASSIGN;
       fprintf (stderr, "W_COMPASSIGN%s", f ? "|" : "");
     }
-  if (f & W_NOEXPAND)
-    {
-      f &= ~W_NOEXPAND;
-      fprintf (stderr, "W_NOEXPAND%s", f ? "|" : "");
-    }
-  if (f & W_ITILDE)
+  if (f & W_EXPANDRHS)
     {
-      f &= ~W_ITILDE;
-      fprintf (stderr, "W_ITILDE%s", f ? "|" : "");
+      f &= ~W_EXPANDRHS;
+      fprintf (stderr, "W_EXPANDRHS%s", f ? "|" : "");
     }
   if (f & W_NOTILDE)
     {
@@ -458,10 +468,10 @@ dump_word_flags (flags)
       f &= ~W_NOCOMSUB;
       fprintf (stderr, "W_NOCOMSUB%s", f ? "|" : "");
     }
-  if (f & W_DOLLARSTAR)
+  if (f & W_ARRAYREF)
     {
-      f &= ~W_DOLLARSTAR;
-      fprintf (stderr, "W_DOLLARSTAR%s", f ? "|" : "");
+      f &= ~W_ARRAYREF;
+      fprintf (stderr, "W_ARRAYREF%s", f ? "|" : "");
     }
   if (f & W_DOLLARAT)
     {
@@ -523,7 +533,12 @@ dump_word_flags (flags)
       f &= ~W_CHKLOCAL;
       fprintf (stderr, "W_CHKLOCAL%s", f ? "|" : "");
     }
-  
+  if (f & W_FORCELOCAL)
+    {
+      f &= ~W_FORCELOCAL;
+      fprintf (stderr, "W_FORCELOCAL%s", f ? "|" : "");
+    }
+
   fprintf (stderr, "\n");
   fflush (stderr);
 }
@@ -725,16 +740,15 @@ unquoted_substring (substr, string)
 INLINE char *
 sub_append_string (source, target, indx, size)
      char *source, *target;
-     int *indx;
+     size_t *indx;
      size_t *size;
 {
   if (source)
     {
-      int n;
-      size_t srclen;
+      size_t n, srclen;
 
       srclen = STRLEN (source);
-      if (srclen >= (int)(*size - *indx))
+      if (srclen >= (*size - *indx))
        {
          n = srclen + *indx;
          n = (n + DEFAULT_ARRAY_SIZE) - (n % DEFAULT_ARRAY_SIZE);
@@ -758,7 +772,7 @@ char *
 sub_append_number (number, target, indx, size)
      intmax_t number;
      char *target;
-     int *indx;
+     size_t *indx;
      size_t *size;
 {
   char *temp;
@@ -964,7 +978,9 @@ add_one_character:
            temp[j] = ret[t];
          temp[j] = string[si];
 
-         if (string[si])
+         if (si < i + 2)       /* we went back? */
+           i += 2;
+         else if (string[si])
            {
              j++;
              i = si + 1;
@@ -1076,22 +1092,38 @@ skip_double_quoted (string, slen, sind, flags)
 /* Extract the contents of STRING as if it is enclosed in single quotes.
    SINDEX, when passed in, is the offset of the character immediately
    following the opening single quote; on exit, SINDEX is left pointing after
-   the closing single quote. */
+   the closing single quote. ALLOWESC allows the single quote to be quoted by
+   a backslash; it's not used yet. */
 static inline char *
-string_extract_single_quoted (string, sindex)
+string_extract_single_quoted (string, sindex, allowesc)
      char *string;
      int *sindex;
+     int allowesc;
 {
   register int i;
   size_t slen;
   char *t;
+  int pass_next;
   DECLARE_MBSTATE;
 
   /* Don't need slen for ADVANCE_CHAR unless multibyte chars possible. */
   slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 0;
   i = *sindex;
-  while (string[i] && string[i] != '\'')
-    ADVANCE_CHAR (string, slen, i);
+  pass_next = 0;
+  while (string[i])
+    {
+      if (pass_next)
+       {
+         pass_next = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      if (allowesc && string[i] == '\\')
+       pass_next++;
+      else if (string[i] == '\'')
+        break;
+      ADVANCE_CHAR (string, slen, i);
+    }
 
   t = substring (string, *sindex, i);
 
@@ -1148,11 +1180,23 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
 
   if ((flags & SX_NOCTLESC) && charlist[0] == '\'' && charlist[1] == '\0')
     {
-      temp = string_extract_single_quoted (string, sindex);
+      temp = string_extract_single_quoted (string, sindex, 0);
       --*sindex;       /* leave *sindex at separator character */
       return temp;
     }
 
+  /* This can never be called with charlist == NULL. If *charlist == NULL,
+     we can skip the loop and just return a copy of the string, updating
+     *sindex */
+  if (*charlist == 0)
+    {
+      temp = string + *sindex;
+      c = (*sindex == 0) ? slen : STRLEN (temp);
+      temp = savestring (temp);
+      *sindex += c;
+      return temp;
+    }
+
   i = *sindex;
 #if defined (HANDLE_MULTIBYTE)
   wcharlist = 0;
@@ -1475,6 +1519,288 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
   return (result);
 }
 
+/* A simplified version of extract_dollar_brace_string that exists to handle
+   $'...' and $"..." quoting in here-documents, since the here-document read
+   path doesn't. It's separate because we don't want to mess with the fast
+   common path. We already know we're going to allocate and return a new
+   string and quoted == Q_HERE_DOCUMENT. We might be able to cut it down
+   some more, but extracting strings and adding them as we go adds complexity.
+   This needs to match the logic in parse.y:parse_matched_pair so we get
+   consistent behavior between here-documents and double-quoted strings. */
+static char *
+extract_heredoc_dolbrace_string (string, sindex, quoted, flags)
+     char *string;
+     int *sindex, quoted, flags;
+{
+  register int i, c;
+  size_t slen, tlen, result_index, result_size;
+  int pass_character, nesting_level, si, dolbrace_state;
+  char *result, *t, *send;
+  DECLARE_MBSTATE;
+
+  pass_character = 0;
+  nesting_level = 1;
+  slen = strlen (string + *sindex) + *sindex;
+  send = string + slen;
+
+  result_size = slen;
+  result_index = 0;
+  result = xmalloc (result_size + 1);
+
+  /* This function isn't called if this condition is not true initially. */
+  dolbrace_state = DOLBRACE_QUOTE;
+
+  i = *sindex;
+  while (c = string[i])
+    {
+      if (pass_character)
+       {
+         pass_character = 0;
+         RESIZE_MALLOCED_BUFFER (result, result_index, locale_mb_cur_max + 1, result_size, 64);
+         COPY_CHAR_I (result, result_index, string, send, i);
+         continue;
+       }
+
+      /* CTLESCs and backslashes quote the next character. */
+      if (c == CTLESC || c == '\\')
+       {
+         pass_character++;
+         RESIZE_MALLOCED_BUFFER (result, result_index, 2, result_size, 64);
+         result[result_index++] = c;
+         i++;
+         continue;
+       }
+
+      /* The entire reason we have this separate function right here. */
+      if (c == '$' && string[i+1] == '\'')
+       {
+         char *ttrans;
+         int ttranslen;
+
+         if ((posixly_correct || extended_quote == 0) && dolbrace_state != DOLBRACE_QUOTE && dolbrace_state != DOLBRACE_QUOTE2)
+           {
+             RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, 64);
+             result[result_index++] = '$';
+             result[result_index++] = '\'';
+             i += 2;
+             continue;
+           }
+
+         si = i + 2;
+         t = string_extract_single_quoted (string, &si, 1);    /* XXX */
+         CHECK_STRING_OVERRUN (i, si, slen, c);
+
+         tlen = si - i - 2;    /* -2 since si is one after the close quote */
+         ttrans = ansiexpand (t, 0, tlen, &ttranslen);
+         free (t);
+
+         /* needed to correctly quote any embedded single quotes. */
+         if (dolbrace_state == DOLBRACE_QUOTE || dolbrace_state == DOLBRACE_QUOTE2)
+           {
+             t = sh_single_quote (ttrans);
+             tlen = strlen (t);
+             free (ttrans);
+           }
+         else if (extended_quote) /* dolbrace_state == DOLBRACE_PARAM */
+           {
+             /* This matches what parse.y:parse_matched_pair() does */
+             t = ttrans;
+             tlen = strlen (t);
+           }
+
+         RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 1, result_size, 64);
+         strncpy (result + result_index, t, tlen);
+         result_index += tlen;
+         free (t);
+         i = si;
+         continue;
+       }
+
+#if defined (TRANSLATABLE_STRINGS)
+      if (c == '$' && string[i+1] == '"')
+       {
+         char *ttrans;
+         int ttranslen;
+
+         si = i + 2;
+         t = string_extract_double_quoted (string, &si, flags);        /* XXX */
+         CHECK_STRING_OVERRUN (i, si, slen, c);
+
+         tlen = si - i - 2;    /* -2 since si is one after the close quote */
+         ttrans = locale_expand (t, 0, tlen, line_number, &ttranslen);
+         free (t);
+
+         t = singlequote_translations ? sh_single_quote (ttrans) : sh_mkdoublequoted (ttrans, ttranslen, 0);
+         tlen = strlen (t);
+         free (ttrans);
+
+         RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 1, result_size, 64);
+         strncpy (result + result_index, t, tlen);
+         result_index += tlen;
+         free (t);
+         i = si;
+         continue;
+       }
+#endif /* TRANSLATABLE_STRINGS */
+
+      if (c == '$' && string[i+1] == LBRACE)
+       {
+         nesting_level++;
+         RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, 64);
+         result[result_index++] = c;
+         result[result_index++] = string[i+1];
+         i += 2;
+         if (dolbrace_state == DOLBRACE_QUOTE || dolbrace_state == DOLBRACE_QUOTE2 || dolbrace_state == DOLBRACE_WORD)
+           dolbrace_state = DOLBRACE_PARAM;
+         continue;
+       }
+
+      if (c == RBRACE)
+       {
+         nesting_level--;
+         if (nesting_level == 0)
+           break;
+         RESIZE_MALLOCED_BUFFER (result, result_index, 2, result_size, 64);
+         result[result_index++] = c;
+         i++;
+         continue;
+       }
+
+      /* Pass the contents of old-style command substitutions through
+        verbatim. */
+      if (c == '`')
+       {
+         si = i + 1;
+         t = string_extract (string, &si, "`", flags); /* already know (flags & SX_NOALLOC) == 0) */
+         CHECK_STRING_OVERRUN (i, si, slen, c);
+
+         tlen = si - i - 1;
+         RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 3, result_size, 64);
+         result[result_index++] = c;
+         strncpy (result + result_index, t, tlen);
+         result_index += tlen;
+         result[result_index++] = string[si];
+         free (t);
+         i = si + 1;
+         continue;
+       }
+
+      /* Pass the contents of new-style command substitutions and
+        arithmetic substitutions through verbatim. */
+      if (string[i] == '$' && string[i+1] == LPAREN)
+       {
+         si = i + 2;
+         t = extract_command_subst (string, &si, flags);
+         CHECK_STRING_OVERRUN (i, si, slen, c);
+
+         tlen = si - i - 2;
+         RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 4, result_size, 64);
+         result[result_index++] = c;
+         result[result_index++] = LPAREN;
+         strncpy (result + result_index, t, tlen);
+         result_index += tlen;
+         result[result_index++] = string[si];
+         free (t);
+         i = si + 1;
+         continue;
+       }
+
+#if defined (PROCESS_SUBSTITUTION)
+      /* Technically this should only work at the start of a word */
+      if ((string[i] == '<' || string[i] == '>') && string[i+1] == LPAREN)
+       {
+         si = i + 2;
+         t = extract_process_subst (string, (string[i] == '<' ? "<(" : ">)"), &si, flags);
+         CHECK_STRING_OVERRUN (i, si, slen, c);
+
+         tlen = si - i - 2;
+         RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 4, result_size, 64);
+         result[result_index++] = c;
+         result[result_index++] = LPAREN;
+         strncpy (result + result_index, t, tlen);
+         result_index += tlen;
+         result[result_index++] = string[si];
+         free (t);
+         i = si + 1;
+         continue;
+       }
+#endif
+
+      if (c == '\'' && posixly_correct && shell_compatibility_level > 42 && dolbrace_state != DOLBRACE_QUOTE)
+       {
+         COPY_CHAR_I (result, result_index, string, send, i);
+         continue;
+       }
+
+      /* Pass the contents of single and double-quoted strings through verbatim. */
+      if (c == '"' || c == '\'')
+       {
+         si = i + 1;
+         if (c == '"')
+           t = string_extract_double_quoted (string, &si, flags);
+         else
+           t = string_extract_single_quoted (string, &si, 0);
+         CHECK_STRING_OVERRUN (i, si, slen, c);
+
+         tlen = si - i - 2;    /* -2 since si is one after the close quote */
+         RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 3, result_size, 64);
+         result[result_index++] = c;
+         strncpy (result + result_index, t, tlen);
+         result_index += tlen;
+         result[result_index++] = string[si - 1];
+         free (t);
+         i = si;
+         continue;
+       }
+
+      /* copy this character, which was not special. */
+      COPY_CHAR_I (result, result_index, string, send, 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;
+      /* This is intended to handle all of the [:]op expansions and the substring/
+        length/pattern removal/pattern substitution expansions. */
+      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)
+    {
+      free (result);
+      if (no_longjmp_on_fatal_error == 0)
+       {                       /* { */
+         last_command_exit_value = EXECUTION_FAILURE;
+         report_error (_("bad substitution: no closing `%s' in %s"), "}", string);
+         exp_jump_to_top_level (DISCARD);
+       }
+      else
+       {
+         *sindex = i;
+         return ((char *)NULL);
+       }
+    }
+
+  *sindex = i;
+  result[result_index] = '\0';
+
+  return (result);
+}
+
+#define PARAMEXPNEST_MAX       32      // for now
+static int dbstate[PARAMEXPNEST_MAX];
+
 /* Extract a parameter expansion expression within ${ and } from STRING.
    Obey the Posix.2 rules for finding the ending `}': count braces while
    skipping over enclosed quoted strings and command substitutions.
@@ -1494,10 +1820,6 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
   char *result, *t;
   DECLARE_MBSTATE;
 
-  pass_character = 0;
-  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
@@ -1506,6 +1828,15 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
   if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && (flags & SX_POSIXEXP))
     dolbrace_state = DOLBRACE_QUOTE;
 
+  if (quoted == Q_HERE_DOCUMENT && dolbrace_state == DOLBRACE_QUOTE && (flags & SX_NOALLOC) == 0)
+    return (extract_heredoc_dolbrace_string (string, sindex, quoted, flags));
+
+  dbstate[0] = dolbrace_state;
+
+  pass_character = 0;
+  nesting_level = 1;
+  slen = strlen (string + *sindex) + *sindex;
+
   i = *sindex;
   while (c = string[i])
     {
@@ -1526,8 +1857,12 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
 
       if (string[i] == '$' && string[i+1] == LBRACE)
        {
+         if (nesting_level < PARAMEXPNEST_MAX)
+           dbstate[nesting_level] = dolbrace_state;
          nesting_level++;
          i += 2;
+         if (dolbrace_state == DOLBRACE_QUOTE || dolbrace_state == DOLBRACE_WORD)
+           dolbrace_state = DOLBRACE_PARAM;
          continue;
        }
 
@@ -1536,6 +1871,7 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
          nesting_level--;
          if (nesting_level == 0)
            break;
+         dolbrace_state = (nesting_level < PARAMEXPNEST_MAX) ? dbstate[nesting_level] : dbstate[0];    /* Guess using initial state */
          i++;
          continue;
        }
@@ -1715,12 +2051,14 @@ unquote_bang (string)
 
 #define CQ_RETURN(x) do { no_longjmp_on_fatal_error = oldjmp; return (x); } while (0)
 
-/* This function assumes s[i] == open; returns with s[ret] == close; used to
-   parse array subscripts.  FLAGS & 1 means to not attempt to skip over
-   matched pairs of quotes or backquotes, or skip word expansions; it is
-   intended to be used after expansion has been performed and during final
-   assignment parsing (see arrayfunc.c:assign_compound_array_list()) or
-   during execution by a builtin which has already undergone word expansion. */
+/* When FLAGS & 2 == 0, this function assumes STRING[I] == OPEN; when
+   FLAGS & 2 != 0, it assumes STRING[I] points to one character past OPEN;
+   returns with STRING[RET] == close; used to parse array subscripts.
+   FLAGS & 1 means not to attempt to skip over matched pairs of quotes or
+   backquotes, or skip word expansions; it is intended to be used after
+   expansion has been performed and during final assignment parsing (see
+   arrayfunc.c:assign_compound_array_list()) or during execution by a builtin
+   which has already undergone word expansion. */
 static int
 skip_matched_pair (string, start, open, close, flags)
      const char *string;
@@ -1735,7 +2073,10 @@ skip_matched_pair (string, start, open, close, flags)
   oldjmp = no_longjmp_on_fatal_error;
   no_longjmp_on_fatal_error = 1;
 
-  i = start + 1;               /* skip over leading bracket */
+  /* Move to the first character after a leading OPEN. If FLAGS&2, we assume
+    that START already points to that character. If not, we need to skip over
+    it here. */
+  i = (flags & 2) ? start : start + 1;
   count = 1;
   pass_next = backq = 0;
   ss = (char *)string;
@@ -1788,7 +2129,7 @@ skip_matched_pair (string, start, open, close, flags)
                          : skip_double_quoted (ss, slen, ++i, 0);
          /* no increment, the skip functions increment past the closing quote. */
        }
-      else if ((flags&1) == 0 && c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
+      else if ((flags & 1) == 0 && c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
        {
          si = i + 2;
          if (string[si] == '\0')
@@ -1816,9 +2157,11 @@ skip_matched_pair (string, start, open, close, flags)
 }
 
 #if defined (ARRAY_VARS)
-/* Flags has 1 as a reserved value, since skip_matched_pair uses it for
+/* FLAGS has 1 as a reserved value, since skip_matched_pair uses it for
    skipping over quoted strings and taking the first instance of the
-   closing character. */
+   closing character. FLAGS & 2 means that STRING[START] points one
+   character past the open bracket; FLAGS & 2 == 0 means that STRING[START]
+   points to the open bracket. skip_matched_pair knows how to deal with this. */
 int
 skipsubscript (string, start, flags)
      const char *string;
@@ -1942,7 +2285,7 @@ skip_to_delim (string, start, delims, flags)
            CQ_RETURN(si);
 
          if (string[i+1] == LPAREN)
-           temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+           temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND|completeflag); /* ) */
          else
            temp = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
          CHECK_STRING_OVERRUN (i, si, slen, c);
@@ -2154,6 +2497,21 @@ char_is_quoted (string, eindex)
   oldjmp = no_longjmp_on_fatal_error;
   no_longjmp_on_fatal_error = 1;
   i = pass_next = 0;
+
+  /* If we have an open quoted string from a previous line, see if it's
+     closed before string[eindex], so we don't interpret that close quote
+     as starting a new quoted string. */
+  if (current_command_line_count > 0 && dstack.delimiter_depth > 0)
+    {
+      c = dstack.delimiters[dstack.delimiter_depth - 1];
+      if (c == '\'')
+       i = skip_single_quoted (string, slen, 0, 0);
+      else if (c == '"')
+       i = skip_double_quoted (string, slen, 0, SX_COMPLETE);
+      if (i > eindex)
+       CQ_RETURN (1);
+    }
+
   while (i <= eindex)
     {
       c = string[i];
@@ -2253,7 +2611,7 @@ WORD_LIST *
 split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp)
      char *string;
      int slen;
-     char *delims;
+     const char *delims;
      int sentinel, flags;
      int *nwp, *cwp;
 {
@@ -2270,7 +2628,7 @@ split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp)
       return ((WORD_LIST *)NULL);
     }
 
-  d = (delims == 0) ? ifs_value : delims;
+  d = (delims == 0) ? ifs_value : (char *)delims;
   ifs_split = delims == 0;
 
   /* Make d2 the non-whitespace characters in delims */
@@ -2350,7 +2708,7 @@ split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp)
 
       token = substring (string, ts, te);
 
-      ret = add_string_to_list (token, ret);
+      ret = add_string_to_list (token, ret);   /* XXX */
       free (token);
       nw++;
 
@@ -2674,11 +3032,13 @@ string_list_dollar_at (list, quoted, flags)
    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. */
+/* This needs to fully understand the additional contexts where word
+   splitting does not occur (W_ASSIGNRHS, etc.) */
 char *
-string_list_pos_params (pchar, list, quoted)
+string_list_pos_params (pchar, list, quoted, pflags)
      int pchar;
      WORD_LIST *list;
-     int quoted;
+     int quoted, pflags;
 {
   char *ret;
   WORD_LIST *tlist;
@@ -2697,6 +3057,8 @@ string_list_pos_params (pchar, list, quoted)
     }
   else if (pchar == '*' && quoted == 0 && ifs_is_null) /* XXX */
     ret = expand_no_split_dollar_star ? string_list_dollar_star (list, quoted, 0) : string_list_dollar_at (list, quoted, 0);   /* Posix interp 888 */
+  else if (pchar == '*' && quoted == 0 && (pflags & PF_ASSIGNRHS))     /* XXX */
+    ret = expand_no_split_dollar_star ? string_list_dollar_star (list, quoted, 0) : string_list_dollar_at (list, quoted, 0);   /* Posix interp 888 */
   else if (pchar == '*')
     {
       /* Even when unquoted, string_list_dollar_star does the right thing
@@ -2715,6 +3077,8 @@ string_list_pos_params (pchar, list, quoted)
     ret = string_list_dollar_at (list, quoted, 0);
   else if (pchar == '@' && quoted == 0 && ifs_is_null) /* XXX */
     ret = string_list_dollar_at (list, quoted, 0);     /* Posix interp 888 */
+  else if (pchar == '@' && quoted == 0 && (pflags & PF_ASSIGNRHS))
+    ret = string_list_dollar_at (list, quoted, pflags);        /* Posix interp 888 */
   else if (pchar == '@')
     ret = string_list_dollar_star (list, quoted, 0);
   else
@@ -2770,7 +3134,7 @@ list_string (string, separators, quoted)
   WORD_LIST *result;
   WORD_DESC *t;
   char *current_word, *s;
-  int sindex, sh_style_split, whitesep, xflags;
+  int sindex, sh_style_split, whitesep, xflags, free_word;
   size_t slen;
 
   if (!string || !*string)
@@ -2792,7 +3156,14 @@ list_string (string, separators, quoted)
      STRING is quoted or if there are no separator characters. We use the
      Posix definition of whitespace as a member of the space character
      class in the current locale. */
+#if 0
   if (!quoted || !separators || !*separators)
+#else
+  /* issep() requires that separators be non-null, and always returns 0 if
+     separator is the empty string, so don't bother if we get an empty string
+     for separators. We already returned NULL above if STRING is empty. */
+  if (!quoted && separators && *separators)
+#endif
     {
       for (s = string; *s && issep (*s) && ifs_whitespace (*s); s++);
 
@@ -2816,6 +3187,8 @@ list_string (string, separators, quoted)
       if (current_word == 0)
        break;
 
+      free_word = 1;   /* If non-zero, we free current_word */
+
       /* If we have a quoted empty string, add a quoted null argument.  We
         want to preserve the quoted null character iff this is a quoted
         empty string; otherwise the quoted null characters are removed
@@ -2832,10 +3205,22 @@ list_string (string, separators, quoted)
          /* If we have something, then add it regardless.  However,
             perform quoted null character removal on the current word. */
          remove_quoted_nulls (current_word);
-         result = add_string_to_list (current_word, result);
+
+         /* We don't want to set the word flags based on the string contents
+            here -- that's mostly for the parser -- so we just allocate a
+            WORD_DESC *, assign current_word (noting that we don't want to
+            free it), and skip all of make_word. */
+         t = alloc_word_desc ();
+         t->word = current_word;
+         result = make_word_list (t, result);
+         free_word = 0;
          result->word->flags &= ~W_HASQUOTEDNULL;      /* just to be sure */
          if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
            result->word->flags |= W_QUOTED;
+         /* If removing quoted null characters leaves an empty word, note
+            that we saw this for the caller to act on. */
+         if (current_word == 0 || current_word[0] == '\0')
+           result->word->flags |= W_SAWQUOTEDNULL;
        }
 
       /* If we're not doing sequences of separators in the traditional
@@ -2848,7 +3233,8 @@ list_string (string, separators, quoted)
          result = make_word_list (t, result);
        }
 
-      free (current_word);
+      if (free_word)
+       free (current_word);
 
       /* Note whether or not the separator is IFS whitespace, used later. */
       whitesep = string[sindex] && ifs_whitesep (string[sindex]);
@@ -3231,7 +3617,8 @@ do_assignment_internal (word, expand)
          report_error (_("%s: cannot assign list to array member"), name);
          ASSIGN_RETURN (0);
        }
-      entry = assign_array_element (name, value, aflags);
+      aflags |= ASS_ALLOWALLSUB;       /* allow a[@]=value for existing associative arrays */
+      entry = assign_array_element (name, value, aflags, (array_eltstate_t *)0);
       if (entry == 0)
        ASSIGN_RETURN (0);
     }
@@ -3251,14 +3638,17 @@ do_assignment_internal (word, expand)
 #endif /* ARRAY_VARS */
   entry = bind_variable (name, value, aflags);
 
-  stupidly_hack_special_variables (name);
+  if (entry)
+    stupidly_hack_special_variables (entry->name);     /* might be a nameref */
+  else
+    stupidly_hack_special_variables (name);
 
   /* Return 1 if the assignment seems to have been performed correctly. */
   if (entry == 0 || readonly_p (entry))
     retval = 0;                /* assignment failure */
   else if (noassign_p (entry))
     {
-      last_command_exit_value = EXECUTION_FAILURE;
+      set_exit_status (EXECUTION_FAILURE);
       retval = 1;      /* error status, but not assignment failure */
     }
   else
@@ -3330,19 +3720,6 @@ list_rest_of_args ()
   return (REVERSE_LIST (list, WORD_LIST *));
 }
 
-int
-number_of_args ()
-{
-  register WORD_LIST *list;
-  int n;
-
-  for (n = 0; n < 9 && dollar_vars[n+1]; n++)
-    ;
-  for (list = rest_of_args; list; list = list->next)
-    n++;
-  return n;
-}
-
 /* Return the value of a positional parameter.  This handles values > 10. */
 char *
 get_dollar_var_value (ind)
@@ -3385,9 +3762,9 @@ string_rest_of_args (dollar_star)
    Q_HERE_DOCUMENT or Q_DOUBLE_QUOTES, this returns a quoted list, otherwise
    no quoting chars are added. */
 static char *
-pos_params (string, start, end, quoted)
+pos_params (string, start, end, quoted, pflags)
      char *string;
-     int start, end, quoted;
+     int start, end, quoted, pflags;
 {
   WORD_LIST *save, *params, *h, *t;
   char *ret;
@@ -3421,7 +3798,7 @@ pos_params (string, start, end, quoted)
     }
   t->next = (WORD_LIST *)NULL;
 
-  ret = string_list_pos_params (string[0], h, quoted);
+  ret = string_list_pos_params (string[0], h, quoted, pflags);
 
   if (t != params)
     t->next = params;
@@ -3442,6 +3819,10 @@ pos_params (string, start, end, quoted)
 #define EXP_CHAR(s) (s == '$' || s == '`' || s == CTLESC || s == '~')
 #endif
 
+/* We don't perform process substitution in arithmetic expressions, so don't
+   bother checking for it. */
+#define ARITH_EXP_CHAR(s) (s == '$' || s == '`' || s == CTLESC || s == '~')
+
 /* If there are any characters in STRING that require full expansion,
    then call FUNC to expand STRING; otherwise just perform quote
    removal if necessary.  This returns a new string. */
@@ -3536,35 +3917,137 @@ expand_assignment_string_to_string (string, quoted)
   return (expand_string_to_string_internal (string, quoted, expand_string_assignment));
 }
 
-char *
-expand_arith_string (string, quoted)
+/* Kind of like a combination of dequote_string and quote_string_for_globbing;
+   try to remove CTLESC quoting characters and convert CTLESC escaping a `&'
+   or a backslash into a backslash. The output of this function must eventually
+   be processed by strcreplace(). */
+static char *
+quote_string_for_repl (string, flags)
      char *string;
-     int quoted;
+     int flags;
 {
-  WORD_DESC td;
-  WORD_LIST *list, *tlist;
   size_t slen;
-  int i, saw_quote;
-  char *ret;
+  char *result, *t;
+  const char *s, *send;
   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])
+  slen = strlen (string);
+  send = string + slen;
+
+  result = (char *)xmalloc (slen * 2 + 1);
+
+  if (string[0] == CTLESC && string[1] == 0)
     {
-      if (EXP_CHAR (string[i]))
-       break;
-      else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"')
-       saw_quote = 1;
-      ADVANCE_CHAR (string, slen, i);
+      result[0] = CTLESC;
+      result[1] = '\0';
+      return (result);
     }
 
-  if (string[i])
+  /* This is awkward. We want to translate CTLESC-\ to \\ if we will
+     eventually send this string through strcreplace(), which we will do
+     only if shouldexp_replacement() determines that there is something
+     to replace. We can either make sure to escape backslashes here and
+     have shouldexp_replacement() signal that we should send the string to
+     strcreplace() if it sees an escaped backslash, or we can scan the
+     string before copying it and turn CTLESC-\ into \\ only if we encounter
+     a CTLESC-& or a &. This does the former and changes shouldexp_replacement().
+     If we double the backslashes  here, we'll get doubled backslashes in any
+     result that doesn't get passed to strcreplace(). */
+
+  for (s = string, t = result; *s; )
+    {
+      /* This function's result has to be processed by strcreplace() */
+      if (*s == CTLESC && (s[1] == '&' || s[1] == '\\'))
+        {
+          *t++ = '\\';
+          s++;
+          *t++ = *s++;
+          continue;
+        }
+      /* Dequote it */
+      if (*s == CTLESC)
+        {
+         s++;
+         if (*s == '\0')
+           break;
+        }
+      COPY_CHAR_P (t, s, send);
+    }
+
+  *t = '\0';
+  return (result);
+}
+       
+/* This does not perform word splitting on the WORD_LIST it returns and
+   it treats $* as if it were quoted. It dequotes the WORD_LIST, adds
+   backslash escapes before CTLESC-quoted backslash and `& if 
+   patsub_replacement is enabled. */
+static char *
+expand_string_for_patsub (string, quoted)
+     char *string;
+     int quoted;
+{
+  WORD_LIST *value;
+  char *ret, *t;
+
+  if (string == 0 || *string == '\0')
+    return (char *)NULL;
+
+  value = expand_string_for_pat (string, quoted, (int *)0, (int *)0);
+
+  if (value && value->word)
+    {
+      remove_quoted_nulls (value->word->word); /* XXX */
+      value->word->flags &= ~W_HASQUOTEDNULL;
+    }
+
+  if (value)
+    {
+      t = (value->next) ? string_list (value) : value->word->word;
+      ret = quote_string_for_repl (t, quoted);
+      if (t != value->word->word)
+       free (t);
+      dispose_words (value);
+    }
+  else
+    ret = (char *)NULL;
+
+  return (ret);
+}
+
+char *
+expand_arith_string (string, quoted)
+     char *string;
+     int quoted;
+{
+  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 (ARITH_EXP_CHAR (string[i]))
+       break;
+      else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"')
+       saw_quote = string[i];
+      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|W_NOTILDE;        /* don't want process substitution or tilde expansion */
+#if 0  /* TAG: bash-5.2 */
+      if (quoted & Q_ARRAYSUB)
+       td.flags |= W_NOCOMSUB;
+#endif
       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
@@ -3624,7 +4107,9 @@ remove_backslashes (string)
    to the != or == operator, and should be treated as a pattern.  In
    this case, we quote the string specially for the globbing code.  If
    SPECIAL is 2, this is an rhs argument for the =~ operator, and should
-   be quoted appropriately for regcomp/regexec.  The caller is responsible
+   be quoted appropriately for regcomp/regexec.  If SPECIAL is 3, this is
+   an array subscript and should be quoted after expansion so it's only
+   expanded once (Q_ARITH). The caller is responsible
    for removing the backslashes if the unquoted word is needed later. In
    any case, since we don't perform word splitting, we need to do quoted
    null character removal. */
@@ -3642,7 +4127,8 @@ cond_expand_word (w, special)
 
   expand_no_split_dollar_star = 1;
   w->flags |= W_NOSPLIT2;
-  l = call_expand_word_internal (w, 0, 0, (int *)0, (int *)0);
+  qflags = (special == 3) ? Q_ARITH : 0;
+  l = call_expand_word_internal (w, qflags, 0, (int *)0, (int *)0);
   expand_no_split_dollar_star = 0;
   if (l)
     {
@@ -3653,6 +4139,13 @@ cond_expand_word (w, special)
          dequote_list (l);
          r = string_list (l);
        }
+      else if (special == 3)           /* arithmetic expression, Q_ARITH */
+       {
+         if (l->word)
+           word_list_remove_quoted_nulls (l);  /* for now */
+         dequote_list (l);
+         r = string_list (l);
+       }
       else
        {
          /* Need to figure out whether or not we should call dequote_escapes
@@ -3675,6 +4168,150 @@ cond_expand_word (w, special)
 }
 #endif
 
+/* Expand $'...' and $"..." in a string for code paths that don't do it. The
+   FLAGS argument is 1 if this function should treat CTLESC as a quote
+   character (e.g., for here-documents) or not (e.g., for shell_expand_line). */
+char *
+expand_string_dollar_quote (string, flags)
+     char *string;
+     int flags;
+{
+  size_t slen, retind, retsize;
+  int sindex, c, translen, peekc, news;
+  char *ret, *trans, *send, *t;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string);
+  send = string + slen;
+  sindex = 0;
+
+  retsize = slen + 1;
+  ret = xmalloc (retsize);
+  retind = 0;
+
+  while (c = string[sindex])
+    {
+      switch (c)
+       {
+       default:
+         RESIZE_MALLOCED_BUFFER (ret, retind, locale_mb_cur_max + 1, retsize, 64);
+         COPY_CHAR_I (ret, retind, string, send, sindex);
+         break;
+
+       case '\\':
+         RESIZE_MALLOCED_BUFFER (ret, retind, locale_mb_cur_max + 2, retsize, 64);
+         ret[retind++] = string[sindex++];
+
+         if (string[sindex])
+           COPY_CHAR_I (ret, retind, string, send, sindex);
+         break;
+
+       case '\'':
+       case '"':
+         if (c == '\'')
+           news = skip_single_quoted (string, slen, ++sindex, SX_COMPLETE);
+         else
+           news = skip_double_quoted (string, slen, ++sindex, SX_COMPLETE);
+         translen = news - sindex - 1;
+         RESIZE_MALLOCED_BUFFER (ret, retind, translen + 3, retsize, 64);
+         ret[retind++] = c;
+         if (translen > 0)
+           {
+             strncpy (ret + retind, string + sindex, translen);
+             retind += translen;
+           }
+         if (news > sindex && string[news - 1] == c)
+           ret[retind++] = c;
+         sindex = news;
+         break;
+
+       case CTLESC:
+         RESIZE_MALLOCED_BUFFER (ret, retind, locale_mb_cur_max + 2, retsize, 64);
+         if (flags)
+           ret[retind++] = string[sindex++];
+         if (string[sindex])
+           COPY_CHAR_I (ret, retind, string, send, sindex);
+         break;
+
+       case '$':
+         peekc = string[++sindex];
+#if defined (TRANSLATABLE_STRINGS)
+         if (peekc != '\'' && peekc != '"')
+#else
+         if (peekc != '\'')
+#endif
+           {
+             RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 16);
+             ret[retind++] = c;
+             break;
+           }
+         if (string[sindex + 1] == '\0')       /* don't bother */      
+           {
+             RESIZE_MALLOCED_BUFFER (ret, retind, 3, retsize, 16);
+             ret[retind++] = c;
+             ret[retind++] = peekc;
+             sindex++;
+             break;
+           }
+         if (peekc == '\'')
+           {
+             /* SX_COMPLETE is the  equivalent of ALLOWESC here */
+             /* We overload SX_COMPLETE below */
+             news = skip_single_quoted (string, slen, ++sindex, SX_COMPLETE);
+             /* Check for unclosed string and don't bother if so */
+             if (news > sindex && string[news] == '\0' && string[news-1] != peekc)
+               {
+                 RESIZE_MALLOCED_BUFFER (ret, retind, 3, retsize, 16);
+                 ret[retind++] = c;
+                 ret[retind++] = peekc;
+                 continue;
+               }
+             t = substring (string, sindex, news - 1);
+             trans = ansiexpand (t, 0, news-sindex-1, &translen);
+             free (t);
+             t = sh_single_quote (trans);
+             sindex = news;
+           }
+#if defined (TRANSLATABLE_STRINGS)
+         else
+           {
+             news = ++sindex;
+             t = string_extract_double_quoted (string, &news, SX_COMPLETE);
+             /* Check for unclosed string and don't bother if so */
+             if (news > sindex && string[news] == '\0' && string[news-1] != peekc)
+               {
+                 RESIZE_MALLOCED_BUFFER (ret, retind, 3, retsize, 16);
+                 ret[retind++] = c;
+                 ret[retind++] = peekc;
+                 free (t);
+                 continue;
+               }
+             trans = locale_expand (t, 0, news-sindex, 0, &translen);
+             free (t);
+             if (singlequote_translations &&
+                   ((news-sindex-1) != translen || STREQN (t, trans, translen) == 0))
+               t = sh_single_quote (trans);
+             else
+               t = sh_mkdoublequoted (trans, translen, 0);
+             sindex = news;
+           }
+#endif /* TRANSLATABLE_STRINGS */
+         free (trans);
+         trans = t;
+         translen = strlen (trans);
+
+         RESIZE_MALLOCED_BUFFER (ret, retind, translen + 1, retsize, 128);
+         strcpy (ret + retind, trans);
+         retind += translen;
+         FREE (trans);
+         break;
+       }
+    }
+
+  ret[retind] = 0;
+  return ret;
+}
+
 /* Call expand_word_internal to expand W and handle error returns.
    A convenience function for functions that don't want to handle
    any errors or free any memory before aborting. */
@@ -3749,7 +4386,7 @@ expand_string_unsplit (string, quoted)
     {
       if (value->word)
        {
-         remove_quoted_nulls (value->word->word);
+         remove_quoted_nulls (value->word->word);      /* XXX */
          value->word->flags &= ~W_HASQUOTEDNULL;
        }
       dequote_list (value);
@@ -3781,6 +4418,7 @@ expand_string_assignment (string, quoted)
 #else
   td.flags = W_ASSIGNRHS;
 #endif
+  td.flags |= (W_NOGLOB|W_TILDEEXP);
   td.word = savestring (string);
   value = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
   FREE (td.word);
@@ -3791,7 +4429,7 @@ expand_string_assignment (string, quoted)
     {
       if (value->word)
        {
-         remove_quoted_nulls (value->word->word);
+         remove_quoted_nulls (value->word->word);      /* XXX */
          value->word->flags &= ~W_HASQUOTEDNULL;
        }
       dequote_list (value);
@@ -3799,7 +4437,6 @@ expand_string_assignment (string, quoted)
   return (value);
 }
 
-
 /* Expand one of the PS? prompt strings. This is a sort of combination of
    expand_string_unsplit and expand_string_internal, but returns the
    passed string when an error occurs.  Might want to trap other calls
@@ -3833,7 +4470,7 @@ expand_prompt_string (string, quoted, wflags)
     {
       if (value->word)
        {
-         remove_quoted_nulls (value->word->word);
+         remove_quoted_nulls (value->word->word);      /* XXX */
          value->word->flags &= ~W_HASQUOTEDNULL;
        }
       dequote_list (value);
@@ -3890,6 +4527,7 @@ expand_string_for_rhs (string, quoted, op, pflags, dollar_at_p, expanded_p)
      depending on whether or not are are going to be splitting: if the expansion
      is quoted, if the OP is `=', or if IFS is set to the empty string, we
      are not going to be splitting, so we set expand_no_split_dollar_star to
+     note this to callees.
      We pass through PF_ASSIGNRHS as W_ASSIGNRHS if this is on the RHS of an
      assignment statement. */
   /* The updated treatment of $* is the result of Posix interp 888 */
@@ -3897,7 +4535,8 @@ expand_string_for_rhs (string, quoted, op, pflags, dollar_at_p, expanded_p)
      in Posix bug 1129 */
   old_nosplit = expand_no_split_dollar_star;
   expand_no_split_dollar_star = (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || op == '=' || ifs_is_null == 0; /* XXX - was 1 */
-  td.flags = W_NOSPLIT2;               /* no splitting, remove "" and '' */
+  td.flags = W_EXPANDRHS;              /* expanding RHS of ${paramOPword} */
+  td.flags |= W_NOSPLIT2;              /* no splitting, remove "" and '' */
   if (pflags & PF_ASSIGNRHS)           /* pass through */
     td.flags |= W_ASSIGNRHS;
   if (op == '=')
@@ -3906,9 +4545,10 @@ expand_string_for_rhs (string, quoted, op, pflags, dollar_at_p, expanded_p)
 #else
     td.flags |= W_ASSIGNRHS|W_NOASSNTILDE;             /* expand b in ${a=b} like assignment */
 #endif
-  td.word = string;
+  td.word = savestring (string);
   tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, expanded_p);
   expand_no_split_dollar_star = old_nosplit;
+  free (td.word);
 
   return (tresult);
 }
@@ -3930,9 +4570,10 @@ expand_string_for_pat (string, quoted, dollar_at_p, expanded_p)
   oexp = expand_no_split_dollar_star;
   expand_no_split_dollar_star = 1;
   td.flags = W_NOSPLIT2;               /* no splitting, remove "" and '' */
-  td.word = string;
+  td.word = savestring (string);
   tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, expanded_p);
   expand_no_split_dollar_star = oexp;
+  free (td.word);
 
   return (tresult);
 }
@@ -4242,10 +4883,8 @@ dequote_string (string)
   char *result, *send;
   DECLARE_MBSTATE;
 
-#if defined (DEBUG)
   if (string[0] == CTLESC && string[1] == 0)
-    internal_inform ("dequote_string: string with bare CTLESC");
-#endif
+    internal_debug ("dequote_string: string with bare CTLESC");
 
   slen = STRLEN (string);
 
@@ -4785,6 +5424,8 @@ match_upattern (string, pat, mtype, sp, ep)
   end = string + len;
 
   mlen = umatchlen (pat, len);
+  if (mlen > (int)len)
+    return (0);
 
   switch (mtype)
     {
@@ -4940,6 +5581,8 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
     return (0);
 
   mlen = wmatchlen (wpat, wstrlen);
+  if (mlen > (int)wstrlen)
+    return (0);
 
 /* itrace("wmatchlen (%ls) -> %d", wpat, mlen); */
   switch (mtype)
@@ -5112,6 +5755,8 @@ getpattern (value, quoted, expandpat)
                                      (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? Q_PATQUOTE : quoted,
                                      (int *)NULL, (int *)NULL)
             : (WORD_LIST *)0;
+  if (l)
+    word_list_remove_quoted_nulls (l);
   pat = string_list (l);
   dispose_words (l);
   if (pat)
@@ -5158,7 +5803,7 @@ list_remove_pattern (list, pattern, patspec, itype, quoted)
     }
 
   l = REVERSE_LIST (new, WORD_LIST *);
-  tword = string_list_pos_params (itype, l, quoted);
+  tword = string_list_pos_params (itype, l, quoted, 0);
   dispose_words (l);
 
   return (tword);
@@ -5183,11 +5828,11 @@ parameter_list_remove_pattern (itype, pattern, patspec, quoted)
 
 #if defined (ARRAY_VARS)
 static char *
-array_remove_pattern (var, pattern, patspec, varname, quoted)
+array_remove_pattern (var, pattern, patspec, starsub, quoted)
      SHELL_VAR *var;
      char *pattern;
      int patspec;
-     char *varname;    /* so we can figure out how it's indexed */
+     int starsub;      /* so we can figure out how it's indexed */
      int quoted;
 {
   ARRAY *a;
@@ -5197,14 +5842,9 @@ array_remove_pattern (var, pattern, patspec, varname, quoted)
   WORD_LIST *list;
   SHELL_VAR *v;
 
-  /* compute itype from varname here */
-  v = array_variable_part (varname, 0, &ret, 0);
-
-  /* XXX */
-  if (v && invisible_p (v))
-    return ((char *)NULL);
+  v = var;             /* XXX - for now */
 
-  itype = ret[0];
+  itype = starsub ? '*' : '@';
 
   a = (v && array_p (v)) ? array_cell (v) : 0;
   h = (v && assoc_p (v)) ? assoc_cell (v) : 0;
@@ -5220,9 +5860,9 @@ array_remove_pattern (var, pattern, patspec, varname, quoted)
 #endif /* ARRAY_VARS */
 
 static char *
-parameter_brace_remove_pattern (varname, value, ind, patstr, rtype, quoted, flags)
+parameter_brace_remove_pattern (varname, value, estatep, patstr, rtype, quoted, flags)
      char *varname, *value;
-     int ind;
+     array_eltstate_t *estatep;
      char *patstr;
      int rtype, quoted, flags;
 {
@@ -5236,7 +5876,7 @@ parameter_brace_remove_pattern (varname, value, ind, patstr, rtype, quoted, flag
   oname = this_command_name;
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
+  vtype = get_var_and_type (varname, value, estatep, quoted, flags, &v, &val);
   if (vtype == -1)
     {
       this_command_name = oname;
@@ -5275,7 +5915,7 @@ parameter_brace_remove_pattern (varname, value, ind, patstr, rtype, quoted, flag
       break;
 #if defined (ARRAY_VARS)
     case VT_ARRAYVAR:
-      temp1 = array_remove_pattern (v, pattern, patspec, varname, quoted);
+      temp1 = array_remove_pattern (v, pattern, patspec, starsub, quoted);
       if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
        {
          val = quote_escapes (temp1);
@@ -5307,6 +5947,8 @@ parameter_brace_remove_pattern (varname, value, ind, patstr, rtype, quoted, flag
 
 #if defined (PROCESS_SUBSTITUTION)
 
+static void reap_some_procsubs PARAMS((int));
+
 /*****************************************************************/
 /*                                                              */
 /*                 Hacking Process Substitution                 */
@@ -5316,8 +5958,11 @@ parameter_brace_remove_pattern (varname, value, ind, patstr, rtype, quoted, flag
 #if !defined (HAVE_DEV_FD)
 /* Named pipes must be removed explicitly with `unlink'.  This keeps a list
    of FIFOs the shell has open.  unlink_fifo_list will walk the list and
-   unlink all of them. add_fifo_list adds the name of an open FIFO to the
-   list.  NFIFO is a count of the number of FIFOs in the list. */
+   unlink the ones that don't have a living process on the other end.
+   unlink_all_fifos will walk the list and unconditionally unlink them, trying
+   to open and close the FIFO first to release any child processes sleeping on
+   the FIFO. add_fifo_list adds the name of an open FIFO to the list. 
+   NFIFO is a count of the number of FIFOs in the list. */
 #define FIFO_INCR 20
 
 /* PROC value of -1 means the process has been reaped and the FIFO needs to
@@ -5334,6 +5979,16 @@ static int fifo_list_size;
 void
 clear_fifo_list ()
 {
+  int i;
+
+  for (i = 0; i < fifo_list_size; i++)
+    {
+      if (fifo_list[i].file)
+       free (fifo_list[i].file);
+      fifo_list[i].file = NULL;
+      fifo_list[i].proc = 0;
+    }
+  nfifo = 0;
 }
 
 void *
@@ -5423,6 +6078,30 @@ unlink_fifo_list ()
     nfifo = 0;
 }
 
+void
+unlink_all_fifos ()
+{
+  int i, fd;
+
+  if (nfifo == 0)
+    return;
+
+  for (i = 0; i < nfifo; i++)
+    {
+      fifo_list[i].proc = (pid_t)-1;
+#if defined (O_NONBLOCK)
+      fd = open (fifo_list[i].file, O_RDWR|O_NONBLOCK);
+#else
+      fd = -1;
+#endif
+      unlink_fifo (i);
+      if (fd >= 0)
+       close (fd);
+    }
+
+  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
@@ -5474,16 +6153,25 @@ set_procsub_status (ind, pid, status)
 
 /* If we've marked the process for this procsub as dead, close the
    associated file descriptor and delete the FIFO. */
-void
-reap_procsubs ()
+static void
+reap_some_procsubs (max)
+     int max;
 {
   int i;
 
-  for (i = 0; i < nfifo; i++)
+  for (i = 0; i < max; i++)
     if (fifo_list[i].proc == (pid_t)-1)        /* reaped */
       unlink_fifo (i);
 }
 
+void
+reap_procsubs ()
+{
+  reap_some_procsubs (nfifo);
+}
+
+#if 0
+/* UNUSED */
 void
 wait_procsubs ()
 {
@@ -5493,11 +6181,13 @@ wait_procsubs ()
     {
       if (fifo_list[i].proc != (pid_t)-1 && fifo_list[i].proc > 0)
        {
-         r = wait_for (fifo_list[i].proc);
+         r = wait_for (fifo_list[i].proc, 0);
+         save_proc_status (fifo_list[i].proc, r);
          fifo_list[i].proc = (pid_t)-1;
        }
     }
 }
+#endif
 
 int
 fifos_pending ()
@@ -5646,6 +6336,12 @@ unlink_fifo_list ()
   nfds = 0;
 }
 
+void
+unlink_all_fifos ()
+{
+  unlink_fifo_list ();
+}
+
 /* 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.
@@ -5701,16 +6397,25 @@ set_procsub_status (ind, pid, status)
 
 /* If we've marked the process for this procsub as dead, close the
    associated file descriptor. */
-void
-reap_procsubs ()
+static void
+reap_some_procsubs (max)
+     int max;
 {
   int i;
 
-  for (i = 0; nfds > 0 && i < totfds; i++)
+  for (i = 0; nfds > 0 && i < max; i++)
     if (dev_fd_list[i] == (pid_t)-1)
       unlink_fifo (i);
 }
 
+void
+reap_procsubs ()
+{
+  reap_some_procsubs (totfds);
+}
+
+#if 0
+/* UNUSED */
 void
 wait_procsubs ()
 {
@@ -5720,11 +6425,13 @@ wait_procsubs ()
     {
       if (dev_fd_list[i] != (pid_t)-1 && dev_fd_list[i] > 0)
        {
-         r = wait_for (dev_fd_list[i]);
+         r = wait_for (dev_fd_list[i], 0);
+         save_proc_status (dev_fd_list[i], r);
          dev_fd_list[i] = (pid_t)-1;
        }
     }
 }
+#endif
 
 #if defined (NOTDEF)
 print_dev_fd_list ()
@@ -5826,16 +6533,30 @@ process_substitute (string, open_for_read_in_child)
   save_pipeline (1);
 #endif /* JOB_CONTROL */
 
-  pid = make_child ((char *)NULL, 1);
+  pid = make_child ((char *)NULL, FORK_ASYNC);
   if (pid == 0)
     {
+#if 0
+      int old_interactive;
+
+      old_interactive = interactive;
+#endif
+      /* The currently-executing shell is not interactive */
+      interactive = 0;
+
       reset_terminating_signals ();    /* XXX */
       free_pushed_string_input ();
       /* Cancel traps, in trap.c. */
       restore_original_signals ();     /* XXX - what about special builtins? bash-4.2 */
+      subshell_environment &= ~SUBSHELL_IGNTRAP;
       QUIT;    /* catch any interrupts we got post-fork */
       setup_async_signals ();
-      subshell_environment |= SUBSHELL_COMSUB|SUBSHELL_PROCSUB;
+#if 0
+      if (open_for_read_in_child == 0 && old_interactive && (bash_input.type == st_stdin || bash_input.type == st_stream))
+       async_redirect_stdin ();
+#endif
+
+      subshell_environment |= SUBSHELL_COMSUB|SUBSHELL_PROCSUB|SUBSHELL_ASYNC;
 
       /* We don't inherit the verbose option for command substitutions now, so
         let's try it for process substitutions. */
@@ -5874,9 +6595,11 @@ process_substitute (string, open_for_read_in_child)
   if (pid > 0)
     {
 #if defined (JOB_CONTROL)
-      if (last_procsub_child)
-       discard_last_procsub_child ();
       last_procsub_child = restore_pipeline (0);
+      /* We assume that last_procsub_child->next == last_procsub_child because
+        of how jobs.c:add_process() works. */
+      last_procsub_child->next = 0;
+      procsub_add (last_procsub_child);
 #endif
 
 #if defined (HAVE_DEV_FD)
@@ -5904,6 +6627,9 @@ process_substitute (string, open_for_read_in_child)
   /* make sure we don't have any job control */
   set_job_control (0);
 
+  /* Clear out any existing list of process substitutions */
+  procsub_clear ();
+
   /* The idea is that we want all the jobs we start from an async process
      substitution to be in the same process group, but not the same pgrp
      as our parent shell, since we don't want to affect our parent shell's
@@ -5986,6 +6712,9 @@ process_substitute (string, open_for_read_in_child)
 
   remove_quoted_escapes (string);
 
+  startup_state = 2;   /* see if we can avoid a fork */
+  parse_and_execute_level = 0;
+
   /* Give process substitution a place to jump back to on failure,
      so we don't go back up to main (). */
   result = setjmp_nosigs (top_level);
@@ -6000,7 +6729,7 @@ process_substitute (string, open_for_read_in_child)
 
   if (result == ERREXIT)
     rc = last_command_exit_value;
-  else if (result == EXITPROG)
+  else if (result == EXITPROG || result == EXITBLTIN)
     rc = last_command_exit_value;
   else if (result)
     rc = EXECUTION_FAILURE;
@@ -6031,16 +6760,44 @@ process_substitute (string, open_for_read_in_child)
 /*                                */
 /***********************************/
 
+#define COMSUB_PIPEBUF 4096
+
+static char *
+optimize_cat_file (r, quoted, flags, flagp)
+     REDIRECT *r;
+     int quoted, flags, *flagp;
+{
+  char *ret;
+  int fd;
+
+  fd = open_redir_file (r, (char **)0);
+  if (fd < 0)
+    return &expand_param_error;
+
+  ret = read_comsub (fd, quoted, flags, flagp);
+  close (fd);
+
+  return ret;
+}
+
 static char *
 read_comsub (fd, quoted, flags, rflag)
      int fd, quoted, flags;
      int *rflag;
 {
-  char *istring, buf[128], *bufp;
-  int istring_index, c, tflag, skip_ctlesc, skip_ctlnul;
+  char *istring, buf[COMSUB_PIPEBUF], *bufp;
+  int c, tflag, skip_ctlesc, skip_ctlnul;
+  int mb_cur_max;
+  size_t istring_index;
   size_t istring_size;
   ssize_t bufn;
   int nullbyte;
+#if defined (HANDLE_MULTIBYTE)
+  mbstate_t ps;
+  wchar_t wc;
+  size_t mblen;
+  int i;
+#endif
 
   istring = (char *)NULL;
   istring_index = istring_size = bufn = tflag = 0;
@@ -6048,10 +6805,10 @@ read_comsub (fd, quoted, flags, rflag)
   skip_ctlesc = ifs_cmap[CTLESC];
   skip_ctlnul = ifs_cmap[CTLNUL];
 
+  mb_cur_max = MB_CUR_MAX;
   nullbyte = 0;
 
-  /* Read the output of the command through the pipe.  This may need to be
-     changed to understand multibyte characters in the future. */
+  /* Read the output of the command through the pipe. */
   while (1)
     {
       if (fd < 0)
@@ -6078,7 +6835,7 @@ read_comsub (fd, quoted, flags, rflag)
        }
 
       /* Add the character to ISTRING, possibly after resizing it. */
-      RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, DEFAULT_ARRAY_SIZE);
+      RESIZE_MALLOCED_BUFFER (istring, istring_index, mb_cur_max+1, istring_size, 512);
 
       /* This is essentially quote_string inline */
       if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) /* || c == CTLESC || c == CTLNUL */)
@@ -6093,17 +6850,28 @@ read_comsub (fd, quoted, flags, rflag)
       else if ((skip_ctlnul == 0 && c == CTLNUL) || (c == ' ' && (ifs_value && *ifs_value == 0)))
        istring[istring_index++] = CTLESC;
 
-      istring[istring_index++] = c;
-
-#if 0
-#if defined (__CYGWIN__)
-      if (c == '\n' && istring_index > 1 && istring[istring_index - 2] == '\r')
-       {
-         istring_index--;
-         istring[istring_index - 1] = '\n';
+#if defined (HANDLE_MULTIBYTE)
+      if ((locale_utf8locale && (c & 0x80)) ||
+         (locale_utf8locale == 0 && mb_cur_max > 1 && (unsigned char)c > 127))
+       {
+         /* read a multibyte character from buf */
+         /* punt on the hard case for now */
+         memset (&ps, '\0', sizeof (mbstate_t));
+         mblen = mbrtowc (&wc, bufp-1, bufn, &ps);
+         if (MB_INVALIDCH (mblen) || mblen == 0 || mblen == 1)
+           istring[istring_index++] = c;
+         else
+           {
+             istring[istring_index++] = c;
+             for (i = 0; i < mblen-1; i++)
+               istring[istring_index++] = *bufp++;
+             bufn -= mblen - 1;
+           }
+         continue;
        }
 #endif
-#endif
+
+      istring[istring_index++] = c;
     }
 
   if (istring)
@@ -6155,22 +6923,46 @@ command_substitute (string, quoted, flags)
 {
   pid_t pid, old_pid, old_pipeline_pgrp, old_async_pid;
   char *istring, *s;
-  int result, fildes[2], function_value, pflags, rc, tflag;
+  int result, fildes[2], function_value, pflags, rc, tflag, fork_flags;
   WORD_DESC *ret;
+  sigset_t set, oset;
 
   istring = (char *)NULL;
 
   /* Don't fork () if there is no need to.  In the case of no command to
      run, just return NULL. */
-#if 1
   for (s = string; s && *s && (shellblank (*s) || *s == '\n'); s++)
     ;
   if (s == 0 || *s == 0)
     return ((WORD_DESC *)NULL);
-#else
-  if (!string || !*string || (string[0] == '\n' && !string[1]))
-    return ((WORD_DESC *)NULL);
-#endif
+
+  if (*s == '<' && (s[1] != '<' && s[1] != '>' && s[1] != '&'))
+    {
+      COMMAND *cmd;
+
+      cmd = parse_string_to_command (string, 0);       /* XXX - flags */
+      if (cmd && can_optimize_cat_file (cmd))
+       {
+         tflag = 0;
+         istring = optimize_cat_file (cmd->value.Simple->redirects, quoted, flags, &tflag);
+         if (istring == &expand_param_error)
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+             istring = 0;
+           }
+         else
+           last_command_exit_value = EXECUTION_SUCCESS;        /* compat */
+         last_command_subst_pid = dollar_dollar_pid;
+
+         dispose_command (cmd);          
+         ret = alloc_word_desc ();
+         ret->word = istring;
+         ret->flags = tflag;
+
+         return ret;
+       }
+      dispose_command (cmd);
+    }
 
   if (wordexp_only && read_but_dont_execute)
     {
@@ -6204,14 +6996,17 @@ command_substitute (string, quoted, flags)
 
 #if defined (JOB_CONTROL)
   old_pipeline_pgrp = pipeline_pgrp;
-  /* Don't reset the pipeline pgrp if we're already a subshell in a pipeline. */
-  if ((subshell_environment & SUBSHELL_PIPE) == 0)
+  /* Don't reset the pipeline pgrp if we're already a subshell in a pipeline or
+     we've already forked to run a disk command (and are expanding redirections,
+     for example). */
+  if ((subshell_environment & (SUBSHELL_FORK|SUBSHELL_PIPE)) == 0)
     pipeline_pgrp = shell_pgrp;
   cleanup_the_pipeline ();
 #endif /* JOB_CONTROL */
 
   old_async_pid = last_asynchronous_pid;
-  pid = make_child ((char *)NULL, subshell_environment&SUBSHELL_ASYNC);
+  fork_flags = (subshell_environment&SUBSHELL_ASYNC) ? FORK_ASYNC : 0;
+  pid = make_child ((char *)NULL, fork_flags|FORK_NOTERM);
   last_asynchronous_pid = old_async_pid;
 
   if (pid == 0)
@@ -6227,6 +7022,7 @@ command_substitute (string, quoted, flags)
        }       
       QUIT;    /* catch any interrupts we got post-fork */
       subshell_environment |= SUBSHELL_RESETTRAP;
+      subshell_environment &= ~SUBSHELL_IGNTRAP;
     }
 
 #if defined (JOB_CONTROL)
@@ -6257,6 +7053,13 @@ command_substitute (string, quoted, flags)
       /* The currently executing shell is not interactive. */
       interactive = 0;
 
+#if defined (JOB_CONTROL)
+      /* Invariant: in child processes started to run command substitutions,
+        pipeline_pgrp == shell_pgrp. Other parts of the shell assume this. */
+      if (pipeline_pgrp > 0 && pipeline_pgrp != shell_pgrp)
+       shell_pgrp = pipeline_pgrp;
+#endif
+
       set_sigint_handler ();   /* XXX */
 
       free_pushed_string_input ();
@@ -6323,6 +7126,15 @@ command_substitute (string, quoted, flags)
 
       remove_quoted_escapes (string);
 
+      /* We want to expand aliases on this pass if we are not in posix mode
+        for backwards compatibility. parse_and_execute() takes care of
+        setting expand_aliases back to the global value when executing the
+        parsed string. We only do this for $(...) command substitution,
+        since that is what parse_comsub handles; `` comsubs are processed
+        using parse.y:parse_matched_pair(). */
+      if (expand_aliases && (flags & PF_BACKQUOTE) == 0)
+        expand_aliases = posixly_correct == 0;
+
       startup_state = 2;       /* see if we can avoid a fork */
       parse_and_execute_level = 0;
 
@@ -6340,7 +7152,7 @@ command_substitute (string, quoted, flags)
 
       if (result == ERREXIT)
        rc = last_command_exit_value;
-      else if (result == EXITPROG)
+      else if (result == EXITPROG || result == EXITBLTIN)
        rc = last_command_exit_value;
       else if (result)
        rc = EXECUTION_FAILURE;
@@ -6362,19 +7174,31 @@ command_substitute (string, quoted, flags)
     }
   else
     {
+      int dummyfd;
+
 #if defined (JOB_CONTROL) && defined (PGRP_PIPE)
       close_pgrp_pipe ();
 #endif /* JOB_CONTROL && PGRP_PIPE */
 
       close (fildes[1]);
 
+      begin_unwind_frame ("read-comsub");
+      dummyfd = fildes[0];
+      add_unwind_protect (close, dummyfd);
+
+      /* Block SIGINT while we're reading from the pipe. If the child
+        process gets a SIGINT, it will either handle it or die, and the
+        read will return. */
+      BLOCK_SIGNAL (SIGINT, set, oset);
       tflag = 0;
       istring = read_comsub (fildes[0], quoted, flags, &tflag);
 
       close (fildes[0]);
+      discard_unwind_frame ("read-comsub");
+      UNBLOCK_SIGNAL (oset);
 
       current_command_subst_pid = pid;
-      last_command_exit_value = wait_for (pid);
+      last_command_exit_value = wait_for (pid, JWAIT_NOTERM);
       last_command_subst_pid = pid;
       last_made_pid = old_pid;
 
@@ -6384,20 +7208,6 @@ command_substitute (string, quoted, flags)
         SIGINT to ourselves.  This will break out of loops, for instance. */
       if (last_command_exit_value == (128 + SIGINT) && last_command_exit_signal == SIGINT)
        kill (getpid (), SIGINT);
-
-      /* 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
-        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.  We duplicate
-        the conditions that wait_for tests to make sure we only give
-        the terminal back to pipeline_pgrp under the conditions that wait_for
-        gave it to shell_pgrp.  If wait_for doesn't mess with the terminal
-        pgrp, we should not either. */
-      if (interactive && pipeline_pgrp != (pid_t)0 && running_in_background == 0 &&
-          (subshell_environment & (SUBSHELL_ASYNC|SUBSHELL_PIPE)) == 0)
-       give_terminal_to (pipeline_pgrp, 0);
 #endif /* JOB_CONTROL */
 
       ret = alloc_word_desc ();
@@ -6436,7 +7246,7 @@ array_length_reference (s)
     {
       c = *--t;
       *t = '\0';
-      last_command_exit_value = EXECUTION_FAILURE;
+      set_exit_status (EXECUTION_FAILURE);
       err_unboundvar (s);
       *t = c;
       return (-1);
@@ -6464,7 +7274,7 @@ array_length_reference (s)
   if (assoc_p (var))
     {
       t[len - 1] = '\0';
-      akey = expand_assignment_string_to_string (t, 0);        /* [ */
+      akey = expand_subscript_string (t, 0);   /* [ */
       t[len - 1] = RBRACK;
       if (akey == 0 || *akey == 0)
        {
@@ -6517,9 +7327,9 @@ valid_brace_expansion_word (name, var_is_special)
 }
 
 static int
-chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at)
+chk_atstar (name, quoted, pflags, quoted_dollar_atp, contains_dollar_at)
      char *name;
-     int quoted;
+     int quoted, pflags;
      int *quoted_dollar_atp, *contains_dollar_at;
 {
   char *temp1;
@@ -6544,7 +7354,9 @@ chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at)
     }
   else if (name[0] == '*' && name[1] == '\0' && quoted == 0)
     {
-      if (contains_dollar_at)
+      /* Need more checks here that parallel what string_list_pos_params and
+        param_expand do. Check expand_no_split_dollar_star and ??? */
+      if (contains_dollar_at && expand_no_split_dollar_star == 0)
        *contains_dollar_at = 1;
       return 1;
     }
@@ -6581,24 +7393,31 @@ 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, indp)
+parameter_brace_expand_word (name, var_is_special, quoted, pflags, estatep)
      char *name;
      int var_is_special, quoted, pflags;
-     arrayind_t *indp;
+     array_eltstate_t *estatep;
 {
   WORD_DESC *ret;
   char *temp, *tt;
   intmax_t arg_index;
   SHELL_VAR *var;
-  int atype, rflags;
-  arrayind_t ind;
+  int rflags;
+  array_eltstate_t es;
 
   ret = 0;
   temp = 0;
   rflags = 0;
 
-  if (indp)
-    *indp = INTMAX_MIN;
+#if defined (ARRAY_VARS)
+  if (estatep)
+    es = *estatep;     /* structure copy */
+  else
+    {
+      init_eltstate (&es);
+      es.ind = INTMAX_MIN;
+    }
+#endif
 
   /* Handle multiple digit arguments, as in ${11}. */  
   if (legal_number (name, &arg_index))
@@ -6621,6 +7440,13 @@ parameter_brace_expand_word (name, var_is_special, quoted, pflags, indp)
 
       ret = param_expand (tt, &sindex, quoted, (int *)NULL, (int *)NULL,
                          (int *)NULL, (int *)NULL, pflags);
+
+      /* Make sure we note that we saw a quoted null string and pass the flag back
+        to the caller in addition to the value. */
+      if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) && STR_DOLLAR_AT_STAR (name) &&
+         ret && ret->word && QUOTED_NULL (ret->word))
+       ret->flags |= W_HASQUOTEDNULL;
+
       free (tt);
     }
 #if defined (ARRAY_VARS)
@@ -6635,12 +7461,12 @@ expand_arrayref:
            {
              /* Only treat as double quoted if array variable */
              if (var && (array_p (var) || assoc_p (var)))
-               temp = array_value (name, quoted|Q_DOUBLE_QUOTES, AV_ASSIGNRHS, &atype, &ind);
+               temp = array_value (name, quoted|Q_DOUBLE_QUOTES, AV_ASSIGNRHS, &es);
              else              
-               temp = array_value (name, quoted, 0, &atype, &ind);
+               temp = array_value (name, quoted, 0, &es);
            }
          else
-           temp = array_value (name, quoted, 0, &atype, &ind);
+           temp = array_value (name, quoted, 0, &es);
        }
       /* Posix interp 888 */
       else if (pflags & PF_NOSPLIT2)
@@ -6651,31 +7477,38 @@ expand_arrayref:
 #else
          if (tt[0] == '@' && tt[1] == RBRACK && var && quoted == 0 && ifs_is_set && ifs_is_null == 0 && ifs_firstc != ' ')
 #endif
-           temp = array_value (name, Q_DOUBLE_QUOTES, AV_ASSIGNRHS, &atype, &ind);
+           temp = array_value (name, Q_DOUBLE_QUOTES, AV_ASSIGNRHS, &es);
          else if (tt[0] == '@' && tt[1] == RBRACK)
-           temp = array_value (name, quoted, 0, &atype, &ind);
+           temp = array_value (name, quoted, 0, &es);
          else if (tt[0] == '*' && tt[1] == RBRACK && expand_no_split_dollar_star && ifs_is_null)
-           temp = array_value (name, Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT, 0, &atype, &ind);
+           temp = array_value (name, Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT, 0, &es);
          else if (tt[0] == '*' && tt[1] == RBRACK)
-           temp = array_value (name, quoted, 0, &atype, &ind);
+           temp = array_value (name, quoted, 0, &es);
          else
-           temp = array_value (name, quoted, 0, &atype, &ind);
+           temp = array_value (name, quoted, 0, &es);
        }                 
       else if (tt[0] == '*' && tt[1] == RBRACK && expand_no_split_dollar_star && ifs_is_null)
-       temp = array_value (name, Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT, 0, &atype, &ind);
+       temp = array_value (name, Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT, 0, &es);
       else
-       temp = array_value (name, quoted, 0, &atype, &ind);
-      if (atype == 0 && temp)
+       temp = array_value (name, quoted, 0, &es);
+      if (es.subtype == 0 && 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)))
+       }
+      /* Note that array[*] and array[@] expanded to a quoted null string by
+        returning the W_HASQUOTEDNULL flag to the caller in addition to TEMP. */
+      else if (es.subtype == 1 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+       rflags |= W_HASQUOTEDNULL;
+      else if (es.subtype == 2 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
        rflags |= W_HASQUOTEDNULL;
+
+      if (estatep)
+       *estatep = es;  /* structure copy */
+      else
+       flush_eltstate (&es);
     }
 #endif
   else if (var = find_variable (name))
@@ -6683,7 +7516,15 @@ expand_arrayref:
       if (var_isset (var) && invisible_p (var) == 0)
        {
 #if defined (ARRAY_VARS)
-         if (assoc_p (var))
+         /* We avoid a memory leak by saving TT as the memory allocated by
+            assoc_to_string or array_to_string and leaving it 0 otherwise,
+            then freeing TT after quoting temp. */
+         tt = (char *)NULL;
+         if ((pflags & PF_ALLINDS) && assoc_p (var))
+           tt = temp = assoc_empty (assoc_cell (var)) ? (char *)NULL : assoc_to_string (assoc_cell (var), " ", quoted);
+         else if ((pflags & PF_ALLINDS) && array_p (var))
+           tt = temp = array_empty (array_cell (var)) ? (char *)NULL : array_to_string (array_cell (var), " ", quoted);
+         else if (assoc_p (var))
            temp = assoc_reference (assoc_cell (var), "0");
          else if (array_p (var))
            temp = array_reference (array_cell (var), 0);
@@ -6698,6 +7539,7 @@ expand_arrayref:
                      ? quote_string (temp)
                      : ((pflags & PF_ASSIGNRHS) ? quote_rhs (temp)
                                                 : quote_escapes (temp));
+         FREE (tt);
        }
       else
        temp = (char *)NULL;
@@ -6717,7 +7559,7 @@ expand_arrayref:
       /* 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;
+         set_exit_status (EXECUTION_FAILURE);
          report_error (_("%s: invalid variable name for name reference"), temp);
          temp = &expand_param_error;
         }
@@ -6783,9 +7625,9 @@ parameter_brace_find_indir (name, var_is_special, quoted, find_nameref)
 /* 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)
+parameter_brace_expand_indir (name, var_is_special, quoted, pflags, quoted_dollar_atp, contains_dollar_at)
      char *name;
-     int var_is_special, quoted;
+     int var_is_special, quoted, pflags;
      int *quoted_dollar_atp, *contains_dollar_at;
 {
   char *t;
@@ -6823,7 +7665,7 @@ parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, c
       
   t = parameter_brace_find_indir (name, var_is_special, quoted, 0);
 
-  chk_atstar (t, quoted, quoted_dollar_atp, contains_dollar_at);
+  chk_atstar (t, quoted, pflags, quoted_dollar_atp, contains_dollar_at);
 
 #if defined (ARRAY_VARS)
   /* Array references to unset variables are also an error */
@@ -6856,7 +7698,7 @@ parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, c
       return (w);
     }
        
-  w = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted, 0, 0);
+  w = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted, pflags, 0);
   free (t);
 
   return w;
@@ -6872,10 +7714,11 @@ parameter_brace_expand_rhs (name, value, op, quoted, pflags, qdollaratp, hasdoll
      int op, quoted, pflags, *qdollaratp, *hasdollarat;
 {
   WORD_DESC *w;
-  WORD_LIST *l;
-  char *t, *t1, *temp, *vname;
-  int l_hasdollat, sindex;
+  WORD_LIST *l, *tl;
+  char *t, *t1, *temp, *vname, *newval;
+  int l_hasdollat, sindex, arrayref;
   SHELL_VAR *v;
+  array_eltstate_t es;
 
 /*itrace("parameter_brace_expand_rhs: %s:%s pflags = %d", name, value, pflags);*/
   /* If the entire expression is between double quotes, we want to treat
@@ -6897,6 +7740,23 @@ parameter_brace_expand_rhs (name, value, op, quoted, pflags, qdollaratp, hasdoll
     *hasdollarat = l_hasdollat || (l && l->next);
   if (temp != value)
     free (temp);
+
+  /* list_string takes multiple CTLNULs and turns them into an empty word
+     with W_SAWQUOTEDNULL set. Turn it back into a single CTLNUL for the
+     rest of this function and the caller. */
+  for (tl = l; tl; tl = tl->next)
+    {
+      if (tl->word && (tl->word->word == 0 || tl->word->word[0] == 0) &&
+           (tl->word->flags | W_SAWQUOTEDNULL))
+       {
+         t = make_quoted_char ('\0');
+         FREE (tl->word->word);
+         tl->word->word = t;
+         tl->word->flags |= W_QUOTED|W_HASQUOTEDNULL;
+         tl->word->flags &= ~W_SAWQUOTEDNULL;
+       }
+    }
+
   if (l)
     {
       /* If l->next is not null, we know that TEMP contained "$@", since that
@@ -7003,9 +7863,15 @@ parameter_brace_expand_rhs (name, value, op, quoted, pflags, qdollaratp, hasdoll
        }
     }
     
+  arrayref = 0;
 #if defined (ARRAY_VARS)
   if (valid_array_reference (vname, 0))
-    v = assign_array_element (vname, t1, 0);
+    {
+      init_eltstate (&es);
+      v = assign_array_element (vname, t1, ASS_ALLOWALLSUB, &es);
+      arrayref = 1;
+      newval = es.value;
+    }
   else
 #endif /* ARRAY_VARS */
   v = bind_variable (vname, t1, 0);
@@ -7028,6 +7894,23 @@ parameter_brace_expand_rhs (name, value, op, quoted, pflags, qdollaratp, hasdoll
 
   stupidly_hack_special_variables (vname);
 
+  /* "In all cases, the final value of parameter shall be substituted." */
+  if (shell_compatibility_level > 51)
+    {
+      FREE (t1);
+#if defined (ARRAY_VARS)
+      if (arrayref)
+       {
+         t1 = newval;
+         flush_eltstate (&es);
+       }
+      else
+        t1 = get_variable_value (v);
+#else
+      t1 = value_cell (v);
+#endif
+    }
+
   if (vname != name)
     free (vname);
 
@@ -7037,7 +7920,16 @@ parameter_brace_expand_rhs (name, value, op, quoted, pflags, qdollaratp, hasdoll
      splitting, we want to quote the value we return appropriately, like
      the other expansions this function handles. */
   w->word = (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) ? quote_string (t1) : quote_escapes (t1);
-  free (t1);
+  /* If we have something that's non-null, but not a quoted null string,
+     and we're not going to be performing word splitting (we know we're not
+     because the operator is `='), we can forget we saw a quoted null. */
+  if (w->word && w->word[0] && QUOTED_NULL (w->word) == 0)
+    w->flags &= ~W_SAWQUOTEDNULL;
+
+  /* If we convert a null string into a quoted null, make sure the caller
+     knows it. */
+  if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) && QUOTED_NULL (w->word))
+    w->flags |= W_HASQUOTEDNULL;
 
   return w;
 }
@@ -7054,7 +7946,7 @@ parameter_brace_expand_error (name, value, check_null)
   WORD_LIST *l;
   char *temp;
 
-  last_command_exit_value = EXECUTION_FAILURE; /* ensure it's non-zero */
+  set_exit_status (EXECUTION_FAILURE); /* ensure it's non-zero */
   if (value && *value)
     {
       l = expand_string (value, 0);
@@ -7098,9 +7990,9 @@ parameter_brace_expand_length (name)
   char *t, *newname;
   intmax_t number, arg_index;
   WORD_LIST *list;
-#if defined (ARRAY_VARS)
   SHELL_VAR *var;
-#endif
+
+  var = (SHELL_VAR *)NULL;
 
   if (name[1] == '\0')                 /* ${#} */
     number = number_of_args ();
@@ -7161,6 +8053,15 @@ parameter_brace_expand_length (name)
          number = MB_STRLEN (t);
        }
 #endif
+      /* Fast path for the common case of taking the length of a non-dynamic
+        scalar variable value. */
+      else if ((var || (var = find_variable (name + 1))) &&
+               invisible_p (var) == 0 &&
+               array_p (var) == 0 && assoc_p (var) == 0 &&
+               var->dynamic_value == 0)
+       number = value_cell (var) ? MB_STRLEN (value_cell (var)) : 0;
+      else if (var == 0 && unbound_vars_is_error == 0)
+       number = 0;
       else                             /* ${#PS1} */
        {
          newname = savestring (name);
@@ -7217,7 +8118,7 @@ verify_substring_values (v, value, substr, vtype, e1p, e2p)
 {
   char *t, *temp1, *temp2;
   arrayind_t len;
-  int expok;
+  int expok, eflag;
 #if defined (ARRAY_VARS)
  ARRAY *a;
  HASH_TABLE *h;
@@ -7230,8 +8131,10 @@ verify_substring_values (v, value, substr, vtype, e1p, e2p)
   else
     t = (char *)0;
 
-  temp1 = expand_arith_string (substr, Q_DOUBLE_QUOTES);
-  *e1p = evalexp (temp1, 0, &expok);           /* XXX - EXP_EXPANDED? */
+  temp1 = expand_arith_string (substr, Q_DOUBLE_QUOTES|Q_ARITH);
+  eflag = (shell_compatibility_level > 51) ? 0 : EXP_EXPANDED;
+
+  *e1p = evalexp (temp1, eflag, &expok);
   free (temp1);
   if (expok == 0)
     return (0);
@@ -7286,10 +8189,10 @@ verify_substring_values (v, value, substr, vtype, e1p, e2p)
     {
       t++;
       temp2 = savestring (t);
-      temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES);
+      temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES|Q_ARITH);
       free (temp2);
       t[-1] = ':';
-      *e2p = evalexp (temp1, 0, &expok);       /* XXX - EXP_EXPANDED? */
+      *e2p = evalexp (temp1, eflag, &expok);
       free (temp1);
       if (expok == 0)
        return (0);
@@ -7298,8 +8201,7 @@ verify_substring_values (v, value, substr, vtype, e1p, e2p)
         from end of positional parameters? */
 #if 1
       if ((vtype == VT_ARRAYVAR || vtype == VT_POSPARMS) && *e2p < 0)
-#else
-      /* XXX - TAG: bash-5.1 */
+#else /* XXX - postponed; this isn't really a valuable feature */
       if (vtype == VT_ARRAYVAR && *e2p < 0)
 #endif
        {
@@ -7339,15 +8241,15 @@ verify_substring_values (v, value, substr, vtype, e1p, e2p)
    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
+   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, ind, quoted, flags, varp, valp)
+get_var_and_type (varname, value, estatep, quoted, flags, varp, valp)
      char *varname, *value;
-     arrayind_t ind;
+     array_eltstate_t *estatep;
      int quoted, flags;
      SHELL_VAR **varp;
      char **valp;
@@ -7355,7 +8257,6 @@ get_var_and_type (varname, value, ind, quoted, flags, varp, valp)
   int vtype, want_indir;
   char *temp, *vname;
   SHELL_VAR *v;
-  arrayind_t lind;
 
   want_indir = *varname == '!' &&
     (legal_variable_starter ((unsigned char)varname[1]) || DIGIT (varname[1])
@@ -7385,8 +8286,11 @@ get_var_and_type (varname, value, ind, quoted, flags, varp, valp)
     {
       v = array_variable_part (vname, 0, &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;
+        the caller will set ESTATEP->IND to that index and pass AV_USEIND in
+        FLAGS. */
+      if (estatep && (flags & AV_USEIND) == 0)
+       estatep->ind = INTMAX_MIN;
+
       if (v && invisible_p (v))
        {
          vtype = VT_ARRAYMEMBER;
@@ -7406,7 +8310,7 @@ get_var_and_type (varname, value, ind, quoted, flags, varp, valp)
          else
            {
              vtype = VT_ARRAYMEMBER;
-             *valp = array_value (vname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
+             *valp = array_value (vname, Q_DOUBLE_QUOTES, flags, estatep);
            }
          *varp = v;
        }
@@ -7415,7 +8319,7 @@ get_var_and_type (varname, value, ind, quoted, flags, varp, valp)
          vtype = VT_VARIABLE;
          *varp = v;
          if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
-           *valp = value ? dequote_string (value) : savestring ("");
+           *valp = value ? dequote_string (value) : (char *)NULL;
          else
            *valp = value ? dequote_escapes (value) : (char *)NULL;
        }
@@ -7423,7 +8327,7 @@ get_var_and_type (varname, value, ind, quoted, flags, varp, valp)
        {
          vtype = VT_ARRAYMEMBER;
          *varp = v;
-         *valp = array_value (vname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
+         *valp = array_value (vname, Q_DOUBLE_QUOTES, flags, estatep);
        }
     }
   else if ((v = find_variable (vname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v)))
@@ -7467,10 +8371,15 @@ string_var_assignment (v, s)
   char flags[MAX_ATTRIBUTES], *ret, *val;
   int i;
 
-  val = sh_quote_reusable (s, 0);
+  val = (v && (invisible_p (v) || var_isset (v) == 0)) ? (char *)NULL : sh_quote_reusable (s, 0);
   i = var_attribute_string (v, 0, flags);
-  ret = (char *)xmalloc (i + strlen (val) + strlen (v->name) + 16 + MAX_ATTRIBUTES);
-  if (i > 0)
+  if (i == 0 && val == 0)
+    return (char *)NULL;
+
+  ret = (char *)xmalloc (i + STRLEN (val) + strlen (v->name) + 16 + MAX_ATTRIBUTES);
+  if (i > 0 && val == 0)
+    sprintf (ret, "declare -%s %s", flags, v->name);
+  else if (i > 0)
     sprintf (ret, "declare -%s %s=%s", flags, v->name, val);
   else
     sprintf (ret, "%s=%s", v->name, val);
@@ -7480,18 +8389,25 @@ string_var_assignment (v, s)
 
 #if defined (ARRAY_VARS)
 static char *
-array_var_assignment (v, itype, quoted)
+array_var_assignment (v, itype, quoted, atype)
      SHELL_VAR *v;
-     int itype, quoted;
+     int itype, quoted, atype;
 {
   char *ret, *val, flags[MAX_ATTRIBUTES];
   int i;
 
   if (v == 0)
     return (char *)NULL;
-  val = array_p (v) ? array_to_assign (array_cell (v), 0)
-                   : assoc_to_assign (assoc_cell (v), 0);
-  if (val == 0)
+  if (atype == 2)
+    val = array_p (v) ? array_to_kvpair (array_cell (v), 0)
+                     : assoc_to_kvpair (assoc_cell (v), 0);
+  else
+    val = array_p (v) ? array_to_assign (array_cell (v), 0)
+                     : assoc_to_assign (assoc_cell (v), 0);
+
+  if (val == 0 && (invisible_p (v) || var_isset (v) == 0))
+    ;  /* placeholder */
+  else if (val == 0)
     {
       val = (char *)xmalloc (3);
       val[0] = LPAREN;
@@ -7504,9 +8420,16 @@ array_var_assignment (v, itype, quoted)
       free (val);
       val = ret;
     }
+
+  if (atype == 2)
+    return val;
+
   i = var_attribute_string (v, 0, flags);
-  ret = (char *)xmalloc (i + strlen (val) + strlen (v->name) + 16);
-  sprintf (ret, "declare -%s %s=%s", flags, v->name, val);
+  ret = (char *)xmalloc (i + STRLEN (val) + strlen (v->name) + 16);
+  if (val)
+    sprintf (ret, "declare -%s %s=%s", flags, v->name, val);
+  else
+    sprintf (ret, "declare -%s %s", flags, v->name);
   free (val);
   return ret;
 }
@@ -7538,7 +8461,9 @@ string_transform (xc, v, s)
   char *ret, flags[MAX_ATTRIBUTES], *t;
   int i;
 
-  if (((xc == 'A' || xc == 'a') && v == 0) || (xc != 'a' && s == 0))
+  if (((xc == 'A' || xc == 'a') && v == 0))
+    return (char *)NULL;
+  else if (xc != 'a' && xc != 'A' && s == 0)
     return (char *)NULL;
 
   switch (xc)
@@ -7551,6 +8476,10 @@ string_transform (xc, v, s)
       case 'A':
        ret = string_var_assignment (v, s);
        break;
+      case 'K':
+      case 'k':
+       ret = sh_quote_reusable (s, 0);
+       break;
       /* Transformations that modify the variable's value */
       case 'E':
        t = ansiexpand (s, 0, strlen (s), (int *)0);
@@ -7563,6 +8492,15 @@ string_transform (xc, v, s)
       case 'Q':
        ret = sh_quote_reusable (s, 0);
        break;
+      case 'U':
+       ret = sh_modcase (s, 0, CASE_UPPER);
+       break;
+      case 'u':
+       ret = sh_modcase (s, 0, CASE_UPFIRST);  /* capitalize */
+       break;
+      case 'L':
+       ret = sh_modcase (s, 0, CASE_LOWER);
+       break;
       default:
        ret = (char *)NULL;
        break;
@@ -7597,7 +8535,7 @@ list_transform (xc, v, list, itype, quoted)
   if (itype == '*' && expand_no_split_dollar_star && ifs_is_null)
     qflags |= Q_DOUBLE_QUOTES;         /* Posix interp 888 */
 
-  tword = string_list_pos_params (itype, l, qflags);
+  tword = string_list_pos_params (itype, l, qflags, 0);
   dispose_words (l);
 
   return (tword);
@@ -7625,34 +8563,58 @@ parameter_list_transform (xc, itype, quoted)
 
 #if defined (ARRAY_VARS)
 static char *
-array_transform (xc, var, varname, quoted)
+array_transform (xc, var, starsub, quoted)
      int xc;
      SHELL_VAR *var;
-     char *varname;    /* so we can figure out how it's indexed */
+     int starsub;      /* so we can figure out how it's indexed */
      int quoted;
 {
   ARRAY *a;
   HASH_TABLE *h;
-  int itype;
+  int itype, qflags;
   char *ret;
   WORD_LIST *list;
   SHELL_VAR *v;
 
-  /* compute itype from varname here */
-  v = array_variable_part (varname, 0, &ret, 0);
-
-  /* XXX */
-  if (v && invisible_p (v))
-    return ((char *)NULL);
+  v = var;     /* XXX - for now */
 
-  itype = ret[0];
+  itype = starsub ? '*' : '@';
 
   if (xc == 'A')
-    return (array_var_assignment (v, itype, quoted));
+    return (array_var_assignment (v, itype, quoted, 1));
+  else if (xc == 'K')
+    return (array_var_assignment (v, itype, quoted, 2));
+
+  /* special case for unset arrays and attributes */
+  if (xc == 'a' && (invisible_p (v) || var_isset (v) == 0))
+    {
+      char flags[MAX_ATTRIBUTES];
+      int i;
+
+      i = var_attribute_string (v, 0, flags);
+      return ((i > 0) ? savestring (flags) : (char *)NULL);
+    }
 
   a = (v && array_p (v)) ? array_cell (v) : 0;
   h = (v && assoc_p (v)) ? assoc_cell (v) : 0;
-  
+
+  /* XXX - for now */
+  if (xc == 'k')
+    {
+      if (v == 0)
+       return ((char *)NULL);
+      list = array_p (v) ? array_to_kvpair_list (a) : assoc_to_kvpair_list (h);
+      qflags = quoted;
+      /* If we are expanding in a context where word splitting will not be
+        performed, treat as quoted.  This changes how $* will be expanded. */
+      if (itype == '*' && expand_no_split_dollar_star && ifs_is_null)
+       qflags |= Q_DOUBLE_QUOTES;              /* Posix interp 888 */
+
+      ret = string_list_pos_params (itype, list, qflags, 0);
+      dispose_words (list);
+      return ret;
+    }
+
   list = a ? array_to_word_list (a) : (h ? assoc_to_word_list (h) : 0);
   if (list == 0)
    return ((char *)NULL);
@@ -7663,14 +8625,40 @@ array_transform (xc, var, varname, quoted)
 }
 #endif /* ARRAY_VARS */
 
+static int
+valid_parameter_transform (xform)
+     char *xform;
+{
+  if (xform[1])
+    return 0;
+
+  /* check for valid values of xform[0] */
+  switch (xform[0])
+    {
+    case 'a':          /* expand to a string with just attributes */
+    case 'A':          /* expand as an assignment statement with attributes */
+    case 'K':          /* expand assoc array to list of key/value pairs */
+    case 'k':          /* XXX - for now */
+    case 'E':          /* expand like $'...' */
+    case 'P':          /* expand like prompt string */
+    case 'Q':          /* quote reusably */
+    case 'U':          /* transform to uppercase */
+    case 'u':          /* transform by capitalizing */
+    case 'L':          /* transform to lowercase */
+      return 1;
+    default:
+      return 0;
+    }
+}
+      
 static char *
-parameter_brace_transform (varname, value, ind, xform, rtype, quoted, pflags, flags)
+parameter_brace_transform (varname, value, estatep, xform, rtype, quoted, pflags, flags)
      char *varname, *value;
-     int ind;
+     array_eltstate_t *estatep;
      char *xform;
      int rtype, quoted, pflags, flags;
 {
-  int vtype, xc;
+  int vtype, xc, starsub;
   char *temp1, *val, *oname;
   SHELL_VAR *v;
 
@@ -7681,34 +8669,31 @@ parameter_brace_transform (varname, value, ind, xform, rtype, quoted, pflags, fl
   oname = this_command_name;
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
+  vtype = get_var_and_type (varname, value, estatep, quoted, flags, &v, &val);
   if (vtype == -1)
     {
       this_command_name = oname;
       return ((char *)NULL);
     }
 
-  /* check for valid values of xc */
-  switch (xc)
+  if (xform[0] == 0 || valid_parameter_transform (xform) == 0)
     {
-    case 'a':          /* expand to a string with just attributes */
-    case 'A':          /* expand as an assignment statement with attributes */
-    case 'E':          /* expand like $'...' */
-    case 'P':          /* expand like prompt string */
-    case 'Q':          /* quote reusably */
-      break;
-    default:
       this_command_name = oname;
-      return &expand_param_error;
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      return (interactive_shell ? &expand_param_error : &expand_param_fatal);
     }
 
+  starsub = vtype & VT_STARSUB;
+  vtype &= ~VT_STARSUB;
+
   /* If we are asked to display the attributes of an unset variable, V will
      be NULL after the call to get_var_and_type. Double-check here. */
-  if (xc == 'a' && vtype == VT_VARIABLE && varname && v == 0)
+  if ((xc == 'a' || xc == 'A') && vtype == VT_VARIABLE && varname && v == 0)
     v = find_variable (varname);
 
   temp1 = (char *)NULL;                /* shut up gcc */
-  switch (vtype & ~VT_STARSUB)
+  switch (vtype)
     {
     case VT_VARIABLE:
     case VT_ARRAYMEMBER:
@@ -7726,7 +8711,7 @@ parameter_brace_transform (varname, value, ind, xform, rtype, quoted, pflags, fl
       break;
 #if defined (ARRAY_VARS)
     case VT_ARRAYVAR:
-      temp1 = array_transform (xc, v, varname, quoted);
+      temp1 = array_transform (xc, v, starsub, quoted);
       if (temp1 && quoted == 0 && ifs_is_null)
        {
                /* Posix interp 888 */
@@ -7800,9 +8785,9 @@ mb_substring (string, s, e)
    VARNAME.  If VARNAME is an array variable, use the array elements. */
 
 static char *
-parameter_brace_substring (varname, value, ind, substr, quoted, pflags, flags)
+parameter_brace_substring (varname, value, estatep, substr, quoted, pflags, flags)
      char *varname, *value;
-     int ind;
+     array_eltstate_t *estatep;
      char *substr;
      int quoted, pflags, flags;
 {
@@ -7817,7 +8802,7 @@ parameter_brace_substring (varname, value, ind, substr, quoted, pflags, flags)
   oname = this_command_name;
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
+  vtype = get_var_and_type (varname, value, estatep, quoted, flags, &v, &val);
   if (vtype == -1)
     {
       this_command_name = oname;
@@ -7858,20 +8843,20 @@ parameter_brace_substring (varname, value, ind, substr, quoted, pflags, flags)
     case VT_POSPARMS:
     case VT_ARRAYVAR:
       if (vtype == VT_POSPARMS)
-       tt = pos_params (varname, e1, e2, quoted);
+       tt = pos_params (varname, e1, e2, quoted, pflags);
 #if defined (ARRAY_VARS)
         /* assoc_subrange and array_subrange both call string_list_pos_params,
           so we can treat this case just like VT_POSPARAMS. */
       else if (assoc_p (v))
        /* we convert to list and take first e2 elements starting at e1th
           element -- officially undefined for now */   
-       tt = assoc_subrange (assoc_cell (v), e1, e2, starsub, quoted);
+       tt = assoc_subrange (assoc_cell (v), e1, e2, starsub, quoted, pflags);
       else
        /* We want E2 to be the number of elements desired (arrays can be
           sparse, so verify_substring_values just returns the numbers
           specified and we rely on array_subrange to understand how to
           deal with them). */
-       tt = array_subrange (array_cell (v), e1, e2, starsub, quoted);
+       tt = array_subrange (array_cell (v), e1, e2, starsub, quoted, pflags);
 #endif
       /* We want to leave this alone in every case where pos_params/
         string_list_pos_params quotes the list members */
@@ -7879,6 +8864,10 @@ parameter_brace_substring (varname, value, ind, substr, quoted, pflags, flags)
        {
          temp = tt;    /* Posix interp 888 */
        }
+      else if (tt && quoted == 0 && (pflags & PF_ASSIGNRHS))
+       {
+         temp = tt;    /* Posix interp 888 */
+       }
       else if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0)
        {
          temp = tt ? quote_escapes (tt) : (char *)NULL;
@@ -7901,43 +8890,54 @@ parameter_brace_substring (varname, value, ind, substr, quoted, pflags, flags)
 /*                                                             */
 /****************************************************************/
 
-#ifdef INCLUDE_UNUSED
 static int
 shouldexp_replacement (s)
      char *s;
 {
-  register char *p;
+  size_t slen;
+  int sindex, c;
+  DECLARE_MBSTATE;
 
-  for (p = s; p && *p; p++)
+  sindex = 0;
+  slen = STRLEN (s);
+  while (c = s[sindex])
     {
-      if (*p == '\\')
-       p++;
-      else if (*p == '&')
+      if (c == '\\')
+       {
+         sindex++;
+         if (s[sindex] == 0)
+           return 0;
+         /* We want to remove this backslash because we treat it as special
+            in this context. THIS ASSUMES THE STRING IS PROCESSED BY
+            strcreplace() OR EQUIVALENT that handles removing backslashes
+            preceding the special character. */
+         if (s[sindex] == '&')
+           return 1;
+         if (s[sindex] == '\\')
+           return 1;
+       }
+      else if (c == '&')
        return 1;
+      ADVANCE_CHAR (s, slen, sindex);
     }
   return 0;
 }
-#endif
 
 char *
 pat_subst (string, pat, rep, mflags)
      char *string, *pat, *rep;
      int mflags;
 {
-  char *ret, *s, *e, *str, *rstr, *mstr;
+  char *ret, *s, *e, *str, *rstr, *mstr, *send;
   int rptr, mtype, rxpand, mlen;
   size_t rsize, l, replen, rslen;
+  DECLARE_MBSTATE;
 
   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
+  rxpand = mflags & MATCH_EXPREP;
 
   /* Special cases:
    *   1.  A null pattern with mtype == MATCH_BEG means to prefix STRING
@@ -7946,40 +8946,39 @@ pat_subst (string, pat, rep, mflags)
    *       STRING and return the result.
    *   3.  A null STRING with a matching pattern means to append REP to
    *       STRING and return the result.
-   * These don't understand or process `&' in the replacement string.
+   *
+   * These process `&' in the replacement string, like `sed' does when
+   * presented with a BRE of `^' or `$'.
    */
   if ((pat == 0 || *pat == 0) && (mtype == MATCH_BEG || mtype == MATCH_END))
     {
-      replen = STRLEN (rep);
+      rstr = (mflags & MATCH_EXPREP) ? strcreplace (rep, '&', "", 2) : rep;
+      rslen = STRLEN (rstr);
       l = STRLEN (string);
-      ret = (char *)xmalloc (replen + l + 2);
-      if (replen == 0)
+      ret = (char *)xmalloc (rslen + l + 2);
+      if (rslen == 0)
        strcpy (ret, string);
       else if (mtype == MATCH_BEG)
        {
-         strcpy (ret, rep);
-         strcpy (ret + replen, string);
+         strcpy (ret, rstr);
+         strcpy (ret + rslen, string);
        }
       else
        {
          strcpy (ret, string);
-         strcpy (ret + l, rep);
+         strcpy (ret + l, rstr);
        }
+      if (rstr != rep)
+       free (rstr);
       return (ret);
     }
   else if (*string == 0 && (match_pattern (string, pat, mtype, &s, &e) != 0))
-    {
-      replen = STRLEN (rep);
-      ret = (char *)xmalloc (replen + 1);
-      if (replen == 0)
-       ret[0] = '\0';
-      else
-       strcpy (ret, rep);
-      return (ret);
-    }
+    return (mflags & MATCH_EXPREP) ? strcreplace (rep, '&', "", 2)
+                                  : (rep ? savestring (rep) : savestring (""));
 
   ret = (char *)xmalloc (rsize = 64);
   ret[0] = '\0';
+  send = string + strlen (string);
 
   for (replen = STRLEN (rep), rptr = 0, str = string; *str;)
     {
@@ -7995,7 +8994,7 @@ pat_subst (string, pat, rep, mflags)
          for (x = 0; x < mlen; x++)
            mstr[x] = s[x];
          mstr[mlen] = '\0';
-         rstr = strcreplace (rep, '&', mstr, 0);
+         rstr = strcreplace (rep, '&', mstr, 2);
          free (mstr);
          rslen = strlen (rstr);
         }
@@ -8033,16 +9032,28 @@ pat_subst (string, pat, rep, mflags)
        {
          /* On a zero-length match, make sure we copy one character, since
             we increment one character to avoid infinite recursion. */
-         RESIZE_MALLOCED_BUFFER (ret, rptr, 1, rsize, 64);
+         char *p, *origp, *origs;
+         size_t clen;
+
+         RESIZE_MALLOCED_BUFFER (ret, rptr, locale_mb_cur_max, rsize, 64);
+#if defined (HANDLE_MULTIBYTE)
+         p = origp = ret + rptr;
+         origs = str;
+         COPY_CHAR_P (p, str, send);
+         rptr += p - origp;
+         e += str - origs;
+#else
          ret[rptr++] = *str++;
          e++;          /* avoid infinite recursion on zero-length match */
+#endif
        }
     }
 
   /* Now copy the unmatched portion of the input string */
   if (str && *str)
     {
-      RESIZE_MALLOCED_BUFFER (ret, rptr, STRLEN(str) + 1, rsize, 64);
+      l = send - str + 1;
+      RESIZE_MALLOCED_BUFFER (ret, rptr, l, rsize, 64);
       strcpy (ret + rptr, str);
     }
   else
@@ -8060,7 +9071,7 @@ pos_params_pat_subst (string, pat, rep, mflags)
   WORD_LIST *save, *params;
   WORD_DESC *w;
   char *ret;
-  int pchar, qflags;
+  int pchar, qflags, pflags;
 
   save = params = list_rest_of_args ();
   if (save == 0)
@@ -8077,13 +9088,14 @@ pos_params_pat_subst (string, pat, rep, mflags)
 
   pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
   qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
+  pflags = (mflags & MATCH_ASSIGNRHS) == MATCH_ASSIGNRHS ? PF_ASSIGNRHS : 0;
 
   /* If we are expanding in a context where word splitting will not be
      performed, treat as quoted. This changes how $* will be expanded. */
   if (pchar == '*' && (mflags & MATCH_ASSIGNRHS) && expand_no_split_dollar_star && ifs_is_null)
     qflags |= Q_DOUBLE_QUOTES;         /* Posix interp 888 */
 
-  ret = string_list_pos_params (pchar, save, qflags);
+  ret = string_list_pos_params (pchar, save, qflags, pflags);
   dispose_words (save);
 
   return (ret);
@@ -8094,9 +9106,9 @@ 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, ind, patsub, quoted, pflags, flags)
+parameter_brace_patsub (varname, value, estatep, patsub, quoted, pflags, flags)
      char *varname, *value;
-     int ind;
+     array_eltstate_t *estatep;
      char *patsub;
      int quoted, pflags, flags;
 {
@@ -8110,7 +9122,7 @@ parameter_brace_patsub (varname, value, ind, patsub, quoted, pflags, flags)
   oname = this_command_name;
   this_command_name = varname;         /* error messages */
 
-  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
+  vtype = get_var_and_type (varname, value, estatep, quoted, flags, &v, &val);
   if (vtype == -1)
     {
       this_command_name = oname;
@@ -8165,13 +9177,22 @@ parameter_brace_patsub (varname, value, ind, patsub, quoted, pflags, flags)
         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)
+      if (shell_compatibility_level > 42 && patsub_replacement == 0)
        rep = expand_string_if_necessary (rep, quoted & ~(Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT), expand_string_unsplit);
+      else if (shell_compatibility_level > 42 && patsub_replacement)
+       rep = expand_string_for_patsub (rep, quoted & ~(Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT));
       /* 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);
+
+      /* Check whether or not to replace `&' in the replacement string after
+        expanding it, since we want to treat backslashes quoting the `&'
+        consistently. */
+      if (patsub_replacement && rep && *rep && shouldexp_replacement (rep))
+       mflags |= MATCH_EXPREP;
+
     }
 
   /* ksh93 doesn't allow the match specifier to be a part of the expanded
@@ -8231,6 +9252,10 @@ parameter_brace_patsub (varname, value, ind, patsub, quoted, pflags, flags)
        {
          /* Posix interp 888 */
        }
+      else if (temp && quoted == 0 && (pflags & PF_ASSIGNRHS))
+       {
+         /* Posix interp 888 */
+       }
       else if (temp && (mflags & MATCH_QUOTED) == 0)
        {
          tt = quote_escapes (temp);
@@ -8292,7 +9317,7 @@ pos_params_modcase (string, pat, modop, mflags)
   WORD_LIST *save, *params;
   WORD_DESC *w;
   char *ret;
-  int pchar, qflags;
+  int pchar, qflags, pflags;
 
   save = params = list_rest_of_args ();
   if (save == 0)
@@ -8309,13 +9334,14 @@ pos_params_modcase (string, pat, modop, mflags)
 
   pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
   qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
+  pflags = (mflags & MATCH_ASSIGNRHS) == MATCH_ASSIGNRHS ? PF_ASSIGNRHS : 0;
 
   /* If we are expanding in a context where word splitting will not be
      performed, treat as quoted.  This changes how $* will be expanded. */
   if (pchar == '*' && (mflags & MATCH_ASSIGNRHS) && ifs_is_null)
     qflags |= Q_DOUBLE_QUOTES;         /* Posix interp 888 */
 
-  ret = string_list_pos_params (pchar, save, qflags);
+  ret = string_list_pos_params (pchar, save, qflags, pflags);
   dispose_words (save);
 
   return (ret);
@@ -8326,9 +9352,10 @@ 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, ind, modspec, patspec, quoted, pflags, flags)
+parameter_brace_casemod (varname, value, estatep, modspec, patspec, quoted, pflags, flags)
      char *varname, *value;
-     int ind, modspec;
+     array_eltstate_t *estatep;
+     int modspec;
      char *patspec;
      int quoted, pflags, flags;
 {
@@ -8342,7 +9369,7 @@ parameter_brace_casemod (varname, value, ind, modspec, patspec, quoted, pflags,
   oname = this_command_name;
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
+  vtype = get_var_and_type (varname, value, estatep, quoted, flags, &v, &val);
   if (vtype == -1)
     {
       this_command_name = oname;
@@ -8514,17 +9541,18 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
      int *indexp, quoted, pflags, *quoted_dollar_atp, *contains_dollar_at;
 {
   int check_nullness, var_is_set, var_is_null, var_is_special;
-  int want_substring, want_indir, want_patsub, want_casemod;
+  int want_substring, want_indir, want_patsub, want_casemod, want_attributes;
   char *name, *value, *temp, *temp1;
   WORD_DESC *tdesc, *ret;
-  int t_index, sindex, c, tflag, modspec, all_element_arrayref;
+  int t_index, sindex, c, tflag, modspec, local_pflags, all_element_arrayref;
   intmax_t number;
-  arrayind_t ind;
+  array_eltstate_t es;
 
   temp = temp1 = value = (char *)NULL;
   var_is_set = var_is_null = var_is_special = check_nullness = 0;
-  want_substring = want_indir = want_patsub = want_casemod = 0;
+  want_substring = want_indir = want_patsub = want_casemod = want_attributes = 0;
 
+  local_pflags = 0;
   all_element_arrayref = 0;
 
   sindex = *indexp;
@@ -8535,12 +9563,12 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
   else
 #if defined (CASEMOD_EXPANSIONS)
     /* To enable case-toggling expansions using the `~' operator character
-       change the 1 to 0. */
-#  if defined (CASEMOD_CAPCASE)
+       define CASEMOD_TOGGLECASE in config-top.h */
+#  if defined (CASEMOD_TOGGLECASE)
     name = string_extract (string, &t_index, "#%^,~:-=?+/@}", SX_VARNAME);
 #  else
     name = string_extract (string, &t_index, "#%^,:-=?+/@}", SX_VARNAME);
-#  endif /* CASEMOD_CAPCASE */
+#  endif /* CASEMOD_TOGGLECASE */
 #else
     name = string_extract (string, &t_index, "#%:-=?+/@}", SX_VARNAME);
 #endif /* CASEMOD_EXPANSIONS */
@@ -8565,7 +9593,10 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
   ret = 0;
   tflag = 0;
 
-  ind = INTMAX_MIN;
+#if defined (ARRAY_VARS)
+  init_eltstate (&es);
+#endif
+  es.ind = INTMAX_MIN; /* XXX */
 
   /* If the name really consists of a special variable, then make sure
      that we have the entire name.  We don't allow indirect references
@@ -8618,6 +9649,12 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
       want_casemod = 1;
     }
 #endif
+  else if (c == '@' && (string[sindex] == 'a' || string[sindex] == 'A') && string[sindex+1] == RBRACE)
+    {
+      /* special case because we do not want to shortcut foo as foo[0] here */
+      want_attributes = 1;
+      local_pflags |= PF_ALLINDS;
+    }
 
   /* Catch the valid and invalid brace expressions that made it through the
      tests above. */
@@ -8670,7 +9707,7 @@ 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;
+         set_exit_status (EXECUTION_FAILURE);
          err_unboundvar (name+1);
          free (name);
          return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
@@ -8750,7 +9787,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
       FREE (x);
       if (ALL_ELEMENT_SUB (x1[0]) && x1[1] == RBRACK)
        {
-         temp = array_keys (temp1, quoted);    /* handles assoc vars too */
+         temp = array_keys (temp1, quoted, pflags);    /* handles assoc vars too */
          if (x1[0] == '@')
            {
              if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
@@ -8785,7 +9822,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);
+      tdesc = parameter_brace_expand_indir (name + 1, var_is_special, quoted, pflags|local_pflags, quoted_dollar_atp, contains_dollar_at);
       if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal)
        {
          temp = (char *)NULL;
@@ -8796,9 +9833,17 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
         to return the index we're supposed to be using. */
       if (tdesc && tdesc->flags)
        tdesc->flags &= ~W_ARRAYIND;
+
+      /* If the indir expansion contains $@/$*, extend the special treatment
+        of the case of no positional parameters and `set -u' to it. */
+      if (contains_dollar_at && *contains_dollar_at)
+       all_element_arrayref = 1;
     }
   else
-    tdesc = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND|(pflags&(PF_NOSPLIT2|PF_ASSIGNRHS)), &ind);
+    {
+      local_pflags |= PF_IGNUNBOUND|(pflags&(PF_NOSPLIT2|PF_ASSIGNRHS));
+      tdesc = parameter_brace_expand_word (name, var_is_special, quoted, local_pflags, &es);
+    }
 
   if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal)
     {
@@ -8813,7 +9858,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
       dispose_word_desc (tdesc);
     }
   else
-    temp = (char  *)0;
+    temp = (char *)0;
 
   if (temp == &expand_param_error || temp == &expand_param_fatal)
     {
@@ -8842,7 +9887,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
          if (expand_no_split_dollar_star && t[1] == '*')       /* XXX */
            qflags |= Q_DOUBLE_QUOTES;
        }
-      chk_atstar (name, qflags, quoted_dollar_atp, contains_dollar_at);
+      chk_atstar (name, qflags, pflags, quoted_dollar_atp, contains_dollar_at);
     }
 #endif
 
@@ -8857,7 +9902,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
                   (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) &&
                   QUOTED_NULL (temp) &&
                   valid_array_reference (name, 0) &&
-                  chk_atstar (name, 0, (int *)0, (int *)0);
+                  chk_atstar (name, 0, 0, (int *)0, (int *)0);
 #endif
 
   /* Get the rest of the stuff inside the braces. */
@@ -8878,11 +9923,11 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 
   /* 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 (want_substring || want_patsub || want_casemod || c == '@' || c == '#' || c == '%' || c == RBRACE)
     {
       if (var_is_set == 0 && unbound_vars_is_error && ((name[0] != '@' && name[0] != '*') || name[1]) && all_element_arrayref == 0)
        {
-         last_command_exit_value = EXECUTION_FAILURE;
+         set_exit_status (EXECUTION_FAILURE);
          err_unboundvar (name);
          FREE (value);
          FREE (temp);
@@ -8894,9 +9939,12 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
   /* If this is a substring spec, process it and add the result. */
   if (want_substring)
     {
-      temp1 = parameter_brace_substring (name, temp, ind, value, quoted, pflags, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      temp1 = parameter_brace_substring (name, temp, &es, value, quoted, pflags, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
       FREE (value);
       FREE (temp);
+#if defined (ARRAY_VARS)
+      flush_eltstate (&es);
+#endif
 
       if (temp1 == &expand_param_error || temp1 == &expand_param_fatal)
         {
@@ -8910,10 +9958,13 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
         "$@" 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 && 
+      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;
+      else if (temp1 && (name[0] == '*' && name[1] == 0) && quoted == 0 &&
+               (pflags & PF_ASSIGNRHS))
+       ret->flags |= W_SPLITSPACE;     /* Posix interp 888 */  
       /* Special handling for $* when unquoted and $IFS is null. Posix interp 888 */
       else if (temp1 && (name[0] == '*' && name[1] == 0) && quoted == 0 && ifs_is_null)
        ret->flags |= W_SPLITSPACE;     /* Posix interp 888 */
@@ -8923,9 +9974,12 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
     }
   else if (want_patsub)
     {
-      temp1 = parameter_brace_patsub (name, temp, ind, value, quoted, pflags, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      temp1 = parameter_brace_patsub (name, temp, &es, value, quoted, pflags, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
       FREE (value);
       FREE (temp);
+#if defined (ARRAY_VARS)
+      flush_eltstate (&es);
+#endif
 
       if (temp1 == &expand_param_error || temp1 == &expand_param_fatal)
         {
@@ -8949,9 +10003,12 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 #if defined (CASEMOD_EXPANSIONS)
   else if (want_casemod)
     {
-      temp1 = parameter_brace_casemod (name, temp, ind, modspec, value, quoted, pflags, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      temp1 = parameter_brace_casemod (name, temp, &es, modspec, value, quoted, pflags, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
       FREE (value);
       FREE (temp);
+#if defined (ARRAY_VARS)
+      flush_eltstate (&es);
+#endif
 
       if (temp1 == &expand_param_error || temp1 == &expand_param_fatal)
         {
@@ -8980,11 +10037,14 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
     default:
     case '\0':
 bad_substitution:
-      last_command_exit_value = EXECUTION_FAILURE;
+      set_exit_status (EXECUTION_FAILURE);
       report_error (_("%s: bad substitution"), string ? string : "??");
       FREE (value);
       FREE (temp);
       free (name);
+#if defined (ARRAY_VARS)
+      flush_eltstate (&es);
+#endif
       if (shell_compatibility_level <= 43)
        return &expand_wdesc_error;
       else
@@ -8994,14 +10054,17 @@ bad_substitution:
       break;
 
     case '@':
-      temp1 = parameter_brace_transform (name, temp, ind, value, c, quoted, pflags, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      temp1 = parameter_brace_transform (name, temp, &es, value, c, quoted, pflags, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
       free (temp);
       free (value);
+#if defined (ARRAY_VARS)
+      flush_eltstate (&es);
+#endif
 
       if (temp1 == &expand_param_error || temp1 == &expand_param_fatal)
        {
          free (name);
-         last_command_exit_value = EXECUTION_FAILURE;
+         set_exit_status (EXECUTION_FAILURE);
          report_error (_("%s: bad substitution"), string ? string : "??");
          return (temp1 == &expand_param_error ? &expand_wdesc_error : &expand_wdesc_fatal);
        }
@@ -9024,9 +10087,12 @@ bad_substitution:
          FREE (value);
          break;
        }
-      temp1 = parameter_brace_remove_pattern (name, temp, ind, value, c, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      temp1 = parameter_brace_remove_pattern (name, temp, &es, value, c, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
       free (temp);
       free (value);
+#if defined (ARRAY_VARS)
+      flush_eltstate (&es);
+#endif
 
       ret = alloc_word_desc ();
       ret->word = temp1;
@@ -9085,19 +10151,33 @@ bad_substitution:
        }
       else     /* VAR not set or VAR is NULL. */
        {
+         /* If we're freeing a quoted null here, we need to remember we saw
+            it so we can restore it later if needed, or the caller can note it.
+            The check against `+' doesn't really matter, since the other cases
+            don't use or return TFLAG, but it's good for clarity. */
+         if (c == '+' && temp && QUOTED_NULL (temp) &&
+             (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+           tflag |= W_HASQUOTEDNULL;
+
          FREE (temp);
          temp = (char *)NULL;
          if (c == '=' && var_is_special)
            {
-             last_command_exit_value = EXECUTION_FAILURE;
+             set_exit_status (EXECUTION_FAILURE);
              report_error (_("$%s: cannot assign in this way"), name);
              free (name);
              free (value);
+#if defined (ARRAY_VARS)
+             flush_eltstate (&es);
+#endif
              return &expand_wdesc_error;
            }
          else if (c == '?')
            {
              parameter_brace_expand_error (name, value, check_nullness);
+#if defined (ARRAY_VARS)
+             flush_eltstate (&es);
+#endif
              return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
            }
          else if (c != '+')
@@ -9127,6 +10207,9 @@ bad_substitution:
       break;
     }
   free (name);
+#if defined (ARRAY_VARS)
+  flush_eltstate (&es);
+#endif
 
   if (ret == 0)
     {
@@ -9149,13 +10232,13 @@ param_expand (string, sindex, quoted, expanded_something,
      int *quoted_dollar_at_p, *had_quoted_null_p, pflags;
 {
   char *temp, *temp1, uerror[3], *savecmd;
-  int zindex, t_index, expok;
+  int zindex, t_index, expok, eflag;
   unsigned char c;
   intmax_t number;
   SHELL_VAR *var;
-  WORD_LIST *list;
+  WORD_LIST *list, *l;
   WORD_DESC *tdesc, *ret;
-  int tflag;
+  int tflag, nullarg;
 
 /*itrace("param_expand: `%s' pflags = %d", string+*sindex, pflags);*/
   zindex = *sindex;
@@ -9186,7 +10269,7 @@ param_expand (string, sindex, quoted, expanded_something,
          uerror[0] = '$';
          uerror[1] = c;
          uerror[2] = '\0';
-         last_command_exit_value = EXECUTION_FAILURE;
+         set_exit_status (EXECUTION_FAILURE);
          err_unboundvar (uerror);
          return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
        }
@@ -9234,7 +10317,7 @@ param_expand (string, sindex, quoted, expanded_something,
              uerror[0] = '$';
              uerror[1] = c;
              uerror[2] = '\0';
-             last_command_exit_value = EXECUTION_FAILURE;
+             set_exit_status (EXECUTION_FAILURE);
              err_unboundvar (uerror);
              return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
            }
@@ -9261,7 +10344,7 @@ param_expand (string, sindex, quoted, expanded_something,
          uerror[0] = '$';
          uerror[1] = '*';
          uerror[2] = '\0';
-         last_command_exit_value = EXECUTION_FAILURE;
+         set_exit_status (EXECUTION_FAILURE);
          err_unboundvar (uerror);
          return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
        }
@@ -9299,9 +10382,14 @@ param_expand (string, sindex, quoted, expanded_something,
             identical to $@ */
          if (expand_no_split_dollar_star && quoted == 0 && ifs_is_set == 0 && (pflags & PF_ASSIGNRHS))
            {
-             /* Posix interp 888: RHS of assignment, IFS unset */
-             temp = string_list_dollar_at (list, Q_DOUBLE_QUOTES, pflags);
-             tflag |= W_SPLITSPACE;
+             /* Posix interp 888: RHS of assignment, IFS unset: no splitting,
+                separate with space */
+             temp1 = string_list_dollar_star (list, quoted, pflags);
+             temp = temp1 ? quote_string (temp1) : temp1;
+             /* XXX - tentative - note that we saw a quoted null here */
+             if (temp1 && *temp1 == 0 && QUOTED_NULL (temp))
+               tflag |= W_SAWQUOTEDNULL;
+             FREE (temp1);
            }
          else if (expand_no_split_dollar_star && quoted == 0 && ifs_is_null && (pflags & PF_ASSIGNRHS))
            {
@@ -9315,6 +10403,10 @@ param_expand (string, sindex, quoted, expanded_something,
              /* Posix interp 888: RHS of assignment, IFS set to non-null value */
              temp1 = string_list_dollar_star (list, quoted, pflags);
              temp = temp1 ? quote_string (temp1) : temp1;
+
+             /* XXX - tentative - note that we saw a quoted null here */
+             if (temp1 && *temp1 == 0 && QUOTED_NULL (temp))
+               tflag |= W_SAWQUOTEDNULL;
              FREE (temp1);
            }
          /* XXX - should we check ifs_is_set here as well? */
@@ -9375,12 +10467,18 @@ param_expand (string, sindex, quoted, expanded_something,
          uerror[0] = '$';
          uerror[1] = '@';
          uerror[2] = '\0';
-         last_command_exit_value = EXECUTION_FAILURE;
+         set_exit_status (EXECUTION_FAILURE);
          err_unboundvar (uerror);
          return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
        }
 #endif
 
+      for (nullarg = 0, l = list; l; l = l->next)
+       {
+         if (l->word && (l->word->word == 0 || l->word->word[0] == 0))
+           nullarg = 1;
+       }
+
       /* We want to flag the fact that we saw this.  We can't turn
         off quoting entirely, because other characters in the
         string might need it (consider "\"$@\""), but we need some
@@ -9399,12 +10497,18 @@ param_expand (string, sindex, quoted, expanded_something,
         parameters no matter what IFS is set to. */
       /* XXX - what to do when in a context where word splitting is not
         performed? Even when IFS is not the default, posix seems to imply
-        that we behave like unquoted $* ?  See below for how we use
-        PF_NOSPLIT2 here. */
+        that we have to expand $@ to all the positional parameters and
+        separate them with spaces, which are preserved because word splitting
+        doesn't take place.  See below for how we use PF_NOSPLIT2 here. */
 
       /* These are the cases where word splitting will not be performed. */
       if (pflags & PF_ASSIGNRHS)
-       temp = string_list_dollar_at (list, (quoted|Q_DOUBLE_QUOTES), pflags);
+       {
+         temp = string_list_dollar_at (list, (quoted|Q_DOUBLE_QUOTES), pflags);
+         if (nullarg)
+           tflag |= W_HASQUOTEDNULL;   /* we know quoting produces quoted nulls */
+       }
+
       /* This needs to match what expand_word_internal does with non-quoted $@
         does with separating with spaces.  Passing Q_DOUBLE_QUOTES means that
         the characters in LIST will be quoted, and PF_ASSIGNRHS ensures that
@@ -9506,7 +10610,9 @@ arithsub:
          /* No error messages. */
          savecmd = this_command_name;
          this_command_name = (char *)NULL;
-         number = evalexp (temp1, EXP_EXPANDED, &expok);
+
+         eflag = (shell_compatibility_level > 51) ? 0 : EXP_EXPANDED;
+         number = evalexp (temp1, eflag, &expok);
          this_command_name = savecmd;
          free (temp);
          free (temp1);
@@ -9514,7 +10620,7 @@ arithsub:
            {
              if (interactive_shell == 0 && posixly_correct)
                {
-                 last_command_exit_value = EXECUTION_FAILURE;
+                 set_exit_status (EXECUTION_FAILURE);
                  return (&expand_wdesc_fatal);
                }
              else
@@ -9619,7 +10725,8 @@ comsub:
 #if defined (ARRAY_VARS)
          if (temp && *temp && valid_array_reference (temp, 0))
            {
-             tdesc = parameter_brace_expand_word (temp, SPECIAL_VAR (temp, 0), quoted, pflags, (arrayind_t *)NULL);
+             chk_atstar (temp, quoted, pflags, quoted_dollar_at_p, contains_dollar_at);
+             tdesc = parameter_brace_expand_word (temp, SPECIAL_VAR (temp, 0), quoted, pflags, 0);
              if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal)
                return (tdesc);
              ret = tdesc;
@@ -9630,7 +10737,7 @@ comsub:
          /* 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;
+             set_exit_status (EXECUTION_FAILURE);
              report_error (_("%s: invalid variable name for name reference"), temp);
              return (&expand_wdesc_error);     /* XXX */
            }
@@ -9643,7 +10750,7 @@ comsub:
 unbound_variable:
       if (unbound_vars_is_error)
        {
-         last_command_exit_value = EXECUTION_FAILURE;
+         set_exit_status (EXECUTION_FAILURE);
          err_unboundvar (temp1);
        }
       else
@@ -9653,7 +10760,7 @@ unbound_variable:
        }
 
       free (temp1);
-      last_command_exit_value = EXECUTION_FAILURE;
+      set_exit_status (EXECUTION_FAILURE);
       return ((unbound_vars_is_error && interactive_shell == 0)
                ? &expand_wdesc_fatal
                : &expand_wdesc_error);
@@ -9674,6 +10781,120 @@ return0:
   return ret;
 }
 
+#if defined (ARRAY_VARS)
+/* Characters that need to be backslash-quoted after expanding array subscripts */
+static char abstab[256] = { '\1' };
+
+/* Run an array subscript through the appropriate word expansions. */
+char *
+expand_subscript_string (string, quoted)
+     char *string;
+     int quoted;
+{
+  WORD_DESC td;
+  WORD_LIST *tlist;
+  int oe;
+  char *ret;
+
+  if (string == 0 || *string == 0)
+    return (char *)NULL;
+
+  oe = expand_no_split_dollar_star;
+  ret = (char *)NULL;
+
+#if 0
+  td.flags = W_NOPROCSUB|W_NOTILDE|W_NOSPLIT2; /* XXX - W_NOCOMSUB? */
+#else
+  td.flags = W_NOPROCSUB|W_NOSPLIT2;   /* XXX - W_NOCOMSUB? */
+#endif
+  td.word = savestring (string);               /* in case it's freed on error */
+
+  expand_no_split_dollar_star = 1;
+  tlist = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+  expand_no_split_dollar_star = oe;
+
+  if (tlist)
+    {
+      if (tlist->word)
+       {
+         remove_quoted_nulls (tlist->word->word);
+         tlist->word->flags &= ~W_HASQUOTEDNULL;
+       }
+      dequote_list (tlist);
+      ret = string_list (tlist);
+      dispose_words (tlist);
+    }
+
+  free (td.word);
+  return (ret);
+}
+
+/* Expand the subscript in STRING, which is an array reference. To ensure we
+   only expand it once, we quote the characters that would start another
+   expansion and the bracket characters that are special to array subscripts. */
+static char *
+expand_array_subscript (string, sindex, quoted, flags)
+     char *string;
+     int *sindex;
+     int quoted, flags;
+{
+  char *ret, *exp, *t;
+  size_t slen;
+  int si, ni;
+
+  si = *sindex;
+  slen = STRLEN (string);
+
+  if (abstab[0] == '\1')
+    {
+      /* These are basically the characters that start shell expansions plus
+        the characters that delimit subscripts. */
+      memset (abstab, '\0', sizeof (abstab));
+      abstab[LBRACK] = abstab[RBRACK] = 1;
+      abstab['$'] = abstab['`'] = abstab['~'] = 1;
+      abstab['\\'] = abstab['\''] = 1;
+      abstab['"'] = 1; /* XXX */
+      /* We don't quote `@' or `*' in the subscript at all. */
+    }
+
+  /* string[si] == LBRACK */
+  ni = skipsubscript (string, si, 0);
+  /* These checks mirror the ones in valid_array_reference. The check for
+     (ni - si) == 1 checks for empty subscripts. We don't check that the
+     subscript is a separate word if we're parsing an arithmetic expression. */
+  if (ni >= slen || string[ni] != RBRACK || (ni - si) == 1 ||
+      (string[ni+1] != '\0' && (quoted & Q_ARITH) == 0))
+    {
+      /* let's check and see what fails this check */
+      INTERNAL_DEBUG (("expand_array_subscript: bad subscript string: `%s'", string+si));
+      ret = (char *)xmalloc (2);       /* badly-formed subscript */
+      ret[0] = string[si];
+      ret[1] = '\0';
+      *sindex = si + 1;
+      return ret;
+    }
+
+  /* STRING[ni] == RBRACK */
+  exp = substring (string, si+1, ni);
+  t = expand_subscript_string (exp, quoted & ~(Q_ARITH|Q_DOUBLE_QUOTES));
+  free (exp);
+  exp = t ? sh_backslash_quote (t, abstab, 0) : savestring ("");
+  free (t);
+
+  slen = STRLEN (exp);
+  ret = xmalloc (slen + 2 + 1);
+  ret[0] ='[';
+  strcpy (ret + 1, exp);
+  ret[slen + 1] = ']';
+  ret[slen + 2] = '\0';
+
+  free (exp);
+  *sindex = ni + 1;
+
+  return ret;
+}
+#endif
+
 void
 invalidate_cached_quoted_dollar_at ()
 {
@@ -9724,7 +10945,7 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
   size_t istring_size;
 
   /* Index into ISTRING. */
-  int istring_index;
+  size_t istring_index;
 
   /* Temporary string storage. */
   char *temp, *temp1;
@@ -9750,6 +10971,7 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
   int had_quoted_null;
   int has_quoted_ifs;          /* did we add a quoted $IFS character here? */
   int has_dollar_at, temp_has_dollar_at;
+  int internal_tilde;
   int split_on_spaces;
   int local_expanded;
   int tflag;
@@ -9765,7 +10987,9 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
 
   DECLARE_MBSTATE;
 
-  /* OK, let's see if we can optimize a common idiom: "$@" */
+  /* OK, let's see if we can optimize a common idiom: "$@". This needs to make sure
+     that all of the flags callers care about (e.g., W_HASQUOTEDNULL) are set in
+     list->flags. */
   if (STREQ (word->word, "\"$@\"") &&
       (word->flags == (W_HASDOLLAR|W_QUOTED)) &&
       dollar_vars[1])          /* XXX - check IFS here as well? */
@@ -9787,6 +11011,7 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
   quoted_dollar_at = had_quoted_null = has_dollar_at = 0;
   has_quoted_ifs = 0;
   split_on_spaces = 0;
+  internal_tilde = 0;          /* expanding =~ or :~ */
   quoted_state = UNQUOTED;
 
   string = word->word;
@@ -9851,7 +11076,7 @@ add_string:
          {
               /* XXX - technically this should only be expanded at the start
               of a word */
-           if (string[++sindex] != LPAREN || (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (word->flags & (W_DQUOTE|W_NOPROCSUB)) || posixly_correct)
+           if (string[++sindex] != LPAREN || (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (word->flags & W_NOPROCSUB))
              {
                sindex--;       /* add_character: label increments sindex */
                goto add_character;
@@ -9874,6 +11099,22 @@ add_string:
          }
 #endif /* PROCESS_SUBSTITUTION */
 
+#if defined (ARRAY_VARS)
+       case '[':               /*]*/
+         if ((quoted & Q_ARITH) == 0 || shell_compatibility_level <= 51)
+           {
+             if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0)
+               goto add_ifs_character;
+             else
+               goto add_character;
+           }
+         else
+           {
+             temp = expand_array_subscript (string, &sindex, quoted, word->flags);
+             goto add_string;
+           }
+#endif
+
        case '=':
          /* Posix.2 section 3.6.1 says that tildes following `=' in words
             which are not assignment statements are not expanded.  If the
@@ -9893,20 +11134,16 @@ add_string:
                goto add_character;
            }
          /* If we're not in posix mode or forcing assignment-statement tilde
-            expansion, note where the `=' appears in the word and prepare to
-            do tilde expansion following the first `='. */
+            expansion, note where the first `=' appears in the word and prepare
+            to do tilde expansion following the first `='. We have to keep
+            track of the first `=' (using assignoff) to avoid being confused
+            by an `=' in the rhs of the assignment statement. */
          if ((word->flags & W_ASSIGNMENT) &&
              (posixly_correct == 0 || (word->flags & W_TILDEEXP)) &&
              assignoff == -1 && sindex > 0)
            assignoff = sindex;
          if (sindex == assignoff && string[sindex+1] == '~')   /* XXX */
-           word->flags |= W_ITILDE;
-#if 0
-         else if ((word->flags & W_ASSIGNMENT) &&
-                  (posixly_correct == 0 || (word->flags & W_TILDEEXP)) &&
-                  string[sindex+1] == '~')
-           word->flags |= W_ITILDE;
-#endif
+           internal_tilde = 1;
 
          if (word->flags & W_ASSIGNARG)
            word->flags |= W_ASSIGNRHS;         /* affects $@ */
@@ -9928,9 +11165,10 @@ add_string:
                goto add_character;
            }
 
-         if ((word->flags & (W_ASSIGNMENT|W_ASSIGNRHS|W_TILDEEXP)) &&
+         if ((word->flags & (W_ASSIGNMENT|W_ASSIGNRHS)) &&
+             (posixly_correct == 0 || (word->flags & W_TILDEEXP)) &&
              string[sindex+1] == '~')
-           word->flags |= W_ITILDE;
+           internal_tilde = 1;
 
          if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
            goto add_ifs_character;
@@ -9943,11 +11181,11 @@ add_string:
             assignment statement, we don't do tilde expansion.  We don't
             do tilde expansion if quoted or in an arithmetic context. */
 
-         if ((word->flags & (W_NOTILDE|W_DQUOTE)) ||
-             (sindex > 0 && ((word->flags & W_ITILDE) == 0)) ||
+         if ((word->flags & W_NOTILDE) ||
+             (sindex > 0 && (internal_tilde == 0)) ||
              (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
            {
-             word->flags &= ~W_ITILDE;
+             internal_tilde = 0;
              if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0)
                goto add_ifs_character;
              else
@@ -9963,7 +11201,7 @@ add_string:
 
          temp = bash_tilde_find_word (string + sindex, tflag, &t_index);
            
-         word->flags &= ~W_ITILDE;
+         internal_tilde = 0;
 
          if (temp && *temp && t_index > 0)
            {
@@ -9998,6 +11236,7 @@ add_string:
            pflags |= PF_ASSIGNRHS;
          if (word->flags & W_COMPLETE)
            pflags |= PF_COMPLETE;
+
          tword = param_expand (string, &sindex, quoted, expanded_something,
                               &temp_has_dollar_at, &quoted_dollar_at,
                               &had_quoted_null, pflags);
@@ -10037,7 +11276,7 @@ add_string:
          {
            t_index = sindex++;
 
-           temp = string_extract (string, &sindex, "`", SX_REQMATCH);
+           temp = string_extract (string, &sindex, "`", (word->flags & W_COMPLETE) ? SX_COMPLETE : SX_REQMATCH);
            /* The test of sindex against t_index is to allow bare instances of
               ` to pass through, for backwards compatibility. */
            if (temp == &extract_string_error || temp == &extract_string_fatal)
@@ -10047,7 +11286,7 @@ add_string:
                    sindex = t_index;
                    goto add_character;
                  }
-               last_command_exit_value = EXECUTION_FAILURE;
+               set_exit_status (EXECUTION_FAILURE);
                report_error (_("bad substitution: no closing \"`\" in %s") , string+t_index);
                free (string);
                free (istring);
@@ -10065,7 +11304,7 @@ add_string:
            else
              {
                de_backslash (temp);
-               tword = command_substitute (temp, quoted, 0);
+               tword = command_substitute (temp, quoted, PF_BACKQUOTE);
                temp1 = tword ? tword->word : (char *)NULL;
                if (tword)
                  dispose_word_desc (tword);
@@ -10084,7 +11323,12 @@ add_string:
 
          c = string[++sindex];
 
-         if (quoted & Q_HERE_DOCUMENT)
+         /* "However, the double-quote character ( '"' ) shall not be treated
+            specially within a here-document, except when the double-quote
+            appears within "$()", "``", or "${}"." */
+         if ((quoted & Q_HERE_DOCUMENT) && (quoted & Q_DOLBRACE) && c == '"')
+           tflag = CBSDQUOTE;          /* special case */
+         else if (quoted & Q_HERE_DOCUMENT)
            tflag = CBSHDOC;
          else if (quoted & Q_DOUBLE_QUOTES)
            tflag = CBSDQUOTE;
@@ -10144,6 +11388,7 @@ add_twochars:
          break;
 
        case '"':
+         /* XXX - revisit this */
          if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) && ((quoted & Q_ARITH) == 0))
            goto add_character;
 
@@ -10170,11 +11415,15 @@ add_twochars:
              if (word->flags & W_NOPROCSUB)
                tword->flags |= W_NOPROCSUB;
 
+             if (word->flags & W_ASSIGNRHS)
+               tword->flags |= W_ASSIGNRHS;
+
              temp = (char *)NULL;
 
              temp_has_dollar_at = 0;   /* does this quoted (sub)string include $@? */
              /* Need to get W_HASQUOTEDNULL flag through this function. */
-             list = expand_word_internal (tword, Q_DOUBLE_QUOTES, 0, &temp_has_dollar_at, (int *)NULL);
+             /* XXX - preserve Q_ARITH here? */
+             list = expand_word_internal (tword, Q_DOUBLE_QUOTES|(quoted&Q_ARITH), 0, &temp_has_dollar_at, (int *)NULL);
              has_dollar_at += temp_has_dollar_at;
 
              if (list == &expand_word_error || list == &expand_word_fatal)
@@ -10208,13 +11457,8 @@ add_twochars:
                 disable the special handling that "$@" gets. */
              if (list && list->word && list->next == 0 && (list->word->flags & W_HASQUOTEDNULL))
                {
-                 /* If we already saw a quoted null, we don't need to add
-                    another one */
                  if (had_quoted_null && temp_has_dollar_at)
-                   {
-                     quoted_dollar_at++;
-                     break;
-                   }
+                   quoted_dollar_at++;
                  had_quoted_null = 1;          /* XXX */
                }
 
@@ -10223,9 +11467,7 @@ add_twochars:
                 a special case; it's the only case where a quoted string
                 can expand into more than one word.  It's going to come back
                 from the above call to expand_word_internal as a list with
-                a single word, in which all characters are quoted and
-                separated by blanks.  What we want to do is to turn it back
-                into a list for the next piece of code. */
+                multiple words. */
              if (list)
                dequote_list (list);
 
@@ -10301,6 +11543,13 @@ add_twochars:
             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 && quoted == 0 && (word->flags & (W_NOSPLIT|W_EXPANDRHS|W_ASSIGNRHS)) == W_EXPANDRHS)
+           {
+             c = CTLNUL;
+             sindex--;
+             had_quoted_null = 1;
+             goto add_character;
+           }
          if (temp == 0 && quoted_state == PARTIALLY_QUOTED && (word->flags & (W_NOSPLIT|W_NOSPLIT2)))
            continue;
 
@@ -10329,7 +11578,7 @@ add_twochars:
            goto add_character;
 
          t_index = ++sindex;
-         temp = string_extract_single_quoted (string, &sindex);
+         temp = string_extract_single_quoted (string, &sindex, 0);
 
          /* If the entire STRING was surrounded by single quotes,
             then the string is wholly quoted. */
@@ -10352,7 +11601,14 @@ add_twochars:
          /* We do not want to add quoted nulls to strings that are only
             partially quoted; such nulls are discarded.  See above for the
             exception, which is when the string is going to be split.
-            Posix interp 888 */
+            Posix interp 888/1129 */
+         if (temp == 0 && quoted_state == PARTIALLY_QUOTED && quoted == 0 && (word->flags & (W_NOSPLIT|W_EXPANDRHS|W_ASSIGNRHS)) == W_EXPANDRHS)
+           {
+             c = CTLNUL;
+             sindex--;
+             goto add_character;
+           }
+
          if (temp == 0 && (quoted_state == PARTIALLY_QUOTED) && (word->flags & (W_NOSPLIT|W_NOSPLIT2)))
            continue;
 
@@ -10368,13 +11624,30 @@ add_twochars:
 
          /* break; */
 
+       case ' ':
+         /* If we are in a context where the word is not going to be split, but
+            we need to account for $@ and $* producing one word for each
+            positional parameter, add quoted spaces so the spaces in the
+            expansion of "$@", if any, behave correctly. We still may need to
+            split if we are expanding the rhs of a word expansion. */
+         if (ifs_is_null || split_on_spaces || ((word->flags & (W_NOSPLIT|W_NOSPLIT2|W_ASSIGNRHS)) && (word->flags & W_EXPANDRHS) == 0))
+           {
+             if (string[sindex])
+               sindex++;
+             twochars[0] = CTLESC;
+             twochars[1] = c;
+             goto add_twochars;
+           }
+         /* FALLTHROUGH */
+         
        default:
          /* This is the fix for " $@ " */
-       add_ifs_character:
+add_ifs_character:
          if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (isexp == 0 && isifs (c) && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0))
            {
              if ((quoted&(Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)
                has_quoted_ifs++;
+add_quoted_character:
              if (string[sindex])       /* from old goto dollar_add_string */
                sindex++;
              if (c == 0)
@@ -10406,7 +11679,7 @@ add_twochars:
 
          SADD_MBCHAR (temp, string, sindex, string_size);
 
-       add_character:
+add_character:
          RESIZE_MALLOCED_BUFFER (istring, istring_index, 1, istring_size,
                                  DEFAULT_ARRAY_SIZE);
          istring[istring_index++] = c;
@@ -10446,7 +11719,11 @@ finished_with_string:
 
   if (*istring == '\0')
     {
+#if 0
       if (quoted_dollar_at == 0 && (had_quoted_null || quoted_state == PARTIALLY_QUOTED))
+#else
+      if (had_quoted_null || (quoted_dollar_at == 0 && quoted_state == PARTIALLY_QUOTED))
+#endif
        {
          istring[0] = CTLNUL;
          istring[1] = '\0';
@@ -10483,12 +11760,23 @@ finished_with_string:
        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 (word->flags & W_ARRAYREF)
+       tword->flags |= W_ARRAYREF;
       if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
        tword->flags |= W_QUOTED;
       list = make_word_list (tword, (WORD_LIST *)NULL);
     }
+  else if (word->flags & W_ASSIGNRHS)
+    {
+      list = list_string (istring, "", quoted);
+      tword = list->word;
+      if (had_quoted_null && QUOTED_NULL (istring))
+       tword->flags |= W_HASQUOTEDNULL;
+      free (list);
+      free (istring);
+      istring = 0;                     /* avoid later free() */
+      goto set_word_flags;
+    }
   else
     {
       char *ifs_chars;
@@ -10564,7 +11852,7 @@ finished_with_string:
            tword->word = remove_quoted_ifs (istring);
          else
            tword->word = istring;
-         if (had_quoted_null && QUOTED_NULL (istring))
+         if (had_quoted_null && QUOTED_NULL (istring)) /* should check for more than one */
            tword->flags |= W_HASQUOTEDNULL;    /* XXX */
          else if (had_quoted_null)
            tword->flags |= W_SAWQUOTEDNULL;    /* XXX */
@@ -10582,8 +11870,8 @@ set_word_flags:
            tword->flags |= W_NOGLOB;
          if (word->flags & W_NOBRACE)
            tword->flags |= W_NOBRACE;
-         if (word->flags & W_NOEXPAND)
-           tword->flags |= W_NOEXPAND;
+         if (word->flags & W_ARRAYREF)
+           tword->flags |= W_ARRAYREF;
          list = make_word_list (tword, (WORD_LIST *)NULL);
        }
     }
@@ -10644,7 +11932,7 @@ string_quote_removal (string, quoted)
              break;
            }
          tindex = sindex + 1;
-         temp = string_extract_single_quoted (string, &tindex);
+         temp = string_extract_single_quoted (string, &tindex, 0);
          if (temp)
            {
              strcpy (r, temp);
@@ -10824,6 +12112,12 @@ word_list_split (list)
          w->word[0] = '\0';
          tresult = make_word_list (w, (WORD_LIST *)NULL);
        }
+#if defined (ARRAY_VARS)
+      /* pass W_ARRAYREF through for words that are not split and are
+        identical to the original word. */
+      if (tresult && tresult->next == 0 && t->next == 0 && (t->word->flags & W_ARRAYREF) && STREQ (t->word->word, tresult->word->word))
+       tresult->word->flags |= W_ARRAYREF;
+#endif
       if (result == 0)
         result = e = tresult;
       else
@@ -11015,6 +12309,7 @@ glob_expand_word_list (tlist, eflags)
   register int glob_index;
   WORD_LIST *glob_list, *output_list, *disposables, *next;
   WORD_DESC *tword;
+  int x;
 
   output_list = disposables = (WORD_LIST *)NULL;
   glob_array = (char **)NULL;
@@ -11038,7 +12333,7 @@ glob_expand_word_list (tlist, eflags)
       if ((tlist->word->flags & W_NOGLOB) == 0 &&
          unquoted_glob_pattern_p (tlist->word->word))
        {
-         glob_array = shell_glob_filename (tlist->word->word);
+         glob_array = shell_glob_filename (tlist->word->word, QGLOB_CTLESC);   /* XXX */
 
          /* Handle error cases.
             I don't think we should report errors like "No such file
@@ -11190,8 +12485,10 @@ brace_expand_word_list (tlist, eflags)
 #endif
 
 #if defined (ARRAY_VARS)
-/* Take WORD, a compound associative array assignment, and internally run
-   'declare -A w', where W is the variable name portion of WORD. */
+/* Take WORD, a compound array assignment, and internally run (for example),
+   'declare -A w', where W is the variable name portion of WORD. OPTION is
+   the list of options to supply to `declare'. CMD is the declaration command
+   we are expanding right now; it's unused currently. */
 static int
 make_internal_declare (word, option, cmd)
      char *word;
@@ -11220,7 +12517,275 @@ make_internal_declare (word, option, cmd)
   dispose_words (wl);
   return r;
 }  
+
+/* Expand VALUE in NAME[+]=( VALUE ) to a list of words. FLAGS is 1 if NAME
+   is an associative array.
+
+   If we are  processing an indexed array, expand_compound_array_assignment
+   will expand all the individual words and quote_compound_array_list will
+   single-quote them. If we are processing an associative array, we use
+   parse_string_to_word_list to split VALUE into a list of words instead of
+   faking up a shell variable and calling expand_compound_array_assignment.
+   expand_and_quote_assoc_word expands and single-quotes each word in VALUE
+   together so we don't have problems finding the end of the subscript when
+   quoting it.
+
+   Words in VALUE can be individual words, which are expanded and single-quoted,
+   or words of the form [IND]=VALUE, which end up as explained below, as
+   ['expanded-ind']='expanded-value'. */
+
+static WORD_LIST *
+expand_oneword (value, flags)
+     char *value;
+     int flags;
+{
+  WORD_LIST *l, *nl;
+  char *t;
+  int kvpair;
+  
+  if (flags == 0)
+    {
+      /* Indexed array */
+      l = expand_compound_array_assignment ((SHELL_VAR *)NULL, value, flags);
+      /* Now we quote the results of the expansion above to prevent double
+        expansion. */
+      quote_compound_array_list (l, flags);
+      return l;
+    }
+  else
+    {
+      /* Associative array */
+      l = parse_string_to_word_list (value, 1, "array assign");
+#if ASSOC_KVPAIR_ASSIGNMENT
+      kvpair = kvpair_assignment_p (l);
+#endif
+
+      /* For associative arrays, with their arbitrary subscripts, we have to
+        expand and quote in one step so we don't have to search for the
+        closing right bracket more than once. */
+      for (nl = l; nl; nl = nl->next)
+       {
+#if ASSOC_KVPAIR_ASSIGNMENT
+         if (kvpair)
+           /* keys and values undergo the same set of expansions */
+           t = expand_and_quote_kvpair_word (nl->word->word);
+         else
 #endif
+         if ((nl->word->flags & W_ASSIGNMENT) == 0)
+           t = sh_single_quote (nl->word->word ? nl->word->word : "");
+         else
+           t = expand_and_quote_assoc_word (nl->word->word, flags);
+         free (nl->word->word);
+         nl->word->word = t;
+       }
+      return l;
+    }
+}
+
+/* Expand a single compound assignment argument to a declaration builtin.
+   This word takes the form NAME[+]=( VALUE ). The NAME[+]= is passed through
+   unchanged. The VALUE is expanded and each word in the result is single-
+   quoted. Words of the form [key]=value end up as
+   ['expanded-key']='expanded-value'. Associative arrays have special
+   handling, see expand_oneword() above. The return value is
+   NAME[+]=( expanded-and-quoted-VALUE ). */
+static void
+expand_compound_assignment_word (tlist, flags)
+     WORD_LIST *tlist;
+     int flags;
+{
+  WORD_LIST *l;
+  int wlen, oind, t;
+  char *value, *temp;
+
+/*itrace("expand_compound_assignment_word: original word = -%s-", tlist->word->word);*/
+  t = assignment (tlist->word->word, 0);
+
+  /* value doesn't have the open and close parens */
+  oind = 1;
+  value = extract_array_assignment_list (tlist->word->word + t + 1, &oind);
+  /* This performs one round of expansion on the index/key and value and
+     single-quotes each word in the result. */
+  l = expand_oneword (value, flags);
+  free (value);
+
+  value = string_list (l);
+  dispose_words (l);
+
+  wlen = STRLEN (value);
+
+  /* Now, let's rebuild the string */
+  temp = xmalloc (t + 3 + wlen + 1);   /* name[+]=(value) */
+  memcpy (temp, tlist->word->word, ++t);
+  temp[t++] = '(';
+  if (value)
+    memcpy (temp + t, value, wlen);
+  t += wlen;
+  temp[t++] = ')';
+  temp[t] = '\0';
+/*itrace("expand_compound_assignment_word: reconstructed word = -%s-", temp);*/
+
+  free (tlist->word->word);
+  tlist->word->word = temp;
+
+  free (value);
+}
+
+/* Expand and process an argument to a declaration command. We have already
+   set flags in TLIST->word->flags depending on the declaration command
+   (declare, local, etc.) and the options supplied to it (-a, -A, etc.).
+   TLIST->word->word is of the form NAME[+]=( VALUE ).   
+
+   This does several things, all using pieces of other functions to get the
+   evaluation sequence right. It's called for compound array assignments with
+   the W_ASSIGNMENT flag set (basically, valid identifier names on the lhs).
+   It parses out which flags need to be set for declare to create the variable
+   correctly, then calls declare internally (make_internal_declare) to make
+   sure the variable exists with the correct attributes. Before the variable
+   is created, it calls expand_compound_assignment_word to expand VALUE to a
+   list of words, appropriately quoted for further evaluation. This preserves
+   the semantics of word-expansion-before-calling-builtins. Finally, it calls
+   do_word_assignment to perform the expansion and assignment with the same
+   expansion semantics as a standalone assignment statement (no word splitting,
+   etc.) even though the word is single-quoted so all that needs to happen is
+   quote removal. */
+static WORD_LIST *
+expand_declaration_argument (tlist, wcmd)
+     WORD_LIST *tlist, *wcmd;
+{
+  char opts[16], omap[128];
+  int t, opti, oind, skip, inheriting;
+  WORD_LIST *l;
+
+  inheriting = localvar_inherit;
+  opti = 0;
+  if (tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL|W_CHKLOCAL|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 (tlist->word->flags & W_CHKLOCAL)
+    opts[opti++] = 'G';
+
+  /* If we have special handling note the integer attribute and others
+     that transform the value upon assignment.  What we do is take all
+     of the option arguments and scan through them looking for options
+     that cause such transformations, and add them to the `opts' array. */
+
+  memset (omap, '\0', sizeof (omap));
+  for (l = wcmd->next; l != tlist; l = l->next)
+    {
+      int optchar;
+
+      if (l->word->word[0] != '-' && l->word->word[0] != '+')
+       break;  /* non-option argument */
+      if (l->word->word[0] == '-' && l->word->word[1] == '-' && l->word->word[2] == 0)
+       break;  /* -- signals end of options */
+      optchar = l->word->word[0];
+      for (oind = 1; l->word->word[oind]; oind++)
+       switch (l->word->word[oind])
+         {
+           case 'I':
+             inheriting = 1;
+           case 'i':
+           case 'l':
+           case 'u':
+           case 'c':
+             omap[l->word->word[oind]] = 1;
+             if (opti == 0)
+               opts[opti++] = optchar;
+             break;
+           default:
+             break;
+         }
+    }
+
+  for (oind = 0; oind < sizeof (omap); oind++)
+    if (omap[oind])
+      opts[opti++] = oind;
+
+  /* If there are no -a/-A options, but we have a compound assignment,
+     we have a choice: we can set opts[0]='-', opts[1]='a', since the
+     default is to create an indexed array, and call
+     make_internal_declare with that, or we can just skip the -a and let
+     declare_builtin deal with it.  Once we're here, we're better set
+     up for the latter, since we don't want to deal with looking up
+     any existing variable here -- better to let declare_builtin do it.
+     We need the variable created, though, especially if it's local, so
+     we get the scoping right before we call do_word_assignment.
+     To ensure that make_local_declare gets called, we add `--' if there
+     aren't any options. */
+  if ((tlist->word->flags & (W_ASSIGNASSOC|W_ASSIGNARRAY)) == 0)
+    {
+      if (opti == 0)
+       {
+         opts[opti++] = '-';
+          opts[opti++] = '-';
+       }
+    }
+  opts[opti] = '\0';
+
+  /* This isn't perfect, but it's a start. Improvements later. We expand
+     tlist->word->word and single-quote the results to avoid multiple
+     expansions by, say, do_assignment_internal(). We have to weigh the
+     cost of reconstructing the compound assignment string with its single
+     quoting and letting the declare builtin handle it. The single quotes
+     will prevent any unwanted additional expansion or word splitting. */
+  expand_compound_assignment_word (tlist, (tlist->word->flags & W_ASSIGNASSOC) ? 1 : 0);
+
+  skip = 0;
+  if (opti > 0)
+    {
+      t = make_internal_declare (tlist->word->word, opts, wcmd ? wcmd->word->word : (char *)0);
+      if (t != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = t;
+         if (tlist->word->flags & W_FORCELOCAL)        /* non-fatal error */
+           skip = 1;
+         else
+           exp_jump_to_top_level (DISCARD);
+       }
+    }
+
+  if (skip == 0)
+    {
+      t = do_word_assignment (tlist->word, 0);
+      if (t == 0)
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         exp_jump_to_top_level (DISCARD);
+       }
+    }
+
+  /* Now transform the word as ksh93 appears to do and go on */
+  t = assignment (tlist->word->word, 0);
+  tlist->word->word[t] = '\0';
+  if (tlist->word->word[t - 1] == '+')
+    tlist->word->word[t - 1] = '\0';   /* cut off append op */
+  tlist->word->flags &= ~(W_ASSIGNMENT|W_NOSPLIT|W_COMPASSIGN|W_ASSIGNARG|W_ASSIGNASSOC|W_ASSIGNARRAY);
+
+  return (tlist);
+}
+#endif /* ARRAY_VARS */
 
 static WORD_LIST *
 shell_expand_word_list (tlist, eflags)
@@ -11231,13 +12796,13 @@ shell_expand_word_list (tlist, eflags)
   int expanded_something, has_dollar_at;
 
   /* We do tilde expansion all the time.  This is what 1003.2 says. */
-  new_list = (WORD_LIST *)NULL;
-  for (wcmd = tlist; wcmd; wcmd = wcmd->next)
-    if (wcmd->word->flags & W_ASSNBLTIN)
-      break;
+  wcmd = new_list = (WORD_LIST *)NULL;
 
   for (orig_list = tlist; tlist; tlist = next)
     {
+      if (wcmd == 0 && (tlist->word->flags & W_ASSNBLTIN))
+       wcmd = tlist;
+       
       next = tlist->next;
 
 #if defined (ARRAY_VARS)
@@ -11248,98 +12813,7 @@ shell_expand_word_list (tlist, eflags)
          because `declare' does some evaluation of compound assignments on
          its own. */
       if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG))
-       {
-         int t;
-         char opts[16];
-         int opti;
-
-         opti = 0;
-         if (tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL|W_CHKLOCAL|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 (tlist->word->flags & W_CHKLOCAL)
-           opts[opti++] = 'G';
-
-         /* If we have special handling note the integer attribute and others
-            that transform the value upon assignment.  What we do is take all
-            of the option arguments and scan through them looking for options
-            that cause such transformations, and add them to the `opts' array. */
-/*       if (opti > 0) */
-           {
-             char omap[128];
-             int oind;
-             WORD_LIST *l;
-
-             memset (omap, '\0', sizeof (omap));
-             for (l = orig_list->next; l != tlist; l = l->next)
-               {
-                 if (l->word->word[0] != '-')
-                   break;      /* non-option argument */
-                 if (l->word->word[0] == '-' && l->word->word[1] == '-' && l->word->word[2] == 0)
-                   break;      /* -- signals end of options */
-                 for (oind = 1; l->word->word[oind]; oind++)
-                   switch (l->word->word[oind])
-                     {
-                       case 'i':
-                       case 'l':
-                       case 'u':
-                       case 'c':
-                         omap[l->word->word[oind]] = 1;
-                         if (opti == 0)
-                           opts[opti++] = '-';
-                         break;
-                       default:
-                         break;
-                     }
-               }
-
-             for (oind = 0; oind < sizeof (omap); oind++)
-               if (omap[oind])
-                 opts[opti++] = oind;
-           }
-
-         opts[opti] = '\0';
-         if (opti > 0)
-           {
-             t = make_internal_declare (tlist->word->word, opts, wcmd ? wcmd->word->word : (char *)0);
-             if (t != EXECUTION_SUCCESS)
-               {
-                 last_command_exit_value = t;
-                 exp_jump_to_top_level (DISCARD);
-               }
-           }
-
-         t = do_word_assignment (tlist->word, 0);
-         if (t == 0)
-           {
-             last_command_exit_value = EXECUTION_FAILURE;
-             exp_jump_to_top_level (DISCARD);
-           }
-
-         /* Now transform the word as ksh93 appears to do and go on */
-         t = assignment (tlist->word->word, 0);
-         tlist->word->word[t] = '\0';
-         if (tlist->word->word[t - 1] == '+')
-           tlist->word->word[t - 1] = '\0';    /* cut off append op */
-         tlist->word->flags &= ~(W_ASSIGNMENT|W_NOSPLIT|W_COMPASSIGN|W_ASSIGNARG|W_ASSIGNASSOC|W_ASSIGNARRAY);
-       }
+       expand_declaration_argument (tlist, wcmd);
 #endif
 
       expanded_something = 0;
@@ -11393,6 +12867,87 @@ shell_expand_word_list (tlist, eflags)
   return (new_list);
 }
 
+/* Perform assignment statements optionally preceding a command name COMMAND.
+   If COMMAND == NULL, is_nullcmd usually == 1. Follow the POSIX rules for
+   variable assignment errors. */
+static int
+do_assignment_statements (varlist, command, is_nullcmd)
+     WORD_LIST *varlist;
+     char *command;
+     int is_nullcmd;
+{
+  WORD_LIST *temp_list;
+  char *savecmd;
+  sh_wassign_func_t *assign_func;
+  int is_special_builtin, is_builtin_or_func, tint;
+
+  /* If the remainder of the words expand to nothing, Posix.2 requires
+     that the variable and environment assignments affect the shell's
+     environment (do_word_assignment). */
+  assign_func = is_nullcmd ? do_word_assignment : assign_in_env;
+  tempenv_assign_error = 0;
+
+  is_builtin_or_func = command && (find_shell_builtin (command) || find_function (command));
+  /* Posix says that special builtins exit if a variable assignment error
+     occurs in an assignment preceding it. (XXX - this is old -- current Posix
+     says that any variable assignment error causes a non-interactive shell
+     to exit. See the STRICT_POSIX checks below. */
+  is_special_builtin = posixly_correct && command && find_special_builtin (command);
+
+  savecmd = this_command_name;
+  for (temp_list = varlist; temp_list; temp_list = temp_list->next)
+    {
+      this_command_name = (char *)NULL;
+      assigning_in_environment = is_nullcmd == 0;
+      tint = (*assign_func) (temp_list->word, is_builtin_or_func);
+      assigning_in_environment = 0;
+      this_command_name = savecmd;
+
+      /* Variable assignment errors in non-interactive shells running
+        in posix mode cause the shell to exit. */
+      if (tint == 0)
+       {
+         if (is_nullcmd)       /* assignment statement */
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+#if defined (STRICT_POSIX)
+             if (posixly_correct && interactive_shell == 0)
+#else
+             if (posixly_correct && interactive_shell == 0 && executing_command_builtin == 0)
+#endif
+               exp_jump_to_top_level (FORCE_EOF);
+             else
+               exp_jump_to_top_level (DISCARD);
+           }
+         /* In posix mode, assignment errors in the temporary environment
+            cause a non-interactive shell executing a special builtin to
+            exit and a non-interactive shell to otherwise jump back to the
+            top level. This is what POSIX says to do for variable assignment
+            errors, and POSIX says errors in assigning to the temporary
+            environment are treated as variable assignment errors.
+            (XXX - this is not what current POSIX says - look at the
+            STRICT_POSIX defines. */
+         else if (posixly_correct)
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+#if defined (STRICT_POSIX)
+             exp_jump_to_top_level ((interactive_shell == 0) ? FORCE_EOF : DISCARD);
+#else
+             if (interactive_shell == 0 && is_special_builtin)
+               exp_jump_to_top_level (FORCE_EOF);
+             else if (interactive_shell == 0)
+               exp_jump_to_top_level (DISCARD);        /* XXX - maybe change later */
+             else
+               exp_jump_to_top_level (DISCARD);
+#endif
+           }
+         else
+           tempenv_assign_error++;
+       }
+    }
+  return (tempenv_assign_error);
+}
+
 /* The workhorse for expand_words () and expand_words_no_vars ().
    First arg is LIST, a WORD_LIST of words.
    Second arg EFLAGS is a flags word controlling which expansions are
@@ -11412,8 +12967,6 @@ expand_word_list_internal (list, eflags)
      int eflags;
 {
   WORD_LIST *new_list, *temp_list;
-  int tint;
-  char *savecmd;
 
   tempenv_assign_error = 0;
   if (list == 0)
@@ -11426,30 +12979,11 @@ expand_word_list_internal (list, eflags)
       if (new_list == 0)
        {
          if (subst_assign_varlist)
-           {
-             /* All the words were variable assignments, so they are placed
-                into the shell's environment. */
-             for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
-               {
-                 savecmd = this_command_name;
-                 this_command_name = (char *)NULL;     /* no arithmetic errors */
-                 tint = do_word_assignment (temp_list->word, 0);
-                 this_command_name = savecmd;
-                 /* Variable assignment errors in non-interactive shells
-                    running in Posix.2 mode cause the shell to exit, unless
-                    they are being run by the `command' builtin. */
-                 if (tint == 0)
-                   {
-                     last_command_exit_value = EXECUTION_FAILURE;
-                     if (interactive_shell == 0 && posixly_correct && executing_command_builtin == 0)
-                       exp_jump_to_top_level (FORCE_EOF);
-                     else
-                       exp_jump_to_top_level (DISCARD);
-                   }
-               }
-             dispose_words (subst_assign_varlist);
-             subst_assign_varlist = (WORD_LIST *)NULL;
-           }
+           do_assignment_statements (subst_assign_varlist, (char *)NULL, 1);
+           
+         dispose_words (subst_assign_varlist);
+         subst_assign_varlist = (WORD_LIST *)NULL;
+
          return ((WORD_LIST *)NULL);
        }
     }
@@ -11483,49 +13017,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
-        environment. */
-      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)
-       {
-         savecmd = this_command_name;
-         this_command_name = (char *)NULL;
-         assigning_in_environment = (assign_func == assign_in_env);
-         tint = (*assign_func) (temp_list->word, is_builtin_or_func);
-         assigning_in_environment = 0;
-         this_command_name = savecmd;
-         /* Variable assignment errors in non-interactive shells running
-            in Posix.2 mode cause the shell to exit. */
-         if (tint == 0)
-           {
-             if (assign_func == do_word_assignment)
-               {
-                 last_command_exit_value = EXECUTION_FAILURE;
-                 if (interactive_shell == 0 && posixly_correct)
-                   exp_jump_to_top_level (FORCE_EOF);
-                 else
-                   exp_jump_to_top_level (DISCARD);
-               }
-             else if (interactive_shell == 0 && is_special_builtin)
-               {
-                 last_command_exit_value = EXECUTION_FAILURE;
-                 exp_jump_to_top_level (FORCE_EOF);
-               }
-             else
-               tempenv_assign_error++;
-           }
-       }
+      do_assignment_statements (subst_assign_varlist, (new_list && new_list->word) ? new_list->word->word : (char *)NULL, new_list == 0);
 
       dispose_words (subst_assign_varlist);
       subst_assign_varlist = (WORD_LIST *)NULL;