]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - subst.c
saved background process status hash table loop fixes
[thirdparty/bash.git] / subst.c
diff --git a/subst.c b/subst.c
index 48c89c1fab4b2b050b54bd2fdff91ea9a17d2bb7..fc00cab08fbb8c4ea2d6014c9de112627d1103c1 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-2010 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2016 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -37,6 +37,8 @@
 #  include <unistd.h>
 #endif
 
+#define NEED_FPURGE_DECL
+
 #include "bashansi.h"
 #include "posixstat.h"
 #include "bashintl.h"
@@ -52,6 +54,9 @@
 #include "mailcheck.h"
 
 #include "shmbutil.h"
+#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR)
+#  include <mbstr.h>           /* mbschr */
+#endif
 #include "typemax.h"
 
 #include "builtins/getopt.h"
@@ -85,26 +90,26 @@ extern int errno;
 #define ST_SQUOTE      0x04    /* unused yet */
 #define ST_DQUOTE      0x08    /* unused yet */
 
-/* Flags for the `pflags' argument to param_expand() */
-#define PF_NOCOMSUB    0x01    /* Do not perform command substitution */
-#define PF_IGNUNBOUND  0x02    /* ignore unbound vars even if -u set */
-#define PF_NOSPLIT2    0x04    /* same as W_NOSPLIT2 */
-
 /* These defs make it easier to use the editor. */
 #define LBRACE         '{'
 #define RBRACE         '}'
 #define LPAREN         '('
 #define RPAREN         ')'
+#define LBRACK         '['
+#define RBRACK         ']'
 
 #if defined (HANDLE_MULTIBYTE)
 #define WLPAREN                L'('
 #define WRPAREN                L')'
 #endif
 
+#define DOLLAR_AT_STAR(c)      ((c) == '@' || (c) == '*')
+#define STR_DOLLAR_AT_STAR(s)  (DOLLAR_AT_STAR ((s)[0]) && (s)[1] == '\0')
+
 /* Evaluates to 1 if C is one of the shell's special parameters whose length
    can be taken, but is also one of the special expansion characters. */
 #define VALID_SPECIAL_LENGTH_PARAM(c) \
-  ((c) == '-' || (c) == '?' || (c) == '#')
+  ((c) == '-' || (c) == '?' || (c) == '#' || (c) == '@')
 
 /* Evaluates to 1 if C is one of the shell's special parameters for which an
    indirect variable reference may be made. */
@@ -117,9 +122,24 @@ extern int errno;
 
 /* Evaluates to 1 if this is one of the shell's special variables. */
 #define SPECIAL_VAR(name, wi) \
- ((DIGIT (*name) && all_digits (name)) || \
+ (*name && ((DIGIT (*name) && all_digits (name)) || \
       (name[1] == '\0' && (sh_syntaxtab[(unsigned char)*name] & CSPECVAR)) || \
-      (wi && name[2] == '\0' && VALID_INDIR_PARAM (name[1])))
+      (wi && name[2] == '\0' && VALID_INDIR_PARAM (name[1]))))
+
+/* This can be used by all of the *_extract_* functions that have a similar
+   structure.  It can't just be wrapped in a do...while(0) loop because of
+   the embedded `break'. The dangling else accommodates a trailing semicolon;
+   we could also put in a do ; while (0) */
+
+   
+#define CHECK_STRING_OVERRUN(oind, ind, len, ch) \
+  if (ind >= len) \
+    { \
+      oind = len; \
+      ch = 0; \
+      break; \
+    } \
+  else \
 
 /* 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
@@ -134,6 +154,7 @@ pid_t current_command_subst_pid = NO_PID;
 SHELL_VAR *ifs_var;
 char *ifs_value;
 unsigned char ifs_cmap[UCHAR_MAX + 1];
+int ifs_is_set, ifs_is_null;
 
 #if defined (HANDLE_MULTIBYTE)
 unsigned char ifs_firstc[MB_LEN_MAX];
@@ -142,6 +163,9 @@ size_t ifs_firstc_len;
 unsigned char ifs_firstc;
 #endif
 
+/* If non-zero, command substitution inherits the value of errexit option */
+int inherit_errexit = 0;
+
 /* Sentinel to tell when we are performing variable assignments preceding a
    command name and putting them into the environment.  Used to make sure
    we use the temporary environment when looking up variable values. */
@@ -152,11 +176,15 @@ int assigning_in_environment;
    SIGCHLD trap and so it can be saved and restored by the trap handlers. */
 WORD_LIST *subst_assign_varlist = (WORD_LIST *)NULL;
 
+/* Tell the expansion functions to not longjmp back to top_level on fatal
+   errors.  Enabled when doing completion and prompt string expansion. */
+int no_longjmp_on_fatal_error = 0;
+
 /* Extern functions and variables from different files. */
 extern int last_command_exit_value, last_command_exit_signal;
-extern int subshell_environment, line_number;
+extern int subshell_environment, running_in_background;
 extern int subshell_level, parse_and_execute_level, sourcelevel;
-extern int eof_encountered;
+extern int eof_encountered, line_number;
 extern int return_catch_flag, return_catch_value;
 extern pid_t dollar_dollar_pid;
 extern int posixly_correct;
@@ -165,6 +193,11 @@ extern struct fd_bitmap *current_fds_to_close;
 extern int wordexp_only;
 extern int expanding_redir;
 extern int tempenv_assign_error;
+extern int builtin_ignoring_errexit;
+
+#if defined (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 *));
@@ -186,15 +219,13 @@ char *glob_argv_flags;
 static int glob_argv_flags_size;
 #endif
 
+static WORD_LIST *cached_quoted_dollar_at = 0;
+
 static WORD_LIST expand_word_error, expand_word_fatal;
 static WORD_DESC expand_wdesc_error, expand_wdesc_fatal;
 static char expand_param_error, expand_param_fatal;
 static char extract_string_error, extract_string_fatal;
 
-/* Tell the expansion functions to not longjmp back to top_level on fatal
-   errors.  Enabled when doing completion and prompt string expansion. */
-static int no_longjmp_on_fatal_error = 0;
-
 /* Set by expand_word_unsplit; used to inhibit splitting and re-joining
    $* on $IFS, primarily when doing assignment statements. */
 static int expand_no_split_dollar_star = 0;
@@ -215,6 +246,8 @@ static WORD_LIST *expand_string_leave_quoted __P((char *, int));
 static WORD_LIST *expand_string_for_rhs __P((char *, int, int *, int *));
 
 static WORD_LIST *list_quote_escapes __P((WORD_LIST *));
+static WORD_LIST *list_dequote_escapes __P((WORD_LIST *));
+
 static char *make_quoted_char __P((int));
 static WORD_LIST *quote_list __P((WORD_LIST *));
 
@@ -230,8 +263,8 @@ 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));
-static int skip_double_quoted __P((char *, size_t, 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));
@@ -261,6 +294,19 @@ static char *array_remove_pattern __P((SHELL_VAR *, char *, int, char *, int));
 #endif
 static char *parameter_brace_remove_pattern __P((char *, char *, int, char *, int, int, int));
 
+static char *string_var_assignment __P((SHELL_VAR *, char *));
+#if defined (ARRAY_VARS)
+static char *array_var_assignment __P((SHELL_VAR *, 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));
+#if defined ARRAY_VARS
+static char *array_transform __P((int, SHELL_VAR *, char *, int));
+#endif
+static char *parameter_brace_transform __P((char *, char *, int, char *, int, int, int));
+
 static char *process_substitute __P((char *, int));
 
 static char *read_comsub __P((int, int, int *));
@@ -274,8 +320,9 @@ static int chk_atstar __P((char *, int, int *, int *));
 static int chk_arithsub __P((const char *, int));
 
 static WORD_DESC *parameter_brace_expand_word __P((char *, int, int, int, arrayind_t *));
+static char *parameter_brace_find_indir __P((char *, int, int, int));
 static WORD_DESC *parameter_brace_expand_indir __P((char *, int, int, int *, int *));
-static WORD_DESC *parameter_brace_expand_rhs __P((char *, char *, int, int, int *, int *));
+static WORD_DESC *parameter_brace_expand_rhs __P((char *, char *, int, int, int, int *, int *));
 static void parameter_brace_expand_error __P((char *, char *));
 
 static int valid_length_expression __P((char *));
@@ -291,7 +338,7 @@ static int shouldexp_replacement __P((char *));
 
 static char *pos_params_pat_subst __P((char *, char *, char *, int));
 
-static char *parameter_brace_patsub __P((char *, char *, int, char *, int, int));
+static char *parameter_brace_patsub __P((char *, char *, int, 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));
@@ -311,7 +358,7 @@ static WORD_LIST *glob_expand_word_list __P((WORD_LIST *, int));
 static WORD_LIST *brace_expand_word_list __P((WORD_LIST *, int));
 #endif
 #if defined (ARRAY_VARS)
-static int make_internal_declare __P((char *, char *));
+static int make_internal_declare __P((char *, char *, char *));
 #endif
 static WORD_LIST *shell_expand_word_list __P((WORD_LIST *, int));
 static WORD_LIST *expand_word_list_internal __P((WORD_LIST *, int));
@@ -331,11 +378,21 @@ dump_word_flags (flags)
 
   f = flags;
   fprintf (stderr, "%d -> ", f);
+  if (f & W_ARRAYIND)
+    {
+      f &= ~W_ARRAYIND;
+      fprintf (stderr, "W_ARRAYIND%s", f ? "|" : "");
+    }
   if (f & W_ASSIGNASSOC)
     {
       f &= ~W_ASSIGNASSOC;
       fprintf (stderr, "W_ASSIGNASSOC%s", f ? "|" : "");
     }
+  if (f & W_ASSIGNARRAY)
+    {
+      f &= ~W_ASSIGNARRAY;
+      fprintf (stderr, "W_ASSIGNARRAY%s", f ? "|" : "");
+    }
   if (f & W_HASCTLESC)
     {
       f &= ~W_HASCTLESC;
@@ -421,20 +478,25 @@ dump_word_flags (flags)
       f &= ~W_NOSPLIT2;
       fprintf (stderr, "W_NOSPLIT2%s", f ? "|" : "");
     }
-  if (f & W_NOGLOB)
-    {
-      f &= ~W_NOGLOB;
-      fprintf (stderr, "W_NOGLOB%s", f ? "|" : "");
-    }
   if (f & W_NOSPLIT)
     {
       f &= ~W_NOSPLIT;
       fprintf (stderr, "W_NOSPLIT%s", f ? "|" : "");
     }
-  if (f & W_GLOBEXP)
+  if (f & W_NOBRACE)
+    {
+      f &= ~W_NOBRACE;
+      fprintf (stderr, "W_NOBRACE%s", f ? "|" : "");
+    }
+  if (f & W_NOGLOB)
+    {
+      f &= ~W_NOGLOB;
+      fprintf (stderr, "W_NOGLOB%s", f ? "|" : "");
+    }
+  if (f & W_SPLITSPACE)
     {
-      f &= ~W_GLOBEXP;
-      fprintf (stderr, "W_GLOBEXP%s", f ? "|" : "");
+      f &= ~W_SPLITSPACE;
+      fprintf (stderr, "W_SPLITSPACE%s", f ? "|" : "");
     }
   if (f & W_ASSIGNMENT)
     {
@@ -451,6 +513,12 @@ dump_word_flags (flags)
       f &= ~W_HASDOLLAR;
       fprintf (stderr, "W_HASDOLLAR%s", f ? "|" : "");
     }
+  if (f & W_COMPLETE)
+    {
+      f &= ~W_COMPLETE;
+      fprintf (stderr, "W_COMPLETE%s", f ? "|" : "");
+    }
+  
   fprintf (stderr, "\n");
   fflush (stderr);
 }
@@ -550,6 +618,7 @@ quoted_strchr (s, c, flags)
   return ((char *)NULL);
 }
 
+#if defined (INCLUDE_UNUSED)
 /* Return 1 if CHARACTER appears in an unquoted portion of
    STRING.  Return 0 otherwise.  CHARACTER must be a single-byte character. */
 static int
@@ -581,11 +650,11 @@ unquoted_member (character, string)
          break;
 
        case '\'':
-         sindex = skip_single_quoted (string, slen, ++sindex);
+         sindex = skip_single_quoted (string, slen, ++sindex, 0);
          break;
 
        case '"':
-         sindex = skip_double_quoted (string, slen, ++sindex);
+         sindex = skip_double_quoted (string, slen, ++sindex, 0);
          break;
        }
     }
@@ -620,11 +689,11 @@ unquoted_substring (substr, string)
          break;
 
        case '\'':
-         sindex = skip_single_quoted (string, slen, ++sindex);
+         sindex = skip_single_quoted (string, slen, ++sindex, 0);
          break;
 
        case '"':
-         sindex = skip_double_quoted (string, slen, ++sindex);
+         sindex = skip_double_quoted (string, slen, ++sindex, 0);
          break;
 
        default:
@@ -634,6 +703,7 @@ unquoted_substring (substr, string)
     }
   return (0);
 }
+#endif
 
 /* Most of the substitutions must be done in parallel.  In order
    to avoid using tons of unclear goto's, I have some functions
@@ -650,11 +720,13 @@ unquoted_substring (substr, string)
 INLINE char *
 sub_append_string (source, target, indx, size)
      char *source, *target;
-     int *indx, *size;
+     int *indx;
+     size_t *size;
 {
   if (source)
     {
-      int srclen, n;
+      int n;
+      size_t srclen;
 
       srclen = STRLEN (source);
       if (srclen >= (int)(*size - *indx))
@@ -680,8 +752,9 @@ sub_append_string (source, target, indx, size)
 char *
 sub_append_number (number, target, indx, size)
      intmax_t number;
-     int *indx, *size;
      char *target;
+     int *indx;
+     size_t *size;
 {
   char *temp;
 
@@ -765,9 +838,9 @@ string_extract (string, sindex, charlist, flags)
    Backslashes between the embedded double quotes are processed.  If STRIPDQ
    is zero, an unquoted `"' terminates the string. */
 static char *
-string_extract_double_quoted (string, sindex, stripdq)
+string_extract_double_quoted (string, sindex, flags)
      char *string;
-     int *sindex, stripdq;
+     int *sindex, flags;
 {
   size_t slen;
   char *send;
@@ -776,11 +849,14 @@ string_extract_double_quoted (string, sindex, stripdq)
   char *temp, *ret;            /* The new string we return. */
   int pass_next, backquote, si;        /* State variables for the machine. */
   int dquote;
+  int stripdq;
   DECLARE_MBSTATE;
 
   slen = strlen (string + *sindex) + *sindex;
   send = string + slen;
 
+  stripdq = (flags & SX_STRIPDQ);
+
   pass_next = backquote = dquote = 0;
   temp = (char *)xmalloc (1 + slen - *sindex);
 
@@ -863,7 +939,7 @@ add_one_character:
 
          si = i + 2;
          if (string[i + 1] == LPAREN)
-           ret = extract_command_subst (string, &si, 0);
+           ret = extract_command_subst (string, &si, (flags & SX_COMPLETE));
          else
            ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, 0);
 
@@ -922,10 +998,11 @@ add_one_character:
 
 /* This should really be another option to string_extract_double_quoted. */
 static int
-skip_double_quoted (string, slen, sind)
+skip_double_quoted (string, slen, sind, flags)
      char *string;
      size_t slen;
      int sind;
+     int flags;
 {
   int c, i;
   char *ret;
@@ -965,10 +1042,13 @@ skip_double_quoted (string, slen, sind)
        {
          si = i + 2;
          if (string[i + 1] == LPAREN)
-           ret = extract_command_subst (string, &si, SX_NOALLOC);
+           ret = extract_command_subst (string, &si, SX_NOALLOC|(flags&SX_COMPLETE));
          else
            ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, SX_NOALLOC);
 
+         /* These can consume the entire string if they are unterminated */
+         CHECK_STRING_OVERRUN (i, si, slen, c);
+
          i = si + 1;
          continue;
        }
@@ -1016,18 +1096,26 @@ string_extract_single_quoted (string, sindex)
   return (t);
 }
 
+/* Skip over a single-quoted string.  We overload the SX_COMPLETE flag to mean
+   that we are splitting out words for completion and have encountered a $'...'
+   string, which allows backslash-escaped single quotes. */
 static inline int
-skip_single_quoted (string, slen, sind)
+skip_single_quoted (string, slen, sind, flags)
      const char *string;
      size_t slen;
      int sind;
+     int flags;
 {
   register int c;
   DECLARE_MBSTATE;
 
   c = sind;
   while (string[c] && string[c] != '\'')
-    ADVANCE_CHAR (string, slen, c);
+    {
+      if ((flags & SX_COMPLETE) && string[c] == '\\' && string[c+1] == '\'' && string[c+2])
+       ADVANCE_CHAR (string, slen, c);
+      ADVANCE_CHAR (string, slen, c);
+    }
 
   if (string[c])
     c++;
@@ -1053,7 +1141,7 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
   char *temp;
   DECLARE_MBSTATE;
 
-  if (charlist[0] == '\'' && charlist[1] == '\0')
+  if ((flags & SX_NOCTLESC) && charlist[0] == '\'' && charlist[1] == '\0')
     {
       temp = string_extract_single_quoted (string, sindex);
       --*sindex;       /* leave *sindex at separator character */
@@ -1144,12 +1232,15 @@ extract_command_subst (string, sindex, xflags)
      int *sindex;
      int xflags;
 {
-  if (string[*sindex] == LPAREN)
+  char *ret;
+
+  if (string[*sindex] == LPAREN || (xflags & SX_COMPLETE))
     return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/
   else
     {
       xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
-      return (xparse_dolparen (string, string+*sindex, sindex, xflags));
+      ret = xparse_dolparen (string, string+*sindex, sindex, xflags);
+      return ret;
     }
 }
 
@@ -1169,12 +1260,18 @@ extract_arithmetic_subst (string, sindex)
    Start extracting at (SINDEX) as if we had just seen "<(".
    Make (SINDEX) get the position of the matching ")". */ /*))*/
 char *
-extract_process_subst (string, starter, sindex)
+extract_process_subst (string, starter, sindex, xflags)
      char *string;
      char *starter;
      int *sindex;
+     int xflags;
 {
-  return (extract_delimited_string (string, sindex, starter, "(", ")", 0));
+#if 0
+  return (extract_delimited_string (string, sindex, starter, "(", ")", SX_COMMAND));
+#else
+  xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
+  return (xparse_dolparen (string, string+*sindex, sindex, xflags));
+#endif
 }
 #endif /* PROCESS_SUBSTITUTION */
 
@@ -1237,6 +1334,15 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
     {
       c = string[i];
 
+      /* If a recursive call or a call to ADVANCE_CHAR leaves the index beyond
+        the end of the string, catch it and cut the loop. */
+      if (i > slen)
+       {
+         i = slen;
+         c = string[i = slen];
+         break;
+       }
+
       if (c == 0)
        break;
 
@@ -1322,8 +1428,8 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
       if (c == '\'' || c == '"')
        {
          si = i + 1;
-         i = (c == '\'') ? skip_single_quoted (string, slen, si)
-                         : skip_double_quoted (string, slen, si);
+         i = (c == '\'') ? skip_single_quoted (string, slen, si, 0)
+                         : skip_double_quoted (string, slen, si, 0);
          continue;
        }
 
@@ -1335,8 +1441,8 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
     {
       if (no_longjmp_on_fatal_error == 0)
        {
-         report_error (_("bad substitution: no closing `%s' in %s"), closer, string);
          last_command_exit_value = EXECUTION_FAILURE;
+         report_error (_("bad substitution: no closing `%s' in %s"), closer, string);
          exp_jump_to_top_level (DISCARD);
        }
       else
@@ -1431,6 +1537,9 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
        {
          si = i + 1;
          t = string_extract (string, &si, "`", flags|SX_NOALLOC);
+
+         CHECK_STRING_OVERRUN (i, si, slen, c);
+    
          i = si + 1;
          continue;
        }
@@ -1445,23 +1554,11 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
          continue;
        }
 
-#if 0
-      /* Pass the contents of single-quoted and double-quoted strings
-        through verbatim. */
-      if (c == '\'' || c == '"')
-       {
-         si = i + 1;
-         i = (c == '\'') ? skip_single_quoted (string, slen, si)
-                         : skip_double_quoted (string, slen, si);
-         /* skip_XXX_quoted leaves index one past close quote */
-         continue;
-       }
-#else  /* XXX - bash-4.2 */
       /* Pass the contents of double-quoted strings through verbatim. */
       if (c == '"')
        {
          si = i + 1;
-         i = skip_double_quoted (string, slen, si);
+         i = skip_double_quoted (string, slen, si, 0);
          /* skip_XXX_quoted leaves index one past close quote */
          continue;
        }
@@ -1469,17 +1566,16 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
       if (c == '\'')
        {
 /*itrace("extract_dollar_brace_string: c == single quote flags = %d quoted = %d dolbrace_state = %d", flags, quoted, dolbrace_state);*/
-         if (posixly_correct && shell_compatibility_level > 41 && dolbrace_state != DOLBRACE_QUOTE && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+         if (posixly_correct && shell_compatibility_level > 42 && dolbrace_state != DOLBRACE_QUOTE && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
            ADVANCE_CHAR (string, slen, i);
          else
            {
              si = i + 1;
-             i = skip_single_quoted (string, slen, si);
+             i = skip_single_quoted (string, slen, si, 0);
            }
 
           continue;
        }
-#endif
 
       /* move past this character, which was not special. */
       ADVANCE_CHAR (string, slen, i);
@@ -1491,11 +1587,13 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
       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;
+        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)
@@ -1506,8 +1604,8 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
     {
       if (no_longjmp_on_fatal_error == 0)
        {                       /* { */
-         report_error (_("bad substitution: no closing `%s' in %s"), "}", string);
          last_command_exit_value = EXECUTION_FAILURE;
+         report_error (_("bad substitution: no closing `%s' in %s"), "}", string);
          exp_jump_to_top_level (DISCARD);
        }
       else
@@ -1579,7 +1677,7 @@ unquote_bang (string)
 }
 #endif
 
-#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0)
+#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
@@ -1591,12 +1689,13 @@ skip_matched_pair (string, start, open, close, flags)
      const char *string;
      int start, open, close, flags;
 {
-  int i, pass_next, backq, si, c, count;
+  int i, pass_next, backq, si, c, count, oldjmp;
   size_t slen;
   char *temp, *ss;
   DECLARE_MBSTATE;
 
   slen = strlen (string + start) + start;
+  oldjmp = no_longjmp_on_fatal_error;
   no_longjmp_on_fatal_error = 1;
 
   i = start + 1;               /* skip over leading bracket */
@@ -1648,8 +1747,8 @@ skip_matched_pair (string, start, open, close, flags)
        }
       else if ((flags & 1) == 0 && (c == '\'' || c == '"'))
        {
-         i = (c == '\'') ? skip_single_quoted (ss, slen, ++i)
-                         : skip_double_quoted (ss, slen, ++i);
+         i = (c == '\'') ? skip_single_quoted (ss, slen, ++i, 0)
+                         : 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))
@@ -1662,6 +1761,9 @@ skip_matched_pair (string, start, open, close, flags)
            temp = extract_delimited_string (ss, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
          else
            temp = extract_dollar_brace_string (ss, &si, 0, SX_NOALLOC);
+
+         CHECK_STRING_OVERRUN (i, si, slen, c);
+
          i = si;
          if (string[i] == '\0')        /* don't increment i past EOS in loop */
            break;
@@ -1698,19 +1800,27 @@ skip_to_delim (string, start, delims, flags)
      char *delims;
      int flags;
 {
-  int i, pass_next, backq, si, c, invert, skipquote, skipcmd;
+  int i, pass_next, backq, dquote, si, c, oldjmp;
+  int invert, skipquote, skipcmd, noprocsub, completeflag;
+  int arithexp, skipcol;
   size_t slen;
   char *temp, open[3];
   DECLARE_MBSTATE;
 
   slen = strlen (string + start) + start;
+  oldjmp = no_longjmp_on_fatal_error;
   if (flags & SD_NOJMP)
     no_longjmp_on_fatal_error = 1;
   invert = (flags & SD_INVERT);
   skipcmd = (flags & SD_NOSKIPCMD) == 0;
+  noprocsub = (flags & SD_NOPROCSUB);
+  completeflag = (flags & SD_COMPLETE) ? SX_COMPLETE : 0;
+
+  arithexp = (flags & SD_ARITHEXP);
+  skipcol = 0;
 
   i = start;
-  pass_next = backq = 0;
+  pass_next = backq = dquote = 0;
   while (c = string[i])
     {
       /* If this is non-zero, we should not let quote characters be delimiters
@@ -1745,14 +1855,45 @@ skip_to_delim (string, start, delims, flags)
          i++;
          continue;
        }
-      else if (skipquote == 0 && invert == 0 && member (c, delims))
-       break;
-      else if (c == '\'' || c == '"')
+      else if (arithexp && skipcol && c == ':')
        {
-         i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
-                         : skip_double_quoted (string, slen, ++i);
-         /* no increment, the skip functions increment past the closing quote. */
+         skipcol--;
+         i++;
+         continue;
+       }
+      else if (arithexp && c == '?')
+       {
+         skipcol++;
+         i++;
+         continue;
        }
+      else if (skipquote == 0 && invert == 0 && member (c, delims))
+       break;
+      /* the usual case is to use skip_xxx_quoted, but we don't skip over double
+        quoted strings when looking for the history expansion character as a
+        delimiter. */
+      /* special case for programmable completion which takes place before
+         parser converts backslash-escaped single quotes between $'...' to
+         `regular' single-quoted strings. */
+      else if (completeflag && i > 0 && string[i-1] == '$' && c == '\'')
+       i = skip_single_quoted (string, slen, ++i, SX_COMPLETE);
+      else if (c == '\'')
+       i = skip_single_quoted (string, slen, ++i, 0);
+      else if (c == '"')
+       i = skip_double_quoted (string, slen, ++i, completeflag);
+      else if (c == LPAREN && arithexp)
+        {
+          si = i + 1;
+          if (string[si] == '\0')
+           CQ_RETURN(si);
+
+         temp = extract_delimited_string (string, &si, "(", "(", ")", SX_NOALLOC); /* ) */
+         i = si;
+         if (string[i] == '\0')        /* don't increment i past EOS in loop */
+           break;
+         i++;
+         continue;         
+        }
       else if (c == '$' && ((skipcmd && string[i+1] == LPAREN) || string[i+1] == LBRACE))
        {
          si = i + 2;
@@ -1770,12 +1911,17 @@ skip_to_delim (string, start, delims, flags)
          continue;
        }
 #if defined (PROCESS_SUBSTITUTION)
-      else if (skipcmd && (c == '<' || c == '>') && string[i+1] == LPAREN)
+      else if (skipcmd && noprocsub == 0 && (c == '<' || c == '>') && string[i+1] == LPAREN)
        {
          si = i + 2;
          if (string[si] == '\0')
            CQ_RETURN(si);
-         temp = extract_process_subst (string, (c == '<') ? "<(" : ">(", &si);
+#if 1
+         temp = extract_delimited_string (string, &si, (c == '<') ? "<(" : ">(", "(", ")", SX_COMMAND|SX_NOALLOC); /* )) */
+#else
+         temp = extract_process_subst (string, (c == '<') ? "<(" : ">(", &si, 0);
+         free (temp);          /* XXX - not using SX_NOALLOC here yet */
+#endif
          i = si;
          if (string[i] == '\0')
            break;
@@ -1802,6 +1948,20 @@ skip_to_delim (string, start, delims, flags)
          continue;
        }
 #endif
+      else if ((flags & SD_GLOB) && c == LBRACK)
+       {
+         si = i + 1;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+
+         temp = extract_delimited_string (string, &si, "[", "[", "]", SX_NOALLOC); /* ] */
+
+         i = si;
+         if (string[i] == '\0')        /* don't increment i past EOS in loop */
+           break;
+         i++;
+         continue;
+       }
       else if ((skipquote || invert) && (member (c, delims) == 0))
        break;
       else
@@ -1811,6 +1971,128 @@ skip_to_delim (string, start, delims, flags)
   CQ_RETURN(i);
 }
 
+#if defined (BANG_HISTORY)
+/* Skip to the history expansion character (delims[0]), paying attention to
+   quoted strings and command and process substitution.  This is a stripped-
+   down version of skip_to_delims.  The essential difference is that this
+   resets the quoting state when starting a command substitution */
+int
+skip_to_histexp (string, start, delims, flags)
+     char *string;
+     int start;
+     char *delims;
+     int flags;
+{
+  int i, pass_next, backq, dquote, si, c, oldjmp;
+  int histexp_comsub, histexp_backq, old_dquote;
+  size_t slen;
+  char *temp, open[3];
+  DECLARE_MBSTATE;
+
+  slen = strlen (string + start) + start;
+  oldjmp = no_longjmp_on_fatal_error;
+  if (flags & SD_NOJMP)
+    no_longjmp_on_fatal_error = 1;
+
+  histexp_comsub = histexp_backq = old_dquote = 0;
+
+  i = start;
+  pass_next = backq = dquote = 0;
+  while (c = string[i])
+    {
+      if (pass_next)
+       {
+         pass_next = 0;
+         if (c == 0)
+           CQ_RETURN(i);
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (c == '\\')
+       {
+         pass_next = 1;
+         i++;
+         continue;
+       }
+      else if (backq && c == '`')
+       {
+         backq = 0;
+         histexp_backq--;
+         dquote = old_dquote;
+         i++;
+         continue;
+       }
+      else if (c == '`')
+       {
+         backq = 1;
+         histexp_backq++;
+         old_dquote = dquote;          /* simple - one level for now */
+         dquote = 0;
+         i++;
+         continue;
+       }
+      /* When in double quotes, act as if the double quote is a member of
+        history_no_expand_chars, like the history library does */
+      else if (dquote && c == delims[0] && string[i+1] == '"')
+       {
+         i++;
+         continue;
+       }
+      else if (c == delims[0])
+       break;
+      /* the usual case is to use skip_xxx_quoted, but we don't skip over double
+        quoted strings when looking for the history expansion character as a
+        delimiter. */
+      else if (dquote && c == '\'')
+        {
+          i++;
+          continue;
+        }
+      else if (c == '\'')
+       i = skip_single_quoted (string, slen, ++i, 0);
+      /* The posixly_correct test makes posix-mode shells allow double quotes
+        to quote the history expansion character */
+      else if (posixly_correct == 0 && c == '"')
+       {
+         dquote = 1 - dquote;
+         i++;
+         continue;
+       }     
+      else if (c == '"')
+       i = skip_double_quoted (string, slen, ++i, 0);
+#if defined (PROCESS_SUBSTITUTION)
+      else if ((c == '$' || c == '<' || c == '>') && string[i+1] == LPAREN && string[i+2] != LPAREN)
+#else
+      else if (c == '$' && string[i+1] == LPAREN && string[i+2] != LPAREN)
+#endif
+        {
+         if (string[i+2] == '\0')
+           CQ_RETURN(i+2);
+         i += 2;
+         histexp_comsub++;
+         old_dquote = dquote;
+         dquote = 0;
+        }
+      else if (histexp_comsub && c == RPAREN)
+       {
+         histexp_comsub--;
+         dquote = old_dquote;
+         i++;
+         continue;
+       }
+      else if (backq)          /* placeholder */
+       {
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else
+       ADVANCE_CHAR (string, slen, i);
+    }
+
+  CQ_RETURN(i);
+}
+#endif /* BANG_HISTORY */
+
 #if defined (READLINE)
 /* Return 1 if the portion of STRING ending at EINDEX is quoted (there is
    an unclosed quoted string), or if the character at EINDEX is quoted
@@ -1825,11 +2107,12 @@ char_is_quoted (string, eindex)
      char *string;
      int eindex;
 {
-  int i, pass_next, c;
+  int i, pass_next, c, oldjmp;
   size_t slen;
   DECLARE_MBSTATE;
 
   slen = strlen (string);
+  oldjmp = no_longjmp_on_fatal_error;
   no_longjmp_on_fatal_error = 1;
   i = pass_next = 0;
   while (i <= eindex)
@@ -1850,10 +2133,17 @@ char_is_quoted (string, eindex)
          i++;
          continue;
        }
+      else if (c == '$' && string[i+1] == '\'' && string[i+2])
+       {
+         i += 2;
+         i = skip_single_quoted (string, slen, i, SX_COMPLETE);
+         if (i > eindex)
+           CQ_RETURN (i);
+       }
       else if (c == '\'' || c == '"')
        {
-         i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
-                         : skip_double_quoted (string, slen, ++i);
+         i = (c == '\'') ? skip_single_quoted (string, slen, ++i, 0)
+                         : skip_double_quoted (string, slen, ++i, SX_COMPLETE);
          if (i > eindex)
            CQ_RETURN(1);
          /* no increment, the skip_xxx functions go one past end */
@@ -1899,10 +2189,11 @@ unclosed_pair (string, eindex, openstr)
          openc = 1 - openc;
          i += olen;
        }
+      /* XXX - may want to handle $'...' specially here */
       else if (string[i] == '\'' || string[i] == '"')
        {
-         i = (string[i] == '\'') ? skip_single_quoted (string, slen, i)
-                                 : skip_double_quoted (string, slen, i);
+         i = (string[i] == '\'') ? skip_single_quoted (string, slen, i, 0)
+                                 : skip_double_quoted (string, slen, i, SX_COMPLETE);
          if (i > eindex)
            return 0;
        }
@@ -1989,7 +2280,10 @@ split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp)
   for (i = 0; member (string[i], d) && spctabnl (string[i]); i++)
     ;
   if (string[i] == '\0')
-    return (ret);
+    {
+      FREE (d2);
+      return (ret);
+    }
 
   ts = i;
   nw = 0;
@@ -2074,6 +2368,8 @@ split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp)
   if (cwp)
     *cwp = cw;
 
+  FREE (d2);
+
   return (REVERSE_LIST (ret, WORD_LIST *));
 }
 #endif /* READLINE */
@@ -2111,7 +2407,7 @@ string_list_internal (list, sep)
 {
   register WORD_LIST *t;
   char *result, *r;
-  int word_len, sep_len, result_size;
+  size_t word_len, sep_len, result_size;
 
   if (list == 0)
     return ((char *)NULL);
@@ -2254,11 +2550,18 @@ string_list_dollar_star (list)
    <space><tab><newline>, IFS characters in the words in the list should
    also be split.  If IFS is null, and the word is not quoted, we need
    to quote the words in the list to preserve the positional parameters
-   exactly. */
+   exactly.
+   Valid values for the FLAGS argument are the PF_ flags in command.h,
+   the only one we care about is PF_ASSIGNRHS.  $@ is supposed to expand
+   to the positional parameters separated by spaces no matter what IFS is
+   set to if in a context where word splitting is not performed.  The only
+   one that we didn't handle before is assignment statement arguments to
+   declaration builtins like `declare'. */
 char *
-string_list_dollar_at (list, quoted)
+string_list_dollar_at (list, quoted, flags)
      WORD_LIST *list;
      int quoted;
+     int flags;
 {
   char *ifs, *ret;
 #if defined (HANDLE_MULTIBYTE)
@@ -2279,7 +2582,13 @@ string_list_dollar_at (list, quoted)
 #  if !defined (__GNUC__)
   sep = (char *)xmalloc (MB_CUR_MAX + 1);
 #  endif /* !__GNUC__ */
-  if (ifs && *ifs)
+  /* XXX - bash-4.4/bash-5.0 testing PF_ASSIGNRHS */
+  if (flags & PF_ASSIGNRHS)
+    {
+      sep[0] = ' ';
+      sep[1] = '\0';
+    }
+  else if (ifs && *ifs)
     {
       if (ifs_firstc_len == 1)
        {
@@ -2298,7 +2607,8 @@ string_list_dollar_at (list, quoted)
       sep[1] = '\0';
     }
 #else
-  sep[0] = (ifs == 0 || *ifs == 0) ? ' ' : *ifs;
+  /* XXX - bash-4.4/bash-5.0 test PF_ASSIGNRHS */
+  sep[0] = ((flags & PF_ASSIGNRHS) || ifs == 0 || *ifs == 0) ? ' ' : *ifs;
   sep[1] = '\0';
 #endif
 
@@ -2315,7 +2625,7 @@ string_list_dollar_at (list, quoted)
   return ret;
 }
 
-/* Turn the positional paramters into a string, understanding quoting and
+/* Turn the positional parameters into a string, understanding quoting and
    the various subtleties of using the first character of $IFS as the
    separator.  Calls string_list_dollar_at, string_list_dollar_star, and
    string_list as appropriate. */
@@ -2355,7 +2665,7 @@ string_list_pos_params (pchar, list, quoted)
        string_list_dollar_star if the string is unquoted so we make sure that
        the elements of $@ are separated by the first character of $IFS for
        later splitting. */
-    ret = string_list_dollar_at (list, quoted);
+    ret = string_list_dollar_at (list, quoted, 0);
   else if (pchar == '@')
     ret = string_list_dollar_star (list);
   else
@@ -2439,7 +2749,7 @@ list_string (string, separators, quoted)
        extract a word, stopping at a separator
        skip sequences of spc, tab, or nl as long as they are separators
      This obeys the field splitting rules in Posix.2. */
-  slen = (MB_CUR_MAX > 1) ? strlen (string) : 1;
+  slen = (MB_CUR_MAX > 1) ? STRLEN (string) : 1;
   for (result = (WORD_LIST *)NULL, sindex = 0; string[sindex]; )
     {
       /* Don't need string length in ADVANCE_CHAR or string_extract_verbatim
@@ -2515,11 +2825,15 @@ list_string (string, separators, quoted)
 
 /* Parse a single word from STRING, using SEPARATORS to separate fields.
    ENDPTR is set to the first character after the word.  This is used by
-   the `read' builtin.  This is never called with SEPARATORS != $IFS;
-   it should be simplified.
+   the `read' builtin.
+   
+   This is never called with SEPARATORS != $IFS, and takes advantage of that.
 
    XXX - this function is very similar to list_string; they should be
         combined - XXX */
+
+#define islocalsep(c)  (local_cmap[(unsigned char)(c)] != 0)
+
 char *
 get_word_from_string (stringp, separators, endptr)
      char **stringp, *separators, **endptr;
@@ -2527,6 +2841,7 @@ get_word_from_string (stringp, separators, endptr)
   register char *s;
   char *current_word;
   int sindex, sh_style_split, whitesep, xflags;
+  unsigned char local_cmap[UCHAR_MAX+1];       /* really only need single-byte chars here */
   size_t slen;
 
   if (!stringp || !*stringp || !**stringp)
@@ -2536,20 +2851,23 @@ get_word_from_string (stringp, separators, endptr)
                                 separators[1] == '\t' &&
                                 separators[2] == '\n' &&
                                 separators[3] == '\0';
-  for (xflags = 0, s = ifs_value; s && *s; s++)
+  memset (local_cmap, '\0', sizeof (local_cmap));
+  for (xflags = 0, s = separators; s && *s; s++)
     {
       if (*s == CTLESC) xflags |= SX_NOCTLESC;
       if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
+      local_cmap[(unsigned char)*s] = 1;       /* local charmap of separators */
     }
 
   s = *stringp;
   slen = 0;
 
   /* Remove sequences of whitespace at the beginning of STRING, as
-     long as those characters appear in IFS. */
-  if (sh_style_split || !separators || !*separators)
+     long as those characters appear in SEPARATORS.  This happens if
+     SEPARATORS == $' \t\n' or if IFS is unset. */
+  if (sh_style_split || separators == 0)
     {
-      for (; *s && spctabnl (*s) && isifs (*s); s++);
+      for (; *s && spctabnl (*s) && islocalsep (*s); s++);
 
       /* If the string is nothing but whitespace, update it and return. */
       if (!*s)
@@ -2568,9 +2886,9 @@ get_word_from_string (stringp, separators, endptr)
 
      This obeys the field splitting rules in Posix.2. */
   sindex = 0;
-  /* Don't need string length in ADVANCE_CHAR or string_extract_verbatim
-     unless multibyte chars are possible. */
-  slen = (MB_CUR_MAX > 1) ? strlen (s) : 1;
+  /* Don't need string length in ADVANCE_CHAR unless multibyte chars are
+     possible, but need it in string_extract_verbatim for bounds checking */
+  slen = STRLEN (s);
   current_word = string_extract_verbatim (s, slen, &sindex, separators, xflags);
 
   /* Set ENDPTR to the first character after the end of the word. */
@@ -2589,19 +2907,19 @@ get_word_from_string (stringp, separators, endptr)
 
   /* Now skip sequences of space, tab, or newline characters if they are
      in the list of separators. */
-  while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex]))
+  while (s[sindex] && spctabnl (s[sindex]) && islocalsep (s[sindex]))
     sindex++;
 
   /* If the first separator was IFS whitespace and the current character is
      a non-whitespace IFS character, it should be part of the current field
      delimiter, not a separate delimiter that would result in an empty field.
      Look at POSIX.2, 3.6.5, (3)(b). */
-  if (s[sindex] && whitesep && isifs (s[sindex]) && !spctabnl (s[sindex]))
+  if (s[sindex] && whitesep && islocalsep (s[sindex]) && !spctabnl (s[sindex]))
     {
       sindex++;
       /* An IFS character that is not IFS white space, along with any adjacent
         IFS white space, shall delimit a field. */
-      while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex]))
+      while (s[sindex] && spctabnl (s[sindex]) && islocalsep(s[sindex]))
        sindex++;
     }
 
@@ -2661,9 +2979,9 @@ list_string_with_quotes (string)
            i++;
        }
       else if (c == '\'')
-       i = skip_single_quoted (s, s_len, ++i);
+       i = skip_single_quoted (s, s_len, ++i, 0);
       else if (c == '"')
-       i = skip_double_quoted (s, s_len, ++i);
+       i = skip_double_quoted (s, s_len, ++i, 0);
       else if (c == 0 || spctabnl (c))
        {
          /* We have found the end of a token.  Make a word out of it and
@@ -2698,24 +3016,66 @@ do_compound_assignment (name, value, flags)
      int flags;
 {
   SHELL_VAR *v;
-  int mklocal, mkassoc;
+  int mklocal, mkassoc, mkglobal;
   WORD_LIST *list;
 
   mklocal = flags & ASS_MKLOCAL;
   mkassoc = flags & ASS_MKASSOC;
+  mkglobal = flags & ASS_MKGLOBAL;
 
   if (mklocal && variable_context)
     {
       v = find_variable (name);
+      if (v && ((readonly_p (v) && (flags & ASS_FORCE) == 0) || noassign_p (v)))
+       {
+         if (v && readonly_p (v))
+           err_readonly (name);
+         return (v);   /* XXX */
+       }
       list = expand_compound_array_assignment (v, value, flags);
       if (mkassoc)
        v = make_local_assoc_variable (name);
       else if (v == 0 || (array_p (v) == 0 && assoc_p (v) == 0) || v->context != variable_context)
-        v = make_local_array_variable (name);
-      assign_compound_array_list (v, list, flags);
+        v = make_local_array_variable (name, 0);
+      if (v)
+       assign_compound_array_list (v, list, flags);
+      if (list)
+       dispose_words (list);
+    }
+  /* In a function but forcing assignment in global context */
+  else if (mkglobal && variable_context)
+    {
+      v = find_global_variable (name);
+      if (v && ((readonly_p (v) && (flags & ASS_FORCE) == 0) || noassign_p (v)))
+       {
+         if (v && readonly_p (v))
+           err_readonly (name);
+         return (v);   /* XXX */
+       }
+      list = expand_compound_array_assignment (v, value, flags);
+      if (v == 0 && mkassoc)
+       v = make_new_assoc_variable (name);
+      else if (v && mkassoc && assoc_p (v) == 0)
+       v = convert_var_to_assoc (v);
+      else if (v == 0)
+       v = make_new_array_variable (name);
+      else if (v && mkassoc == 0 && array_p (v) == 0)
+       v = convert_var_to_array (v);
+      if (v)
+       assign_compound_array_list (v, list, flags);
+      if (list)
+       dispose_words (list);
     }
   else
-    v = assign_array_from_string (name, value, flags);
+    {
+      v = assign_array_from_string (name, value, flags);
+      if (v && ((readonly_p (v) && (flags & ASS_FORCE) == 0) || noassign_p (v)))
+       {
+         if (v && readonly_p (v))
+           err_readonly (name);
+         return (v);   /* XXX */
+       }
+    }
 
   return (v);
 }
@@ -2810,6 +3170,8 @@ do_assignment_internal (word, expand)
     {
       if ((word->flags & W_ASSIGNARG) && (word->flags & W_ASSNGLOBAL) == 0)
        aflags |= ASS_MKLOCAL;
+      if ((word->flags & W_ASSIGNARG) && (word->flags & W_ASSNGLOBAL))
+       aflags |= ASS_MKGLOBAL;
       if (word->flags & W_ASSIGNASSOC)
        aflags |= ASS_MKASSOC;
       entry = do_compound_assignment (name, value, aflags);
@@ -2820,7 +3182,6 @@ do_assignment_internal (word, expand)
 
   stupidly_hack_special_variables (name);
 
-#if 1
   /* Return 1 if the assignment seems to have been performed correctly. */
   if (entry == 0 || readonly_p (entry))
     retval = 0;                /* assignment failure */
@@ -2836,12 +3197,6 @@ do_assignment_internal (word, expand)
     VUNSETATTR (entry, att_invisible);
 
   ASSIGN_RETURN (retval);
-#else
-  if (entry)
-    VUNSETATTR (entry, att_invisible);
-
-  ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0);
-#endif
 }
 
 /* Perform the assignment statement in STRING, and expand the
@@ -2972,7 +3327,7 @@ pos_params (string, start, end, quoted)
     return ((char *)NULL);
 
   save = params = list_rest_of_args ();
-  if (save == 0)
+  if (save == 0 && start > 0)
     return ((char *)NULL);
 
   if (start == 0)              /* handle ${@:0[:x]} specially */
@@ -2984,7 +3339,10 @@ pos_params (string, start, end, quoted)
   for (i = start ? 1 : 0; params && i < start; i++)
     params = params->next;
   if (params == 0)
-    return ((char *)NULL);
+    {
+      dispose_words (save);
+      return ((char *)NULL);
+    }
   for (h = t = params; params && i < end; i++)
     {
       t = params;
@@ -3113,26 +3471,79 @@ expand_arith_string (string, quoted)
      char *string;
      int quoted;
 {
-  return (expand_string_if_necessary (string, quoted, expand_string));
-}
-
-#if defined (COND_COMMAND)
-/* Just remove backslashes in STRING.  Returns a new string. */
-char *
-remove_backslashes (string)
-     char *string;
-{
-  char *r, *ret, *s;
+  WORD_DESC td;
+  WORD_LIST *list, *tlist;
+  size_t slen;
+  int i, saw_quote;
+  char *ret;
+  DECLARE_MBSTATE;
 
-  r = ret = (char *)xmalloc (strlen (string) + 1);
-  for (s = string; s && *s; )
+  /* 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 (*s == '\\')
-       s++;
-      if (*s == 0)
+      if (EXP_CHAR (string[i]))
        break;
-      *r++ = *s++;
-    }
+      else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"')
+       saw_quote = 1;
+      ADVANCE_CHAR (string, slen, i);
+    }
+
+  if (string[i])
+    {
+      /* This is expanded version of expand_string_internal as it's called by
+        expand_string_leave_quoted  */
+      td.flags = W_NOPROCSUB;  /* don't want process substitution */
+      td.word = savestring (string);
+      list = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+      /* This takes care of the calls from expand_string_leave_quoted and
+        expand_string */
+      if (list)
+       {
+         tlist = word_list_split (list);
+         dispose_words (list);
+         list = tlist;
+         if (list)
+           dequote_list (list);
+       }
+      /* This comes from expand_string_if_necessary */
+      if (list)
+       {
+         ret = string_list (list);
+         dispose_words (list);
+       }
+      else
+       ret = (char *)NULL;
+      FREE (td.word);
+    }
+  else if (saw_quote && (quoted & Q_ARITH))
+    ret = string_quote_removal (string, quoted);
+  else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+    ret = string_quote_removal (string, quoted);
+  else
+    ret = savestring (string);
+
+  return ret;
+}
+
+#if defined (COND_COMMAND)
+/* Just remove backslashes in STRING.  Returns a new string. */
+char *
+remove_backslashes (string)
+     char *string;
+{
+  char *r, *ret, *s;
+
+  r = ret = (char *)xmalloc (strlen (string) + 1);
+  for (s = string; s && *s; )
+    {
+      if (*s == '\\')
+       s++;
+      if (*s == 0)
+       break;
+      *r++ = *s++;
+    }
   *r = '\0';
   return ret;
 }
@@ -3157,20 +3568,26 @@ cond_expand_word (w, special)
   if (w->word == 0 || w->word[0] == '\0')
     return ((char *)NULL);
 
+  expand_no_split_dollar_star = 1;
   w->flags |= W_NOSPLIT2;
   l = call_expand_word_internal (w, 0, 0, (int *)0, (int *)0);
+  expand_no_split_dollar_star = 0;
   if (l)
     {
-      if (special == 0)
+      if (special == 0)                        /* LHS */
        {
          dequote_list (l);
          r = string_list (l);
        }
       else
        {
+         /* Need to figure out whether or not we should call dequote_escapes
+            or a new dequote_ctlnul function here, and under what
+            circumstances. */
          qflags = QGLOB_CVTNULL;
          if (special == 2)
            qflags |= QGLOB_REGEXP;
+         word_list_remove_quoted_nulls (l);
          p = string_list (l);
          r = quote_string_for_globbing (p, qflags);
          free (p);
@@ -3205,13 +3622,16 @@ call_expand_word_internal (w, q, i, c, e)
       last_command_exit_value = EXECUTION_FAILURE;
       exp_jump_to_top_level ((result == &expand_word_error) ? DISCARD : FORCE_EOF);
       /* NOTREACHED */
+      return (NULL);
     }
   else
     return (result);
 }
 
 /* Perform parameter expansion, command substitution, and arithmetic
-   expansion on STRING, as if it were a word.  Leave the result quoted. */
+   expansion on STRING, as if it were a word.  Leave the result quoted.
+   Since this does not perform word splitting, it leaves quoted nulls
+   in the result.  */
 static WORD_LIST *
 expand_string_internal (string, quoted)
      char *string;
@@ -3368,9 +3788,9 @@ expand_string_leave_quoted (string, quoted)
 /* This does not perform word splitting or dequote the WORD_LIST
    it returns. */
 static WORD_LIST *
-expand_string_for_rhs (string, quoted, dollar_at_p, has_dollar_at)
+expand_string_for_rhs (string, quoted, dollar_at_p, expanded_p)
      char *string;
-     int quoted, *dollar_at_p, *has_dollar_at;
+     int quoted, *dollar_at_p, *expanded_p;
 {
   WORD_DESC td;
   WORD_LIST *tresult;
@@ -3378,9 +3798,12 @@ expand_string_for_rhs (string, quoted, dollar_at_p, has_dollar_at)
   if (string == 0 || *string == '\0')
     return (WORD_LIST *)NULL;
 
+  expand_no_split_dollar_star = 1;
   td.flags = W_NOSPLIT2;               /* no splitting, remove "" and '' */
   td.word = string;
-  tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, has_dollar_at);
+  tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, expanded_p);
+  expand_no_split_dollar_star = 0;
+
   return (tresult);
 }
 
@@ -3452,6 +3875,7 @@ quote_escapes (string)
       COPY_CHAR_P (t, s, send);
     }
   *t = '\0';
+
   return (result);
 }
 
@@ -3517,9 +3941,26 @@ dequote_escapes (string)
       COPY_CHAR_P (t, s, send);
     }
   *t = '\0';
+
   return result;
 }
 
+static WORD_LIST *
+list_dequote_escapes (list)
+     WORD_LIST *list;
+{
+  register WORD_LIST *w;
+  char *t;
+
+  for (w = list; w; w = w->next)
+    {
+      t = w->word->word;
+      w->word->word = dequote_escapes (t);
+      free (t);
+    }
+  return list;
+}
+
 /* Return a new string with the quoted representation of character C.
    This turns "" into QUOTED_NULL, so the W_HASQUOTEDNULL flag needs to be
    set in any resultant WORD_DESC where this value is the word. */
@@ -3590,6 +4031,11 @@ 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
+
   slen = strlen (string);
 
   t = result = (char *)xmalloc (slen + 1);
@@ -3600,6 +4046,14 @@ dequote_string (string)
       return (result);
     }
 
+  /* A string consisting of only a single CTLESC should pass through unchanged */
+  if (string[0] == CTLESC && string[1] == 0)
+    {
+      result[0] = CTLESC;
+      result[1] = '\0';
+      return (result);
+    }
+
   /* If no character in the string can be quoted, don't bother examining
      each character.  Just return a copy of the string passed to us. */
   if (strchr (string, CTLESC) == NULL)
@@ -3797,7 +4251,7 @@ remove_upattern (param, pattern, op)
      char *param, *pattern;
      int op;
 {
-  register int len;
+  register size_t len;
   register char *end;
   register char *p, *ret, c;
 
@@ -3961,6 +4415,7 @@ remove_pattern (param, pattern, op)
          return ((xret == param) ? savestring (param) : xret);
        }
       n = xdupmbstowcs (&wparam, NULL, param);
+
       if (n == (size_t)-1)
        {
          free (wpattern);
@@ -4008,7 +4463,8 @@ match_upattern (string, pat, mtype, sp, ep)
      int mtype;
      char **sp, **ep;
 {
-  int c, len, mlen;
+  int c, mlen;
+  size_t len;
   register char *p, *p1, *npat;
   char *end;
   int n1;
@@ -4024,19 +4480,41 @@ match_upattern (string, pat, mtype, sp, ep)
   len = STRLEN (pat);
   if (pat[0] != '*' || (pat[0] == '*' && pat[1] == LPAREN && extended_glob) || pat[len - 1] != '*')
     {
+      int unescaped_backslash;
+      char *pp;
+
       p = npat = (char *)xmalloc (len + 3);
       p1 = pat;
       if (*p1 != '*' || (*p1 == '*' && p1[1] == LPAREN && extended_glob))
        *p++ = '*';
       while (*p1)
        *p++ = *p1++;
-      if (p1[-1] != '*' || p[-2] == '\\')
+#if 1
+      /* Need to also handle a pattern that ends with an unescaped backslash.
+        For right now, we ignore it because the pattern matching code will
+        fail the match anyway */
+      /* If the pattern ends with a `*' we leave it alone if it's preceded by
+        an even number of backslashes, but if it's escaped by a backslash
+        we need to add another `*'. */
+      if (p1[-1] == '*' && (unescaped_backslash = p1[-2] == '\\'))
+       {
+         pp = p1 - 3;
+         while (pp >= pat && *pp-- == '\\')
+           unescaped_backslash = 1 - unescaped_backslash;
+         if (unescaped_backslash)
+           *p++ = '*';
+       }
+      else if (p1[-1] != '*')
+       *p++ = '*';
+#else 
+      if (p1[-1] != '*' || p1[-2] == '\\')
        *p++ = '*';
+#endif
       *p = '\0';
     }
   else
     npat = pat;
-  c = strmatch (npat, string, FNMATCH_EXTFLAG);
+  c = strmatch (npat, string, FNMATCH_EXTFLAG | FNMATCH_IGNCASE);
   if (npat != pat)
     free (npat);
   if (c == FNM_NOMATCH)
@@ -4052,11 +4530,8 @@ match_upattern (string, pat, mtype, sp, ep)
     case MATCH_ANY:
       for (p = string; p <= end; p++)
        {
-         if (match_pattern_char (pat, p))
+         if (match_pattern_char (pat, p, FNMATCH_IGNCASE))
            {
-#if 0
-             for (p1 = end; p1 >= p; p1--)
-#else
              p1 = (mlen == -1) ? end : p + mlen;
              /* p1 - p = length of portion of string to be considered
                 p = current position in string
@@ -4069,10 +4544,9 @@ match_upattern (string, pat, mtype, sp, ep)
              if (p1 > end)
                break;
              for ( ; p1 >= p; p1--)
-#endif
                {
                  c = *p1; *p1 = '\0';
-                 if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
+                 if (strmatch (pat, p, FNMATCH_EXTFLAG | FNMATCH_IGNCASE) == 0)
                    {
                      *p1 = c;
                      *sp = p;
@@ -4092,17 +4566,13 @@ match_upattern (string, pat, mtype, sp, ep)
       return (0);
 
     case MATCH_BEG:
-      if (match_pattern_char (pat, string) == 0)
+      if (match_pattern_char (pat, string, FNMATCH_IGNCASE) == 0)
        return (0);
 
-#if 0
-      for (p = end; p >= string; p--)
-#else
       for (p = (mlen == -1) ? end : string + mlen; p >= string; p--)
-#endif
        {
          c = *p; *p = '\0';
-         if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0)
+         if (strmatch (pat, string, FNMATCH_EXTFLAG | FNMATCH_IGNCASE) == 0)
            {
              *p = c;
              *sp = string;
@@ -4110,33 +4580,25 @@ match_upattern (string, pat, mtype, sp, ep)
              return 1;
            }
          *p = c;
-#if 1
          /* If MLEN != -1, we have a fixed length pattern. */
          if (mlen != -1)
            break;
-#endif
        }
 
       return (0);
 
     case MATCH_END:
-#if 0
-      for (p = string; p <= end; p++)
-#else
       for (p = end - ((mlen == -1) ? len : mlen); p <= end; p++)
-#endif
        {
-         if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
+         if (strmatch (pat, p, FNMATCH_EXTFLAG | FNMATCH_IGNCASE) == 0)
            {
              *sp = p;
              *ep = end;
              return 1;
            }
-#if 1
          /* If MLEN != -1, we have a fixed length pattern. */
          if (mlen != -1)
            break;
-#endif
        }
 
       return (0);
@@ -4146,6 +4608,9 @@ match_upattern (string, pat, mtype, sp, ep)
 }
 
 #if defined (HANDLE_MULTIBYTE)
+
+#define WFOLD(c) (match_ignore_case && iswupper (c) ? towlower (c) : (c))
+
 /* Match WPAT anywhere in WSTRING and return the match boundaries.
    This returns 1 in case of a successful match, 0 otherwise.  Wide
    character version. */
@@ -4178,19 +4643,36 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
   len = wcslen (wpat);
   if (wpat[0] != L'*' || (wpat[0] == L'*' && wpat[1] == WLPAREN && extended_glob) || wpat[len - 1] != L'*')
     {
+      int unescaped_backslash;
+      wchar_t *wpp;
+
       wp = nwpat = (wchar_t *)xmalloc ((len + 3) * sizeof (wchar_t));
       wp1 = wpat;
       if (*wp1 != L'*' || (*wp1 == '*' && wp1[1] == WLPAREN && extended_glob))
        *wp++ = L'*';
       while (*wp1 != L'\0')
        *wp++ = *wp1++;
+#if 1
+      /* See comments above in match_upattern. */
+      if (wp1[-1] == L'*' && (unescaped_backslash = wp1[-2] == L'\\'))
+        {
+          wpp = wp1 - 3;
+          while (wpp >= wpat && *wpp-- == L'\\')
+            unescaped_backslash = 1 - unescaped_backslash;
+          if (unescaped_backslash)
+            *wp++ = L'*';
+        }
+      else if (wp1[-1] != L'*')
+        *wp++ = L'*';
+#else      
       if (wp1[-1] != L'*' || wp1[-2] == L'\\')
         *wp++ = L'*';
+#endif
       *wp = '\0';
     }
   else
     nwpat = wpat;
-  len = wcsmatch (nwpat, wstring, FNMATCH_EXTFLAG);
+  len = wcsmatch (nwpat, wstring, FNMATCH_EXTFLAG | FNMATCH_IGNCASE);
   if (nwpat != wpat)
     free (nwpat);
   if (len == FNM_NOMATCH)
@@ -4204,25 +4686,17 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
     case MATCH_ANY:
       for (n = 0; n <= wstrlen; n++)
        {
-#if 1
-         n2 = simple ? (*wpat == wstring[n]) : match_pattern_wchar (wpat, wstring + n);
-#else
-         n2 = match_pattern_wchar (wpat, wstring + n);
-#endif
+         n2 = simple ? (WFOLD(*wpat) == WFOLD(wstring[n])) : match_pattern_wchar (wpat, wstring + n, FNMATCH_IGNCASE);
          if (n2)
            {
-#if 0
-             for (n1 = wstrlen; n1 >= n; n1--)
-#else
              n1 = (mlen == -1) ? wstrlen : n + mlen;
              if (n1 > wstrlen)
                break;
 
              for ( ; n1 >= n; n1--)
-#endif
                {
                  wc = wstring[n1]; wstring[n1] = L'\0';
-                 if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
+                 if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG | FNMATCH_IGNCASE) == 0)
                    {
                      wstring[n1] = wc;
                      *sp = indices[n];
@@ -4230,11 +4704,9 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
                      return 1;
                    }
                  wstring[n1] = wc;
-#if 1
                  /* If MLEN != -1, we have a fixed length pattern. */
                  if (mlen != -1)
                    break;
-#endif
                }
            }
        }
@@ -4242,17 +4714,13 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
       return (0);
 
     case MATCH_BEG:
-      if (match_pattern_wchar (wpat, wstring) == 0)
+      if (match_pattern_wchar (wpat, wstring, FNMATCH_IGNCASE) == 0)
        return (0);
 
-#if 0
-      for (n = wstrlen; n >= 0; n--)
-#else
       for (n = (mlen == -1) ? wstrlen : mlen; n >= 0; n--)
-#endif
        {
          wc = wstring[n]; wstring[n] = L'\0';
-         if (wcsmatch (wpat, wstring, FNMATCH_EXTFLAG) == 0)
+         if (wcsmatch (wpat, wstring, FNMATCH_EXTFLAG | FNMATCH_IGNCASE) == 0)
            {
              wstring[n] = wc;
              *sp = indices[0];
@@ -4260,33 +4728,25 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
              return 1;
            }
          wstring[n] = wc;
-#if 1
          /* If MLEN != -1, we have a fixed length pattern. */
          if (mlen != -1)
            break;
-#endif
        }
 
       return (0);
 
     case MATCH_END:
-#if 0
-      for (n = 0; n <= wstrlen; n++)
-#else
       for (n = wstrlen - ((mlen == -1) ? wstrlen : mlen); n <= wstrlen; n++)
-#endif
        {
-         if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
+         if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG | FNMATCH_IGNCASE) == 0)
            {
              *sp = indices[n];
              *ep = indices[wstrlen];
              return 1;
            }
-#if 1
          /* If MLEN != -1, we have a fixed length pattern. */
          if (mlen != -1)
            break;
-#endif
        }
 
       return (0);
@@ -4294,6 +4754,7 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
 
   return (0);
 }
+#undef WFOLD
 #endif /* HANDLE_MULTIBYTE */
 
 static int
@@ -4310,21 +4771,13 @@ match_pattern (string, pat, mtype, sp, ep)
   size_t slen, plen, mslen, mplen;
 #endif
 
-  if (string == 0 || *string == 0 || pat == 0 || *pat == 0)
+  if (string == 0 || pat == 0 || *pat == 0)
     return (0);
 
 #if defined (HANDLE_MULTIBYTE)
   if (MB_CUR_MAX > 1)
     {
-#if 0
-      slen = STRLEN (string);
-      mslen = MBSLEN (string);
-      plen = STRLEN (pat);
-      mplen = MBSLEN (pat);
-      if (slen == mslen && plen == mplen)
-#else
       if (mbsmbchar (string) == 0 && mbsmbchar (pat) == 0)
-#endif
         return (match_upattern (string, pat, mtype, sp, ep));
 
       n = xdupmbstowcs (&wpat, NULL, pat);
@@ -4387,7 +4840,7 @@ getpattern (value, quoted, expandpat)
   if (expandpat && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *tword)
     {
       i = 0;
-      pat = string_extract_double_quoted (tword, &i, 1);
+      pat = string_extract_double_quoted (tword, &i, SX_STRIPDQ);
       free (tword);
       tword = pat;
     }
@@ -4486,6 +4939,11 @@ array_remove_pattern (var, pattern, patspec, varname, quoted)
 
   /* compute itype from varname here */
   v = array_variable_part (varname, &ret, 0);
+
+  /* XXX */
+  if (v && invisible_p (v))
+    return ((char *)NULL);
+
   itype = ret[0];
 
   a = (v && array_p (v)) ? array_cell (v) : 0;
@@ -4577,6 +5035,276 @@ parameter_brace_remove_pattern (varname, value, ind, patstr, rtype, quoted, flag
   return temp1;
 }    
 
+static char *
+string_var_assignment (v, s)
+     SHELL_VAR *v;
+     char *s;
+{
+  char flags[MAX_ATTRIBUTES], *ret, *val;
+  int i;
+
+  val = 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)
+    sprintf (ret, "declare -%s %s=%s", flags, v->name, val);
+  else
+    sprintf (ret, "%s=%s", v->name, val);
+  free (val);
+  return ret;
+}
+
+#if defined (ARRAY_VARS)
+static char *
+array_var_assignment (v, itype, quoted)
+     SHELL_VAR *v;
+     int itype, quoted;
+{
+  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)
+    {
+      val = (char *)xmalloc (3);
+      val[0] = '(';
+      val[1] = ')';
+      val[2] = 0;
+    }
+  else
+    {
+      ret = (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) ? quote_string (val) : quote_escapes (val);
+      free (val);
+      val = ret;
+    }
+  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);
+  free (val);
+  return ret;
+}
+#endif
+
+static char *
+pos_params_assignment (list, itype, quoted)
+     WORD_LIST *list;
+     int itype;
+     int quoted;
+{
+  char *temp, *ret;
+
+  /* first, we transform the list to quote each word. */
+  temp = list_transform ('Q', (SHELL_VAR *)0, list, itype, quoted);
+  ret = (char *)xmalloc (strlen (temp) + 8);
+  strcpy (ret, "set -- ");
+  strcpy (ret + 7, temp);
+  free (temp);
+  return ret;
+}
+
+static char *
+string_transform (xc, v, s)
+     int xc;
+     SHELL_VAR *v;
+     char *s;
+{
+  char *ret, flags[MAX_ATTRIBUTES];
+  int i;
+
+  if (((xc == 'A' || xc == 'a') && v == 0) || (xc != 'a' && s == 0))
+    return (char *)NULL;
+
+  switch (xc)
+    {
+      /* Transformations that interrogate the variable */
+      case 'a':
+       i = var_attribute_string (v, 0, flags);
+       ret = (i > 0) ? savestring (flags) : (char *)NULL;
+       break;
+      case 'A':
+       ret = string_var_assignment (v, s);
+       break;
+      /* Transformations that modify the variable's value */
+      case 'E':
+       ret = ansiexpand (s, 0, strlen (s), (int *)0);
+       break;
+      case 'P':
+       ret = decode_prompt_string (s);
+       break;
+      case 'Q':
+       ret = sh_quote_reusable (s, 0);
+       break;
+      default:
+       ret = (char *)NULL;
+       break;
+    }
+  return ret;
+}
+
+static char *
+list_transform (xc, v, list, itype, quoted)
+     int xc;
+     SHELL_VAR *v;
+     WORD_LIST *list;
+     int itype, quoted;
+{
+  WORD_LIST *new, *l;
+  WORD_DESC *w;
+  char *tword;
+
+  for (new = (WORD_LIST *)NULL, l = list; l; l = l->next)
+    {
+      tword = string_transform (xc, v, l->word->word);
+      w = alloc_word_desc ();
+      w->word = tword ? tword : savestring ("");       /* XXX */
+      new = make_word_list (w, new);
+    }
+
+  l = REVERSE_LIST (new, WORD_LIST *);
+  tword = string_list_pos_params (itype, l, quoted);
+  dispose_words (l);
+
+  return (tword);
+}
+
+static char *
+parameter_list_transform (xc, itype, quoted)
+     int xc;
+     int itype;
+     int quoted;
+{
+  char *ret;
+  WORD_LIST *list;
+
+  list = list_rest_of_args ();
+  if (list == 0)
+    return ((char *)NULL);
+  if (xc == 'A')
+    return (pos_params_assignment (list, itype, quoted));
+  ret = list_transform (xc, (SHELL_VAR *)0, list, itype, quoted);
+  dispose_words (list);
+  return (ret);
+}
+
+#if defined (ARRAY_VARS)
+static char *
+array_transform (xc, var, varname, quoted)
+     int xc;
+     SHELL_VAR *var;
+     char *varname;    /* so we can figure out how it's indexed */
+     int quoted;
+{
+  ARRAY *a;
+  HASH_TABLE *h;
+  int itype;
+  char *ret;
+  WORD_LIST *list;
+  SHELL_VAR *v;
+
+  /* compute itype from varname here */
+  v = array_variable_part (varname, &ret, 0);
+
+  /* XXX */
+  if (v && invisible_p (v))
+    return ((char *)NULL);
+
+  itype = ret[0];
+
+  if (xc == 'A')
+    return (array_var_assignment (v, itype, quoted));
+
+  a = (v && array_p (v)) ? array_cell (v) : 0;
+  h = (v && assoc_p (v)) ? assoc_cell (v) : 0;
+  
+  list = a ? array_to_word_list (a) : (h ? assoc_to_word_list (h) : 0);
+  if (list == 0)
+   return ((char *)NULL);
+  ret = list_transform (xc, v, list, itype, quoted);
+  dispose_words (list);
+
+  return ret;
+}
+#endif /* ARRAY_VARS */
+
+static char *
+parameter_brace_transform (varname, value, ind, xform, rtype, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *xform;
+     int rtype, quoted, flags;
+{
+  int vtype, xc;
+  char *temp1, *val;
+  SHELL_VAR *v;
+
+  xc = xform[0];
+  if (value == 0 && xc != 'A' && xc != 'a')
+    return ((char *)NULL);
+
+  this_command_name = varname;
+
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
+  if (vtype == -1)
+    return ((char *)NULL);
+
+  /* check for valid values of xc */
+  switch (xc)
+    {
+    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:
+      return &expand_param_error;
+    }
+
+  temp1 = (char *)NULL;                /* shut up gcc */
+  switch (vtype & ~VT_STARSUB)
+    {
+    case VT_VARIABLE:
+    case VT_ARRAYMEMBER:
+      temp1 = string_transform (xc, v, val);
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      if (temp1)
+       {
+         val = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+                       ? quote_string (temp1)
+                       : quote_escapes (temp1);
+         free (temp1);
+         temp1 = val;
+       }
+      break;
+#if defined (ARRAY_VARS)
+    case VT_ARRAYVAR:
+      temp1 = array_transform (xc, v, varname, quoted);
+      if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+       {
+         val = quote_escapes (temp1);
+         free (temp1);
+         temp1 = val;
+       }
+      break;
+#endif
+    case VT_POSPARMS:
+      temp1 = parameter_list_transform (xc, varname[0], quoted);
+      if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+       {
+         val = quote_escapes (temp1);
+         free (temp1);
+         temp1 = val;
+       }
+      break;
+    }
+
+  return temp1;
+}
+
 /*******************************************
  *                                        *
  *     Functions to expand WORD_DESCs     *
@@ -4672,6 +5400,11 @@ static struct temp_fifo *fifo_list = (struct temp_fifo *)NULL;
 static int nfifo;
 static int fifo_list_size;
 
+void
+clear_fifo_list ()
+{
+}
+
 char *
 copy_fifo_list (sizep)
      int *sizep;
@@ -4810,6 +5543,31 @@ static char *dev_fd_list = (char *)NULL;
 static int nfds;
 static int totfds;     /* The highest possible number of open files. */
 
+void
+clear_fifo (i)
+     int i;
+{
+  if (dev_fd_list[i])
+    {
+      dev_fd_list[i] = 0;
+      nfds--;
+    }
+}
+
+void
+clear_fifo_list ()
+{
+  register int i;
+
+  if (nfds == 0)
+    return;
+
+  for (i = 0; nfds && i < totfds; i++)
+    clear_fifo (i);
+
+  nfds = 0;
+}
+
 char *
 copy_fifo_list (sizep)
      int *sizep;
@@ -4987,7 +5745,7 @@ process_substitute (string, open_for_read_in_child)
 #else /* HAVE_DEV_FD */
   if (pipe (fildes) < 0)
     {
-      sys_error (_("cannot make pipe for process substitution"));
+      sys_error ("%s", _("cannot make pipe for process substitution"));
       return ((char *)NULL);
     }
   /* If OPEN_FOR_READ_IN_CHILD == 1, we want to use the write end of
@@ -5003,7 +5761,7 @@ process_substitute (string, open_for_read_in_child)
 
   if (pathname == 0)
     {
-      sys_error (_("cannot make pipe for process substitution"));
+      sys_error ("%s", _("cannot make pipe for process substitution"));
       return ((char *)NULL);
     }
 
@@ -5011,7 +5769,8 @@ process_substitute (string, open_for_read_in_child)
 
 #if defined (JOB_CONTROL)
   old_pipeline_pgrp = pipeline_pgrp;
-  pipeline_pgrp = shell_pgrp;
+  if (pipeline_pgrp == 0 || (subshell_environment & (SUBSHELL_PIPE|SUBSHELL_FORK|SUBSHELL_ASYNC)) == 0)
+    pipeline_pgrp = shell_pgrp;
   save_pipeline (1);
 #endif /* JOB_CONTROL */
 
@@ -5022,8 +5781,15 @@ process_substitute (string, open_for_read_in_child)
       free_pushed_string_input ();
       /* Cancel traps, in trap.c. */
       restore_original_signals ();     /* XXX - what about special builtins? bash-4.2 */
+      QUIT;    /* catch any interrupts we got post-fork */
       setup_async_signals ();
       subshell_environment |= SUBSHELL_COMSUB|SUBSHELL_PROCSUB;
+
+      /* if we're expanding a redirection, we shouldn't have access to the
+        temporary environment, but commands in the subshell should have
+        access to their own temporary environment. */
+      if (expanding_redir)
+        flush_temporary_env ();
     }
 
 #if defined (JOB_CONTROL)
@@ -5031,11 +5797,13 @@ process_substitute (string, open_for_read_in_child)
   stop_making_children ();
   /* XXX - should we only do this in the parent? (as in command subst) */
   pipeline_pgrp = old_pipeline_pgrp;
+#else
+  stop_making_children ();
 #endif /* JOB_CONTROL */
 
   if (pid < 0)
     {
-      sys_error (_("cannot make child for process substitution"));
+      sys_error ("%s", _("cannot make child for process substitution"));
       free (pathname);
 #if defined (HAVE_DEV_FD)
       close (parent_pipe_fd);
@@ -5047,7 +5815,9 @@ process_substitute (string, open_for_read_in_child)
   if (pid > 0)
     {
 #if defined (JOB_CONTROL)
-      restore_pipeline (1);
+      if (last_procsub_child)
+       discard_last_procsub_child ();
+      last_procsub_child = restore_pipeline (0);
 #endif
 
 #if !defined (HAVE_DEV_FD)
@@ -5075,7 +5845,7 @@ process_substitute (string, open_for_read_in_child)
 
 #if !defined (HAVE_DEV_FD)
   /* Open the named pipe in the child. */
-  fd = open (pathname, open_for_read_in_child ? O_RDONLY|O_NONBLOCK : O_WRONLY);
+  fd = open (pathname, open_for_read_in_child ? O_RDONLY : O_WRONLY);
   if (fd < 0)
     {
       /* Two separate strings for ease of translation. */
@@ -5098,6 +5868,11 @@ process_substitute (string, open_for_read_in_child)
   fd = child_pipe_fd;
 #endif /* HAVE_DEV_FD */
 
+  /* Discard  buffered stdio output before replacing the underlying file
+     descriptor. */
+  if (open_for_read_in_child == 0)
+    fpurge (stdout);
+
   if (dup2 (fd, open_for_read_in_child ? 0 : 1) < 0)
     {
       sys_error (_("cannot duplicate named pipe %s as fd %d"), pathname,
@@ -5125,16 +5900,25 @@ process_substitute (string, open_for_read_in_child)
 #endif /* HAVE_DEV_FD */
 
   /* subshells shouldn't have this flag, which controls using the temporary
-     environment for variable lookups. */
+     environment for variable lookups.  We have already flushed the temporary
+     environment above in the case we're expanding a redirection, so processes
+     executed by this command need to be able to set it independently of their
+     parent. */
   expanding_redir = 0;
 
+  remove_quoted_escapes (string);
+
+  subshell_level++;
   result = parse_and_execute (string, "process substitution", (SEVAL_NONINT|SEVAL_NOHIST));
+  subshell_level--;
 
 #if !defined (HAVE_DEV_FD)
   /* Make sure we close the named pipe in the child before we exit. */
   close (open_for_read_in_child ? 0 : 1);
 #endif /* !HAVE_DEV_FD */
 
+  last_command_exit_value = result;
+  result = run_exit_trap ();
   exit (result);
   /*NOTREACHED*/
 }
@@ -5154,6 +5938,7 @@ read_comsub (fd, quoted, rflag)
   char *istring, buf[128], *bufp, *s;
   int istring_index, istring_size, c, tflag, skip_ctlesc, skip_ctlnul;
   ssize_t bufn;
+  int nullbyte;
 
   istring = (char *)NULL;
   istring_index = istring_size = bufn = tflag = 0;
@@ -5161,6 +5946,8 @@ read_comsub (fd, quoted, rflag)
   for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
     skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
 
+  nullbyte = 0;
+
   /* Read the output of the command through the pipe.  This may need to be
      changed to understand multibyte characters in the future. */
   while (1)
@@ -5178,8 +5965,12 @@ read_comsub (fd, quoted, rflag)
 
       if (c == 0)
        {
-#if 0
-         internal_warning ("read_comsub: ignored null byte in input");
+#if 1
+         if (nullbyte == 0)
+           {
+             internal_warning ("%s", _("command substitution: ignored null byte in input"));
+             nullbyte = 1;
+           }
 #endif
          continue;
        }
@@ -5261,7 +6052,7 @@ command_substitute (string, quoted)
      int quoted;
 {
   pid_t pid, old_pid, old_pipeline_pgrp, old_async_pid;
-  char *istring;
+  char *istring, *s;
   int result, fildes[2], function_value, pflags, rc, tflag;
   WORD_DESC *ret;
 
@@ -5269,8 +6060,15 @@ command_substitute (string, quoted)
 
   /* 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 (wordexp_only && read_but_dont_execute)
     {
@@ -5296,7 +6094,7 @@ command_substitute (string, quoted)
   /* Pipe the output of executing STRING into the current shell. */
   if (pipe (fildes) < 0)
     {
-      sys_error (_("cannot make pipe for command substitution"));
+      sys_error ("%s", _("cannot make pipe for command substitution"));
       goto error_exit;
     }
 
@@ -5319,6 +6117,12 @@ command_substitute (string, quoted)
         trap strings.  Set a flag noting that we have to free the
         trap strings if we run trap to change a signal disposition. */
       reset_signal_handlers ();
+      if (ISINTERRUPT)
+       {
+         kill (getpid (), SIGINT);
+         CLRINTERRUPT;         /* if we're ignoring SIGINT somehow */
+       }       
+      QUIT;    /* catch any interrupts we got post-fork */
       subshell_environment |= SUBSHELL_RESETTRAP;
     }
 
@@ -5337,6 +6141,8 @@ command_substitute (string, quoted)
       sys_error (_("cannot make child for command substitution"));
     error_exit:
 
+      last_made_pid = old_pid;
+
       FREE (istring);
       close (fildes[0]);
       close (fildes[1]);
@@ -5345,13 +6151,20 @@ command_substitute (string, quoted)
 
   if (pid == 0)
     {
+      /* The currently executing shell is not interactive. */
+      interactive = 0;
+
       set_sigint_handler ();   /* XXX */
 
       free_pushed_string_input ();
 
+      /* Discard  buffered stdio output before replacing the underlying file
+        descriptor. */
+      fpurge (stdout);
+
       if (dup2 (fildes[1], 1) < 0)
        {
-         sys_error (_("command_substitute: cannot duplicate pipe as fd 1"));
+         sys_error ("%s", _("command_substitute: cannot duplicate pipe as fd 1"));
          exit (EXECUTION_FAILURE);
        }
 
@@ -5378,29 +6191,45 @@ command_substitute (string, quoted)
       sh_setlinebuf (stdout);
 #endif /* __CYGWIN__ */
 
-      /* The currently executing shell is not interactive. */
-      interactive = 0;
-
       /* This is a subshell environment. */
       subshell_environment |= SUBSHELL_COMSUB;
 
-      /* When not in POSIX mode, command substitution does not inherit
-        the -e flag. */
-      if (posixly_correct == 0)
-       exit_immediately_on_error = 0;
+      /* Many shells do not appear to inherit the -v option for command
+        substitutions. */
+      change_flag ('v', FLAG_OFF);
+
+      /* When inherit_errexit option is not enabled, command substitution does
+        not inherit the -e flag.  It is enabled when Posix mode is enabled */
+      if (inherit_errexit == 0)
+        {
+          builtin_ignoring_errexit = 0;
+         change_flag ('e', FLAG_OFF);
+        }
+      set_shellopts ();
+
+      /* If we are expanding a redirection, we can dispose of any temporary
+        environment we received, since redirections are not supposed to have
+        access to the temporary environment.  We will have to see whether this
+        affects temporary environments supplied to `eval', but the temporary
+        environment gets copied to builtin_env at some point. */
+      if (expanding_redir)
+       {
+         flush_temporary_env ();
+         expanding_redir = 0;
+       }
 
       remove_quoted_escapes (string);
 
       startup_state = 2;       /* see if we can avoid a fork */
       /* Give command substitution a place to jump back to on failure,
         so we don't go back up to main (). */
-      result = setjmp (top_level);
+      result = setjmp_nosigs (top_level);
 
       /* If we're running a command substitution inside a shell function,
         trap `return' so we don't return from the function in the subshell
         and go off to never-never land. */
       if (result == 0 && return_catch_flag)
-       function_value = setjmp (return_catch);
+       function_value = setjmp_nosigs (return_catch);
       else
        function_value = 0;
 
@@ -5454,10 +6283,15 @@ command_substitute (string, quoted)
       /* wait_for gives the terminal back to shell_pgrp.  If some other
         process group should have it, give it away to that group here.
         pipeline_pgrp is non-zero only while we are constructing a
-        pipline, so what we are concerned about is whether or not that
+        pipeline, so what we are concerned about is whether or not that
         pipeline was started in the background.  A pipeline started in
-        the background should never get the tty back here. */
-      if (interactive && pipeline_pgrp != (pid_t)0 && (subshell_environment & SUBSHELL_ASYNC) == 0)
+        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 */
 
@@ -5493,7 +6327,7 @@ array_length_reference (s)
 
   /* If unbound variables should generate an error, report one and return
      failure. */
-  if ((var == 0 || (assoc_p (var) == 0 && array_p (var) == 0)) && unbound_vars_is_error)
+  if ((var == 0 || invisible_p (var) || (assoc_p (var) == 0 && array_p (var) == 0)) && unbound_vars_is_error)
     {
       c = *--t;
       *t = '\0';
@@ -5502,7 +6336,7 @@ array_length_reference (s)
       *t = c;
       return (-1);
     }
-  else if (var == 0)
+  else if (var == 0 || invisible_p (var))
     return 0;
 
   /* We support a couple of expansions for variables that are not arrays.
@@ -5530,13 +6364,18 @@ array_length_reference (s)
       if (akey == 0 || *akey == 0)
        {
          err_badarraysub (t);
+         FREE (akey);
          return (-1);
        }
       t = assoc_reference (assoc_cell (var), akey);
+      free (akey);
     }
   else
     {
-      ind = array_expand_index (t, len);
+      ind = array_expand_index (var, t, len);
+      /* negative subscripts to indexed arrays count back from end */
+      if (var && array_p (var) && ind < 0)
+       ind = array_max_index (array_cell (var)) + 1 + ind;
       if (ind < 0)
        {
          err_badarraysub (t);
@@ -5563,7 +6402,7 @@ valid_brace_expansion_word (name, var_is_special)
   else if (var_is_special)
     return 1;
 #if defined (ARRAY_VARS)
-  else if (valid_array_reference (name))
+  else if (valid_array_reference (name, 0))
     return 1;
 #endif /* ARRAY_VARS */
   else if (legal_identifier (name))
@@ -5607,7 +6446,7 @@ chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at)
 
   /* Now check for ${array[@]} and ${array[*]} */
 #if defined (ARRAY_VARS)
-  else if (valid_array_reference (name))
+  else if (valid_array_reference (name, 0))
     {
       temp1 = mbschr (name, '[');
       if (temp1 && temp1[1] == '@' && temp1[2] == ']')
@@ -5680,9 +6519,26 @@ parameter_brace_expand_word (name, var_is_special, quoted, pflags, indp)
       free (tt);
     }
 #if defined (ARRAY_VARS)
-  else if (valid_array_reference (name))
+  else if (valid_array_reference (name, 0))
     {
-      temp = array_value (name, quoted, 0, &atype, &ind);
+expand_arrayref:
+      if (pflags & PF_ASSIGNRHS)
+       {
+         var = array_variable_part (name, &tt, (int *)0);
+         if (ALL_ELEMENT_SUB (tt[0]) && tt[1] == ']')
+           {
+             /* Only treat as double quoted if array variable */
+             if (var && (array_p (var) || assoc_p (var)))
+               /* XXX - bash-4.4/bash-5.0 pass AV_ASSIGNRHS */
+               temp = array_value (name, quoted|Q_DOUBLE_QUOTES, AV_ASSIGNRHS, &atype, &ind);
+             else              
+               temp = array_value (name, quoted, 0, &atype, &ind);
+           }
+         else
+           temp = array_value (name, quoted, 0, &atype, &ind);
+       }
+      else
+       temp = array_value (name, quoted, 0, &atype, &ind);
       if (atype == 0 && temp)
        {
          temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
@@ -5719,6 +6575,28 @@ parameter_brace_expand_word (name, var_is_special, quoted, pflags, indp)
       else
        temp = (char *)NULL;
     }
+  else if (var = find_variable_last_nameref (name, 0))
+    {
+      temp = nameref_cell (var);
+#if defined (ARRAY_VARS)
+      /* Handle expanding nameref whose value is x[n] */
+      if (temp && *temp && valid_array_reference (temp, 0))
+       {
+         name = temp;
+         goto expand_arrayref;
+       }
+      else
+#endif
+      /* y=2 ; typeset -n x=y; echo ${x} is not the same as echo ${2} in ksh */
+      if (temp && *temp && legal_identifier (temp) == 0)
+        {
+         last_command_exit_value = EXECUTION_FAILURE;
+         report_error (_("%s: invalid variable name for name reference"), temp);
+         temp = &expand_param_error;
+        }
+      else
+       temp = (char *)NULL;
+    }
   else
     temp = (char *)NULL;
 
@@ -5731,17 +6609,22 @@ parameter_brace_expand_word (name, var_is_special, quoted, pflags, indp)
   return ret;
 }
 
-/* Expand an indirect reference to a variable: ${!NAME} expands to the
-   value of the variable whose name is the value of NAME. */
-static WORD_DESC *
-parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at)
+static char *
+parameter_brace_find_indir (name, var_is_special, quoted, find_nameref)
      char *name;
-     int var_is_special, quoted;
-     int *quoted_dollar_atp, *contains_dollar_at;
+     int var_is_special, quoted, find_nameref;
 {
   char *temp, *t;
   WORD_DESC *w;
+  SHELL_VAR *v;
+
+  if (find_nameref && var_is_special == 0 && (v = find_variable_last_nameref (name, 0)) &&
+      nameref_p (v) && (t = nameref_cell (v)) && *t)
+    return (savestring (t));
 
+  /* If var_is_special == 0, and name is not an array reference, this does
+     more expansion than necessary.  It should really look up the variable's
+     value and not try to expand it. */
   w = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND, 0);
   t = w->word;
   /* Have to dequote here if necessary */
@@ -5753,12 +6636,55 @@ parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, c
       free (t);
       t = temp;
     }
-  dispose_word_desc (w);
+  dispose_word_desc (w);
+
+  return t;
+}
+  
+/* Expand an indirect reference to a variable: ${!NAME} expands to the
+   value of the variable whose name is the value of NAME. */
+static WORD_DESC *
+parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at)
+     char *name;
+     int var_is_special, quoted;
+     int *quoted_dollar_atp, *contains_dollar_at;
+{
+  char *temp, *t;
+  WORD_DESC *w;
+  SHELL_VAR *v;
+
+  /* See if it's a nameref first, behave in ksh93-compatible fashion.
+     There is at least one incompatibility: given ${!foo[0]} where foo=bar,
+     bash performs an indirect lookup on foo[0] and expands the result;
+     ksh93 expands bar[0].  We could do that here -- there are enough usable
+     primitives to do that -- but do not at this point. */
+  if (var_is_special == 0 && (v = find_variable_last_nameref (name, 0)))
+    {
+      if (nameref_p (v) && (t = nameref_cell (v)) && *t)
+       {
+         w = alloc_word_desc ();
+         w->word = savestring (t);
+         w->flags = 0;
+         return w;
+       }
+    }
+
+  t = parameter_brace_find_indir (name, var_is_special, quoted, 0);
 
   chk_atstar (t, quoted, quoted_dollar_atp, contains_dollar_at);
   if (t == 0)
     return (WORD_DESC *)NULL;
 
+  if (valid_brace_expansion_word (t, SPECIAL_VAR (t, 0)) == 0)
+    {
+      report_error (_("%s: bad substitution"), t);
+      free (t);
+      w = alloc_word_desc ();
+      w->word = &expand_param_error;
+      w->flags = 0;
+      return (w);
+    }
+       
   w = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted, 0, 0);
   free (t);
 
@@ -5770,69 +6696,98 @@ parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, c
    "-", "+", or "=".  QUOTED is true if the entire brace expression occurs
    between double quotes. */
 static WORD_DESC *
-parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat)
+parameter_brace_expand_rhs (name, value, c, quoted, pflags, qdollaratp, hasdollarat)
      char *name, *value;
-     int c, quoted, *qdollaratp, *hasdollarat;
+     int c, quoted, pflags, *qdollaratp, *hasdollarat;
 {
   WORD_DESC *w;
   WORD_LIST *l;
-  char *t, *t1, *temp;
-  int hasdol;
+  char *t, *t1, *temp, *vname;
+  int l_hasdollat, sindex;
 
+/*itrace("parameter_brace_expand_rhs: %s:%s pflags = %d", name, value, pflags);*/
   /* If the entire expression is between double quotes, we want to treat
      the value as a double-quoted string, with the exception that we strip
      embedded unescaped double quotes (for sh backwards compatibility). */
   if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *value)
     {
-      hasdol = 0;
-      temp = string_extract_double_quoted (value, &hasdol, 1);
+      sindex = 0;
+      temp = string_extract_double_quoted (value, &sindex, SX_STRIPDQ);
     }
   else
     temp = value;
 
   w = alloc_word_desc ();
-  hasdol = 0;
+  l_hasdollat = 0;
   /* XXX was 0 not quoted */
-  l = *temp ? expand_string_for_rhs (temp, quoted, &hasdol, (int *)NULL)
+  l = *temp ? expand_string_for_rhs (temp, quoted, &l_hasdollat, (int *)NULL)
            : (WORD_LIST *)0;
   if (hasdollarat)
-    *hasdollarat = hasdol || (l && l->next);
+    *hasdollarat = l_hasdollat || (l && l->next);
   if (temp != value)
     free (temp);
   if (l)
     {
+      /* If l->next is not null, we know that TEMP contained "$@", since that
+        is the only expansion that creates more than one word. */
+      if (qdollaratp && ((l_hasdollat && quoted) || l->next))
+       {
+/*itrace("parameter_brace_expand_rhs: %s:%s: l != NULL, set *qdollaratp", name, value);*/
+         *qdollaratp = 1;
+       }
+
       /* The expansion of TEMP returned something.  We need to treat things
-         slightly differently if HASDOL is non-zero.  If we have "$@", the
-         individual words have already been quoted.  We need to turn them
+         slightly differently if L_HASDOLLAT is non-zero.  If we have "$@",
+         the individual words have already been quoted.  We need to turn them
          into a string with the words separated by the first character of
          $IFS without any additional quoting, so string_list_dollar_at won't
-         do the right thing.  We use string_list_dollar_star instead. */
-      temp = (hasdol || l->next) ? string_list_dollar_star (l) : string_list (l);
+         do the right thing.  If IFS is null, we want "$@" to split into
+         separate arguments, not be concatenated, so we use string_list_internal
+         and mark the word to be split on spaces later.  We use
+         string_list_dollar_star for "$@" otherwise. */
+      if (l->next && ifs_is_null)
+       {
+         temp = string_list_internal (l, " ");
+         w->flags |= W_SPLITSPACE;
+       }
+      else
+       temp = (l_hasdollat || l->next) ? string_list_dollar_star (l) : string_list (l);
 
-      /* If l->next is not null, we know that TEMP contained "$@", since that
-        is the only expansion that creates more than one word. */
-      if (qdollaratp && ((hasdol && quoted) || l->next))
-       *qdollaratp = 1;
       /* If we have a quoted null result (QUOTED_NULL(temp)) and the word is
         a quoted null (l->next == 0 && QUOTED_NULL(l->word->word)), the
         flags indicate it (l->word->flags & W_HASQUOTEDNULL), and the
         expansion is quoted (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
         (which is more paranoia than anything else), we need to return the
         quoted null string and set the flags to indicate it. */
-      if (l->next == 0 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && QUOTED_NULL(temp) && QUOTED_NULL(l->word->word) && (l->word->flags & W_HASQUOTEDNULL))
+      if (l->next == 0 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && QUOTED_NULL (temp) && QUOTED_NULL (l->word->word) && (l->word->flags & W_HASQUOTEDNULL))
        {
          w->flags |= W_HASQUOTEDNULL;
+/*itrace("parameter_brace_expand_rhs (%s:%s): returning quoted null, turning off qdollaratp", name, value);*/
+         /* If we return a quoted null with L_HASDOLLARAT, we either have a
+            construct like "${@-$@}" or "${@-${@-$@}}" with no positional
+            parameters or a quoted expansion of "$@" with $1 == ''.  In either
+            case, we don't want to enable special handling of $@. */
+         if (qdollaratp && l_hasdollat)
+           *qdollaratp = 0;
        }
       dispose_words (l);
     }
-  else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && hasdol)
+  else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && l_hasdollat)
     {
+      /* Posix interp 221 changed the rules on this.  The idea is that
+        something like "$xxx$@" should expand the same as "${foo-$xxx$@}"
+        when foo and xxx are unset.  The problem is that it's not in any
+        way backwards compatible and few other shells do it.  We're eventually
+        going to try and split the difference (heh) a little bit here. */
+      /* l_hasdollat == 1 means we saw a quoted dollar at.  */
+
       /* The brace expansion occurred between double quotes and there was
         a $@ in TEMP.  It does not matter if the $@ is quoted, as long as
         it does not expand to anything.  In this case, we want to return
-        a quoted empty string. */
+        a quoted empty string.  Posix interp 888 */
       temp = make_quoted_char ('\0');
       w->flags |= W_HASQUOTEDNULL;
+/*itrace("parameter_brace_expand_rhs (%s:%s): returning quoted null", name, value);*/
     }
   else
     temp = (char *)NULL;
@@ -5847,12 +6802,40 @@ parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat)
   t = temp ? savestring (temp) : savestring ("");
   t1 = dequote_string (t);
   free (t);
+
+  /* bash-4.4/5.0 */
+  vname = name;
+  if (*name == '!' &&
+      (legal_variable_starter ((unsigned char)name[1]) || DIGIT (name[1]) || VALID_INDIR_PARAM (name[1])))
+    {
+      vname = parameter_brace_find_indir (name + 1, SPECIAL_VAR (name, 1), quoted, 1);
+      if (vname == 0 || *vname == 0)
+       {
+         report_error (_("%s: invalid indirect expansion"), name);
+         free (vname);
+         dispose_word (w);
+         return &expand_wdesc_error;
+       }
+      if (legal_identifier (vname) == 0)
+       {
+         report_error (_("%s: invalid variable name"), vname);
+         free (vname);
+         dispose_word (w);
+         return &expand_wdesc_error;
+       }
+    }
+    
 #if defined (ARRAY_VARS)
-  if (valid_array_reference (name))
-    assign_array_element (name, t1, 0);
+  if (valid_array_reference (vname, 0))
+    assign_array_element (vname, t1, 0);
   else
 #endif /* ARRAY_VARS */
-  bind_variable (name, t1, 0);
+  bind_variable (vname, t1, 0);
+
+  stupidly_hack_special_variables (vname);
+
+  if (vname != name)
+    free (vname);
 
   /* From Posix group discussion Feb-March 2010.  Issue 7 0000221 */
   free (temp);
@@ -5872,6 +6855,7 @@ parameter_brace_expand_error (name, value)
   WORD_LIST *l;
   char *temp;
 
+  last_command_exit_value = EXECUTION_FAILURE; /* ensure it's non-zero */
   if (value && *value)
     {
       l = expand_string (value, 0);
@@ -5899,7 +6883,7 @@ valid_length_expression (name)
          ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') ||  /* special param */
          (DIGIT (name[1]) && all_digits (name + 1)) || /* ${#11} */
 #if defined (ARRAY_VARS)
-         valid_array_reference (name + 1) ||                   /* ${#a[7]} */
+         valid_array_reference (name + 1, 0) ||                /* ${#a[7]} */
 #endif
          legal_identifier (name + 1));                         /* ${#PS1} */
 }
@@ -5919,7 +6903,7 @@ parameter_brace_expand_length (name)
 
   if (name[1] == '\0')                 /* ${#} */
     number = number_of_args ();
-  else if ((name[1] == '@' || name[1] == '*') && name[2] == '\0')      /* ${#@}, ${#*} */
+  else if (DOLLAR_AT_STAR (name[1]) && name[2] == '\0')        /* ${#@}, ${#*} */
     number = number_of_args ();
   else if ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0')
     {
@@ -5949,7 +6933,7 @@ parameter_brace_expand_length (name)
       FREE (t);
     }
 #if defined (ARRAY_VARS)
-  else if (valid_array_reference (name + 1))
+  else if (valid_array_reference (name + 1, 0))
     number = array_length_reference (name + 1);
 #endif /* ARRAY_VARS */
   else
@@ -5999,6 +6983,7 @@ parameter_brace_expand_length (name)
    the first DELIM, instead of using strchr(3).  Two rules:
        1.  If the substring contains a `(', read until closing `)'.
        2.  If the substring contains a `?', read past one `:' for each `?'.
+   The SD_ARITHEXP flag to skip_to_delim takes care of doing this.
 */
 
 static char *
@@ -6006,51 +6991,13 @@ skiparith (substr, delim)
      char *substr;
      int delim;
 {
-  size_t sublen;
-  int skipcol, pcount, i;
-  DECLARE_MBSTATE;
-
-  sublen = strlen (substr);
-  i = skipcol = pcount = 0;
-  while (substr[i])
-    {
-      /* Balance parens */
-      if (substr[i] == LPAREN)
-       {
-         pcount++;
-         i++;
-         continue;
-       }
-      if (substr[i] == RPAREN && pcount)
-       {
-         pcount--;
-         i++;
-         continue;
-       }
-      if (pcount)
-       {
-         ADVANCE_CHAR (substr, sublen, i);
-         continue;
-       }
+  int i;
+  char delims[2];
 
-      /* Skip one `:' for each `?' */
-      if (substr[i] == ':' && skipcol)
-       {
-         skipcol--;
-         i++;
-         continue;
-       }
-      if (substr[i] == delim)
-       break;
-      if (substr[i] == '?')
-       {
-         skipcol++;
-         i++;
-         continue;
-       }
-      ADVANCE_CHAR (substr, sublen, i);
-    }
+  delims[0] = delim;
+  delims[1] = '\0';
 
+  i = skip_to_delim (substr, 0, delims, SD_ARITHEXP);
   return (substr + i);
 }
 
@@ -6145,7 +7092,13 @@ verify_substring_values (v, value, substr, vtype, e1p, e2p)
       free (temp1);
       if (expok == 0)
        return (0);
+#if 1
       if ((vtype == VT_ARRAYVAR || vtype == VT_POSPARMS) && *e2p < 0)
+#else
+      /* bash-4.3: allow positional parameter length < 0 to count backwards
+        from end of positional parameters */
+      if (vtype == VT_ARRAYVAR && *e2p < 0)
+#endif
        {
          internal_error (_("%s: substring expression < 0"), t);
          return (0);
@@ -6196,31 +7149,53 @@ get_var_and_type (varname, value, ind, quoted, flags, varp, valp)
      SHELL_VAR **varp;
      char **valp;
 {
-  int vtype;
-  char *temp;
-#if defined (ARRAY_VARS)
+  int vtype, want_indir;
+  char *temp, *vname;
+  WORD_DESC *wd;
   SHELL_VAR *v;
-#endif
   arrayind_t lind;
 
+  want_indir = *varname == '!' &&
+    (legal_variable_starter ((unsigned char)varname[1]) || DIGIT (varname[1])
+                                       || VALID_INDIR_PARAM (varname[1]));
+  if (want_indir)
+    vname = parameter_brace_find_indir (varname+1, SPECIAL_VAR (varname, 1), quoted, 1);
+    /* XXX - what if vname == 0 || *vname == 0 ? */
+  else
+    vname = varname;
+
+  if (vname == 0)
+    {
+      vtype = VT_VARIABLE;
+      *varp = (SHELL_VAR *)NULL;
+      *valp = (char *)NULL;
+      return (vtype);
+    }
+
   /* This sets vtype to VT_VARIABLE or VT_POSPARMS */
-  vtype = (varname[0] == '@' || varname[0] == '*') && varname[1] == '\0';
-  if (vtype == VT_POSPARMS && varname[0] == '*')
+  vtype = STR_DOLLAR_AT_STAR (vname);
+  if (vtype == VT_POSPARMS && vname[0] == '*')
     vtype |= VT_STARSUB;
   *varp = (SHELL_VAR *)NULL;
 
 #if defined (ARRAY_VARS)
-  if (valid_array_reference (varname))
+  if (valid_array_reference (vname, 0))
     {
-      v = array_variable_part (varname, &temp, (int *)0);
+      v = array_variable_part (vname, &temp, (int *)0);
       /* If we want to signal array_value to use an already-computed index,
         set LIND to that index */
       lind = (ind != INTMAX_MIN && (flags & AV_USEIND)) ? ind : 0;
+      if (v && invisible_p (v))
+       {
+         vtype = VT_ARRAYMEMBER;
+         *varp = (SHELL_VAR *)NULL;
+         *valp = (char *)NULL;
+       }
       if (v && (array_p (v) || assoc_p (v)))
        { /* [ */
          if (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']')
            {
-             /* Callers have to differentiate betwen indexed and associative */
+             /* Callers have to differentiate between indexed and associative */
              vtype = VT_ARRAYVAR;
              if (temp[0] == '*')
                vtype |= VT_STARSUB;
@@ -6229,7 +7204,7 @@ get_var_and_type (varname, value, ind, quoted, flags, varp, valp)
          else
            {
              vtype = VT_ARRAYMEMBER;
-             *valp = array_value (varname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
+             *valp = array_value (vname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
            }
          *varp = v;
        }
@@ -6246,10 +7221,10 @@ get_var_and_type (varname, value, ind, quoted, flags, varp, valp)
        {
          vtype = VT_ARRAYMEMBER;
          *varp = v;
-         *valp = array_value (varname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
+         *valp = array_value (vname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
        }
     }
-  else if ((v = find_variable (varname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v)))
+  else if ((v = find_variable (vname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v)))
     {
       vtype = VT_ARRAYMEMBER;
       *varp = v;
@@ -6260,6 +7235,7 @@ get_var_and_type (varname, value, ind, quoted, flags, varp, valp)
     {
       if (value && vtype == VT_VARIABLE)
        {
+         *varp = find_variable (vname);
          if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
            *valp = dequote_string (value);
          else
@@ -6269,6 +7245,9 @@ get_var_and_type (varname, value, ind, quoted, flags, varp, valp)
        *valp = value;
     }
 
+  if (want_indir)
+    free (vname);
+
   return vtype;
 }
 
@@ -6289,7 +7268,8 @@ mb_substring (string, s, e)
      int s, e;
 {
   char *tt;
-  int start, stop, i, slen;
+  int start, stop, i;
+  size_t slen;
   DECLARE_MBSTATE;
 
   start = 0;
@@ -6324,7 +7304,7 @@ parameter_brace_substring (varname, value, ind, substr, quoted, flags)
   char *temp, *val, *tt, *oname;
   SHELL_VAR *v;
 
-  if (value == 0)
+  if (value == 0 && ((varname[0] != '@' && varname[0] != '*') || varname[1]))
     return ((char *)NULL);
 
   oname = this_command_name;
@@ -6343,7 +7323,11 @@ parameter_brace_substring (varname, value, ind, substr, quoted, flags)
   r = verify_substring_values (v, val, substr, vtype, &e1, &e2);
   this_command_name = oname;
   if (r <= 0)
-    return ((r == 0) ? &expand_param_error : (char *)NULL);
+    {
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      return ((r == 0) ? &expand_param_error : (char *)NULL);
+    }
 
   switch (vtype)
     {
@@ -6402,6 +7386,7 @@ parameter_brace_substring (varname, value, ind, substr, quoted, flags)
 /*                                                             */
 /****************************************************************/
 
+#if 0  /* Unused */
 static int
 shouldexp_replacement (s)
      char *s;
@@ -6417,6 +7402,7 @@ shouldexp_replacement (s)
     }
   return 0;
 }
+#endif
 
 char *
 pat_subst (string, pat, rep, mflags)
@@ -6424,7 +7410,8 @@ pat_subst (string, pat, rep, mflags)
      int mflags;
 {
   char *ret, *s, *e, *str, *rstr, *mstr;
-  int rsize, rptr, l, replen, mtype, rxpand, rslen, mlen;
+  int rptr, mtype, rxpand, mlen;
+  size_t rsize, l, replen, rslen;
 
   if (string  == 0)
     return (savestring (""));
@@ -6442,6 +7429,8 @@ pat_subst (string, pat, rep, mflags)
    *       with REP and return the result.
    *   2.  A null pattern with mtype == MATCH_END means to append REP to
    *       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.
    */
   if ((pat == 0 || *pat == 0) && (mtype == MATCH_BEG || mtype == MATCH_END))
@@ -6463,32 +7452,43 @@ pat_subst (string, pat, rep, mflags)
        }
       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);
+    }
 
   ret = (char *)xmalloc (rsize = 64);
   ret[0] = '\0';
 
-  for (replen = STRLEN (rep), rptr = 0, str = string;;)
+  for (replen = STRLEN (rep), rptr = 0, str = string; *str;)
     {
       if (match_pattern (str, pat, mtype, &s, &e) == 0)
        break;
       l = s - str;
 
-      if (rxpand)
+      if (rep && rxpand)
         {
-          int x;
-          mlen = e - s;
-          mstr = xmalloc (mlen + 1);
+         int x;
+         mlen = e - s;
+         mstr = xmalloc (mlen + 1);
          for (x = 0; x < mlen; x++)
            mstr[x] = s[x];
-          mstr[mlen] = '\0';
-          rstr = strcreplace (rep, '&', mstr, 0);
-          rslen = strlen (rstr);
+         mstr[mlen] = '\0';
+         rstr = strcreplace (rep, '&', mstr, 0);
+         free (mstr);
+         rslen = strlen (rstr);
         }
       else
-        {
-          rstr = rep;
-          rslen = replen;
-        }
+       {
+         rstr = rep;
+         rslen = replen;
+       }
         
       RESIZE_MALLOCED_BUFFER (ret, rptr, (l + rslen), rsize, 64);
 
@@ -6563,18 +7563,7 @@ pos_params_pat_subst (string, pat, rep, mflags)
   pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
   qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
 
-#if 0
-  if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB))
-    ret = string_list_dollar_star (quote_list (save));
-  else if ((mflags & MATCH_STARSUB) == MATCH_STARSUB)
-    ret = string_list_dollar_star (save);
-  else if ((mflags & MATCH_QUOTED) == MATCH_QUOTED)
-    ret = string_list_dollar_at (save, qflags);
-  else
-    ret = string_list_dollar_star (save);
-#else
   ret = string_list_pos_params (pchar, save, qflags);
-#endif
 
   dispose_words (save);
 
@@ -6586,11 +7575,11 @@ pos_params_pat_subst (string, pat, rep, mflags)
    and the string to substitute.  QUOTED is a flags word containing
    the type of quoting currently in effect. */
 static char *
-parameter_brace_patsub (varname, value, ind, patsub, quoted, flags)
+parameter_brace_patsub (varname, value, ind, patsub, quoted, pflags, flags)
      char *varname, *value;
      int ind;
      char *patsub;
-     int quoted, flags;
+     int quoted, pflags, flags;
 {
   int vtype, mflags, starsub, delim;
   char *val, *temp, *pat, *rep, *p, *lpatsub, *tt;
@@ -6609,7 +7598,8 @@ parameter_brace_patsub (varname, value, ind, patsub, quoted, flags)
   vtype &= ~VT_STARSUB;
 
   mflags = 0;
-  if (patsub && *patsub == '/')
+  /* PATSUB is never NULL when this is called. */
+  if (*patsub == '/')
     {
       mflags |= MATCH_GLOBREP;
       patsub++;
@@ -6625,14 +7615,11 @@ parameter_brace_patsub (varname, value, ind, patsub, quoted, flags)
   if (starsub)
     mflags |= MATCH_STARSUB;
 
+  if (pflags & PF_ASSIGNRHS)
+    mflags |= MATCH_ASSIGNRHS;
+
   /* If the pattern starts with a `/', make sure we skip over it when looking
      for the replacement delimiter. */
-#if 0
-  if (rep = quoted_strchr ((*patsub == '/') ? lpatsub+1 : lpatsub, '/', ST_BACKSL))
-    *rep++ = '\0';
-  else
-    rep = (char *)NULL;
-#else
   delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0);
   if (lpatsub[delim] == '/')
     {
@@ -6641,7 +7628,6 @@ parameter_brace_patsub (varname, value, ind, patsub, quoted, flags)
     }
   else
     rep = (char *)NULL;
-#endif
 
   if (rep && *rep == '\0')
     rep = (char *)NULL;
@@ -6652,7 +7638,14 @@ parameter_brace_patsub (varname, value, ind, patsub, quoted, flags)
 
   if (rep)
     {
-      if ((mflags & MATCH_QUOTED) == 0)
+      /* We want to perform quote removal on the expanded replacement even if
+        the entire expansion is double-quoted because the parser and string
+        extraction functions treated quotes in the replacement string as
+        special.  THIS IS NOT BACKWARDS COMPATIBLE WITH BASH-4.2. */
+      if (shell_compatibility_level > 42)
+       rep = expand_string_if_necessary (rep, quoted & ~(Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT), expand_string_unsplit);
+      /* This is the bash-4.2 code. */      
+      else if ((mflags & MATCH_QUOTED) == 0)
        rep = expand_string_if_necessary (rep, quoted, expand_string_unsplit);
       else
        rep = expand_string_to_string_internal (rep, quoted, expand_string_unsplit);
@@ -6912,11 +7905,11 @@ chk_arithsub (s, len)
          break;
 
        case '\'':
-         i = skip_single_quoted (s, len, ++i);
+         i = skip_single_quoted (s, len, ++i, 0);
          break;
 
        case '"':
-         i = skip_double_quoted ((char *)s, len, ++i);
+         i = skip_double_quoted ((char *)s, len, ++i, 0);
          break;
        }
     }
@@ -6934,13 +7927,13 @@ chk_arithsub (s, len)
 static WORD_DESC *
 parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, contains_dollar_at)
      char *string;
-     int *indexp, quoted, *quoted_dollar_atp, *contains_dollar_at, pflags;
+     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;
   char *name, *value, *temp, *temp1;
   WORD_DESC *tdesc, *ret;
-  int t_index, sindex, c, tflag, modspec;
+  int t_index, sindex, c, tflag, modspec, all_element_arrayref;
   intmax_t number;
   arrayind_t ind;
 
@@ -6948,6 +7941,8 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
   var_is_set = var_is_null = var_is_special = check_nullness = 0;
   want_substring = want_indir = want_patsub = want_casemod = 0;
 
+  all_element_arrayref = 0;
+
   sindex = *indexp;
   t_index = ++sindex;
   /* ${#var} doesn't have any of the other parameter expansions on it. */
@@ -6958,14 +7953,31 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
     /* To enable case-toggling expansions using the `~' operator character
        change the 1 to 0. */
 #  if defined (CASEMOD_CAPCASE)
-    name = string_extract (string, &t_index, "#%^,~:-=?+/}", SX_VARNAME);
+    name = string_extract (string, &t_index, "#%^,~:-=?+/@}", SX_VARNAME);
 #  else
-    name = string_extract (string, &t_index, "#%^,:-=?+/}", SX_VARNAME);
+    name = string_extract (string, &t_index, "#%^,:-=?+/@}", SX_VARNAME);
 #  endif /* CASEMOD_CAPCASE */
 #else
-    name = string_extract (string, &t_index, "#%:-=?+/}", SX_VARNAME);
+    name = string_extract (string, &t_index, "#%:-=?+/@}", SX_VARNAME);
 #endif /* CASEMOD_EXPANSIONS */
 
+  /* Handle ${@[stuff]} now that @ is a word expansion operator.  Not exactly
+     the cleanest code ever. */
+  if (*name == 0 && sindex == t_index && string[sindex] == '@')
+    {
+      name = (char *)xrealloc (name, 2);
+      name[0] = '@';
+      name[1] = '\0';
+      t_index++;
+    }
+  else if (*name == '!' && t_index > sindex && string[t_index] == '@' && string[t_index+1] == '}')
+    {
+      name = (char *)xrealloc (name, t_index - sindex + 2);
+      name[t_index - sindex] = '@';
+      name[t_index - sindex + 1] = '\0';
+      t_index++;
+    }
+
   ret = 0;
   tflag = 0;
 
@@ -6973,14 +7985,16 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 
   /* If the name really consists of a special variable, then make sure
      that we have the entire name.  We don't allow indirect references
-     to special variables except `#', `?', `@' and `*'. */
+     to special variables except `#', `?', `@' and `*'.  This clause is
+     designed to handle ${#SPECIAL} and ${!SPECIAL}, not anything more
+     general. */
   if ((sindex == t_index && VALID_SPECIAL_LENGTH_PARAM (string[t_index])) ||
+      (sindex == t_index && string[sindex] == '#' && VALID_SPECIAL_LENGTH_PARAM (string[sindex + 1])) ||
       (sindex == t_index - 1 && string[sindex] == '!' && VALID_INDIR_PARAM (string[t_index])))
     {
       t_index++;
-      free (name);
-      temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0);
-      name = (char *)xmalloc (3 + (strlen (temp1)));
+      temp1 = string_extract (string, &t_index, "#%:-=?+/@}", 0);
+      name = (char *)xrealloc (name, 3 + (strlen (temp1)));
       *name = string[sindex];
       if (string[sindex] == '!')
        {
@@ -7011,7 +8025,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
     }
   else if (c == ':' && string[sindex] != RBRACE)
     want_substring = 1;
-  else if (c == '/' && string[sindex] != RBRACE)
+  else if (c == '/' /* && string[sindex] != RBRACE */) /* XXX */
     want_patsub = 1;
 #if defined (CASEMOD_EXPANSIONS)
   else if (c == '^' || c == ',' || c == '~')
@@ -7039,7 +8053,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
        member (c, "%:=+/") && string[sindex] == RBRACE)
     {
       temp = (char *)NULL;
-      goto bad_substitution;
+      goto bad_substitution;   /* XXX - substitution error */
     }
 
   /* Indirect expansion begins with a `!'.  A valid indirect expansion is
@@ -7066,7 +8080,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
       if (string[sindex - 1] != RBRACE || (valid_length_expression (name) == 0))
        {
          temp = (char *)NULL;
-         goto bad_substitution;
+         goto bad_substitution;        /* substitution error */
        }
 
       number = parameter_brace_expand_length (name);
@@ -7098,6 +8112,8 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 
       if (contains_dollar_at)
        *contains_dollar_at = 1;
+
+      tflag |= W_DOLLARAT;
     }
 
   /* Process ${!PREFIX*} expansion. */
@@ -7117,26 +8133,31 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
        temp = string_list_dollar_star (xlist);
       else
        {
-         temp = string_list_dollar_at (xlist, quoted);
+         temp = string_list_dollar_at (xlist, quoted, 0);
          if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
            *quoted_dollar_atp = 1;
          if (contains_dollar_at)
            *contains_dollar_at = 1;
+
+         tflag |= W_DOLLARAT;
        }
       free (x);
       dispose_words (xlist);
       free (temp1);
       *indexp = sindex;
 
+      free (name);
+
       ret = alloc_word_desc ();
       ret->word = temp;
+      ret->flags = tflag;      /* XXX */
       return ret;
     }
 
 #if defined (ARRAY_VARS)      
   /* Process ${!ARRAY[@]} and ${!ARRAY[*]} expansion. */ /* [ */
   if (want_indir && string[sindex - 1] == RBRACE &&
-      string[sindex - 2] == ']' && valid_array_reference (name+1))
+      string[sindex - 2] == ']' && valid_array_reference (name+1, 0))
     {
       char *x, *x1;
 
@@ -7152,13 +8173,17 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
                *quoted_dollar_atp = 1;
              if (contains_dollar_at)
                *contains_dollar_at = 1;
+
+             tflag |= W_DOLLARAT;
            }       
 
+         free (name);
          free (temp1);
          *indexp = sindex;
 
          ret = alloc_word_desc ();
          ret->word = temp;
+         ret->flags = tflag;   /* XXX */
          return ret;
        }
 
@@ -7171,13 +8196,24 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
                                        var_is_special) == 0)
     {
       temp = (char *)NULL;
-      goto bad_substitution;
+      goto bad_substitution;           /* substitution error */
     }
 
   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, quoted_dollar_atp, contains_dollar_at);
+      if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal)
+       {
+         temp = (char *)NULL;
+         goto bad_substitution;
+       }
+      /* Turn off the W_ARRAYIND flag because there is no way for this function
+        to return the index we're supposed to be using. */
+      if (tdesc && tdesc->flags)
+       tdesc->flags &= ~W_ARRAYIND;
+    }
   else
-    tdesc = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND|(pflags&PF_NOSPLIT2), &ind);
+    tdesc = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND|(pflags&(PF_NOSPLIT2|PF_ASSIGNRHS)), &ind);
 
   if (tdesc)
     {
@@ -7188,13 +8224,37 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
   else
     temp = (char  *)0;
 
+  if (temp == &expand_param_error || temp == &expand_param_fatal)
+    {
+      FREE (name);
+      FREE (value);
+      return (temp == &expand_param_error ? &expand_wdesc_error : &expand_wdesc_fatal);
+    }
+
 #if defined (ARRAY_VARS)
-  if (valid_array_reference (name))
-    chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at);
+  if (valid_array_reference (name, 0))
+    {
+      int qflags;
+      char *t;
+
+      qflags = quoted;
+      /* If in a context where word splitting will not take place, treat as
+        if double-quoted.  Has effects with $* and ${array[*]} */
+      if (pflags & PF_ASSIGNRHS)
+       qflags |= Q_DOUBLE_QUOTES;
+      chk_atstar (name, qflags, quoted_dollar_atp, contains_dollar_at);
+      /* We duplicate a little code here */
+      t = mbschr (name, '[');
+      if (t && ALL_ELEMENT_SUB (t[1]) && t[2] == ']')
+        all_element_arrayref = 1;
+    }
 #endif
 
   var_is_set = temp != (char *)0;
   var_is_null = check_nullness && (var_is_set == 0 || *temp == 0);
+  /* XXX - this may not need to be restricted to special variables */
+  if (check_nullness)
+    var_is_null |= var_is_set && var_is_special && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && QUOTED_NULL (temp);
 
   /* Get the rest of the stuff inside the braces. */
   if (c && c != RBRACE)
@@ -7205,7 +8265,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
       if (string[sindex] == RBRACE)
        sindex++;
       else
-       goto bad_substitution;
+       goto bad_substitution;          /* substitution error */
     }
   else
     value = (char *)NULL;
@@ -7216,7 +8276,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
      variable error. */
   if (want_substring || want_patsub || want_casemod || c == '#' || c == '%' || c == RBRACE)
     {
-      if (var_is_set == 0 && unbound_vars_is_error && ((name[0] != '@' && name[0] != '*') || name[1]))
+      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;
          err_unboundvar (name);
@@ -7242,13 +8302,19 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 
       ret = alloc_word_desc ();
       ret->word = temp1;
-      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+      /* We test quoted_dollar_atp because we want variants with double-quoted
+        "$@" to take a different code path. In fact, we make sure at the end
+        of expand_word_internal that we're only looking at these flags if
+        quoted_dollar_at == 0. */
+      if (temp1 && 
+          (quoted_dollar_atp == 0 || *quoted_dollar_atp == 0) &&
+         QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
        ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
       return ret;
     }
   else if (want_patsub)
     {
-      temp1 = parameter_brace_patsub (name, temp, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      temp1 = parameter_brace_patsub (name, temp, ind, value, quoted, pflags, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
       FREE (name);
       FREE (value);
       FREE (temp);
@@ -7260,9 +8326,9 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 
       ret = alloc_word_desc ();
       ret->word = temp1;
-      ret = alloc_word_desc ();
-      ret->word = temp1;
-      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+      if (temp1 && 
+          (quoted_dollar_atp == 0 || *quoted_dollar_atp == 0) &&
+         QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
        ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
       return ret;
     }
@@ -7281,7 +8347,9 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
 
       ret = alloc_word_desc ();
       ret->word = temp1;
-      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+      if (temp1 &&
+          (quoted_dollar_atp == 0 || *quoted_dollar_atp == 0) &&
+         QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
        ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
       return ret;
     }
@@ -7292,17 +8360,38 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
     {
     default:
     case '\0':
-    bad_substitution:
+bad_substitution:
       last_command_exit_value = EXECUTION_FAILURE;
       report_error (_("%s: bad substitution"), string ? string : "??");
       FREE (value);
       FREE (temp);
       free (name);
-      return &expand_wdesc_error;
+      if (shell_compatibility_level <= 43)
+       return &expand_wdesc_error;
+      else
+       return ((posixly_correct && interactive_shell == 0) ? &expand_wdesc_fatal : &expand_wdesc_error);
 
     case RBRACE:
       break;
 
+    case '@':
+      temp1 = parameter_brace_transform (name, temp, ind, value, c, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      free (temp);
+      free (value);
+      free (name);
+      if (temp1 == &expand_param_error || temp1 == &expand_param_fatal)
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         report_error (_("%s: bad substitution"), string ? string : "??");
+         return (temp1 == &expand_param_error ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
+
+      ret = alloc_word_desc ();
+      ret->word = temp1;
+      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+       ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
+      return ret;
+
     case '#':  /* ${param#[#]pattern} */
     case '%':  /* ${param%[%]pattern} */
       if (value == 0 || *value == '\0' || temp == 0 || *temp == '\0')
@@ -7349,6 +8438,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
                    quoted |= Q_DOLBRACE;
                  ret = parameter_brace_expand_rhs (name, value, c,
                                                    quoted,
+                                                   pflags,
                                                    quoted_dollar_atp,
                                                    contains_dollar_at);
                  /* XXX - fix up later, esp. noting presence of
@@ -7370,6 +8460,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
          temp = (char *)NULL;
          if (c == '=' && var_is_special)
            {
+             last_command_exit_value = EXECUTION_FAILURE;
              report_error (_("$%s: cannot assign in this way"), name);
              free (name);
              free (value);
@@ -7395,7 +8486,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
                 removed. */
              if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
                quoted |= Q_DOLBRACE;
-             ret = parameter_brace_expand_rhs (name, value, c, quoted,
+             ret = parameter_brace_expand_rhs (name, value, c, quoted, pflags,
                                                quoted_dollar_atp,
                                                contains_dollar_at);
              /* XXX - fix up later, esp. noting presence of
@@ -7428,7 +8519,7 @@ param_expand (string, sindex, quoted, expanded_something,
      int *sindex, quoted, *expanded_something, *contains_dollar_at;
      int *quoted_dollar_at_p, *had_quoted_null_p, pflags;
 {
-  char *temp, *temp1, uerror[3];
+  char *temp, *temp1, uerror[3], *savecmd;
   int zindex, t_index, expok;
   unsigned char c;
   intmax_t number;
@@ -7437,6 +8528,7 @@ param_expand (string, sindex, quoted, expanded_something,
   WORD_DESC *tdesc, *ret;
   int tflag;
 
+/*itrace("param_expand: `%s' pflags = %d", string+*sindex, pflags);*/
   zindex = *sindex;
   c = string[++zindex];
 
@@ -7560,10 +8652,11 @@ param_expand (string, sindex, quoted, expanded_something,
          temp = (quoted & (Q_DOUBLE_QUOTES|Q_PATQUOTE)) ? string_list_dollar_star (list) : string_list (list);
          if (temp)
            {
-             temp1 = quote_string (temp);
+             temp1 = (quoted & Q_DOUBLE_QUOTES) ? quote_string (temp) : temp;
              if (*temp == 0)
                tflag |= W_HASQUOTEDNULL;
-             free (temp);
+             if (temp != temp1)
+               free (temp);
              temp = temp1;
            }
        }
@@ -7574,7 +8667,6 @@ param_expand (string, sindex, quoted, expanded_something,
             an assignment statement.  In that case, we don't separate the
             arguments at all.  Otherwise, if the $* is not quoted it is
             identical to $@ */
-#if 1
 #  if defined (HANDLE_MULTIBYTE)
          if (expand_no_split_dollar_star && ifs_firstc[0] == 0)
 #  else
@@ -7582,10 +8674,21 @@ param_expand (string, sindex, quoted, expanded_something,
 #  endif
            temp = string_list_dollar_star (list);
          else
-           temp = string_list_dollar_at (list, quoted);
-#else
-         temp = string_list_dollar_at (list, quoted);
-#endif
+           {
+             temp = string_list_dollar_at (list, quoted, 0);
+             if (quoted == 0 && (ifs_is_set == 0 || ifs_is_null))
+               tflag |= W_SPLITSPACE;
+             /* If we're not quoted but we still don't want word splitting, make
+                we quote the IFS characters to protect them from splitting (e.g.,
+                when $@ is in the string as well). */
+             else if (temp && quoted == 0 && ifs_is_set && (pflags & PF_ASSIGNRHS))
+               {
+                 temp1 = quote_string (temp);
+                 free (temp);
+                 temp = temp1;
+               }
+           }
+
          if (expand_no_split_dollar_star == 0 && contains_dollar_at)
            *contains_dollar_at = 1;
        }
@@ -7631,18 +8734,19 @@ param_expand (string, sindex, quoted, expanded_something,
       if (contains_dollar_at)
        *contains_dollar_at = 1;
 
-#if 0
-      if (pflags & PF_NOSPLIT2)
-       temp = string_list_internal (quoted ? quote_list (list) : list, " ");
-      else
-#endif
       /* We want to separate the positional parameters with the first
         character of $IFS in case $IFS is something other than a space.
         We also want to make sure that splitting is done no matter what --
         according to POSIX.2, this expands to a list of the positional
         parameters no matter what IFS is set to. */
-      temp = string_list_dollar_at (list, quoted);
-
+      /* 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 $* ?  Maybe we should use PF_NOSPLIT2
+        here. */
+      /* XXX - bash-4.4/bash-5.0 passing PFLAGS */
+      temp = string_list_dollar_at (list, (pflags & PF_ASSIGNRHS) ? (quoted|Q_DOUBLE_QUOTES) : quoted, pflags);
+
+      tflag |= W_DOLLARAT;
       dispose_words (list);
       break;
 
@@ -7682,7 +8786,9 @@ param_expand (string, sindex, quoted, expanded_something,
     case LPAREN:
       /* We have to extract the contents of this paren substitution. */
       t_index = zindex + 1;
-      temp = extract_command_subst (string, &t_index, 0);
+      /* XXX - might want to check for string[t_index+2] == LPAREN and parse
+        as arithmetic substitution immediately. */
+      temp = extract_command_subst (string, &t_index, (pflags&PF_COMPLETE) ? SX_COMPLETE : 0);
       zindex = t_index;
 
       /* For Posix.2-style `$(( ))' arithmetic substitution,
@@ -7713,13 +8819,15 @@ param_expand (string, sindex, quoted, expanded_something,
            }
 
          /* Expand variables found inside the expression. */
-         temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES);
+         temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES|Q_ARITH);
          free (temp2);
 
 arithsub:
          /* No error messages. */
+         savecmd = this_command_name;
          this_command_name = (char *)NULL;
          number = evalexp (temp1, &expok);
+         this_command_name = savecmd;
          free (temp);
          free (temp1);
          if (expok == 0)
@@ -7767,7 +8875,7 @@ comsub:
        }         
 
        /* Do initial variable expansion. */
-      temp1 = expand_arith_string (temp, Q_DOUBLE_QUOTES);
+      temp1 = expand_arith_string (temp, Q_DOUBLE_QUOTES|Q_ARITH);
 
       goto arithsub;
 
@@ -7822,6 +8930,32 @@ comsub:
 
          goto return0;
        }
+      else if (var && (invisible_p (var) || var_isset (var) == 0))
+       temp = (char *)NULL;
+      else if ((var = find_variable_last_nameref (temp1, 0)) && var_isset (var) && invisible_p (var) == 0)
+       {
+         temp = nameref_cell (var);
+#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);
+             if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal)
+               return (tdesc);
+             ret = tdesc;
+             goto return0;
+           }
+         else
+#endif
+         /* y=2 ; typeset -n x=y; echo $x is not the same as echo $2 in ksh */
+         if (temp && *temp && legal_identifier (temp) == 0)
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+             report_error (_("%s: invalid variable name for name reference"), temp);
+             return (&expand_wdesc_error);     /* XXX */
+           }
+         else
+           temp = (char *)NULL;
+       }
 
       temp = (char *)NULL;
 
@@ -7859,6 +8993,13 @@ return0:
   return ret;
 }
 
+void
+invalidate_cached_quoted_dollar_at ()
+{
+  dispose_words (cached_quoted_dollar_at);
+  cached_quoted_dollar_at = 0;
+}
+
 /* Make a word list which is the result of parameter and variable
    expansion, command substitution, arithmetic substitution, and
    quote removal of WORD.  Return a pointer to a WORD_LIST which is
@@ -7899,7 +9040,7 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
   char *istring;
 
   /* The current size of the above object. */
-  int istring_size;
+  size_t istring_size;
 
   /* Index into ISTRING. */
   int istring_index;
@@ -7927,8 +9068,10 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
   /* State flags */
   int had_quoted_null;
   int has_dollar_at, temp_has_dollar_at;
+  int split_on_spaces;
   int tflag;
   int pflags;                  /* flags passed to param_expand */
+  int mb_cur_max;
 
   int assignoff;               /* If assignment, offset of `=' */
 
@@ -7939,17 +9082,37 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
 
   DECLARE_MBSTATE;
 
+  /* OK, let's see if we can optimize a common idiom: "$@" */
+  if (STREQ (word->word, "\"$@\"") &&
+      (word->flags == (W_HASDOLLAR|W_QUOTED)) &&
+      dollar_vars[1])          /* XXX - check IFS here as well? */
+    {
+      if (contains_dollar_at)
+       *contains_dollar_at = 1;
+      if (expanded_something)
+       *expanded_something = 1;
+      if (cached_quoted_dollar_at)
+       return (copy_word_list (cached_quoted_dollar_at));
+      list = list_rest_of_args ();
+      list = quote_list (list);
+      cached_quoted_dollar_at = copy_word_list (list);
+      return (list);
+    }
+
   istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE);
   istring[istring_index = 0] = '\0';
   quoted_dollar_at = had_quoted_null = has_dollar_at = 0;
+  split_on_spaces = 0;
   quoted_state = UNQUOTED;
 
   string = word->word;
   if (string == 0)
     goto finished_with_string;
+  mb_cur_max = MB_CUR_MAX;
+
   /* Don't need the string length for the SADD... and COPY_ macros unless
      multibyte characters are possible. */
-  string_size = (MB_CUR_MAX > 1) ? strlen (string) : 1;
+  string_size = (mb_cur_max > 1) ? strlen (string) : 1;
 
   if (contains_dollar_at)
     *contains_dollar_at = 0;
@@ -7962,7 +9125,7 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
     {
       c = string[sindex];
 
-      /* Case on toplevel character. */
+      /* Case on top-level character. */
       switch (c)
        {
        case '\0':
@@ -7971,7 +9134,7 @@ expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_somethin
        case CTLESC:
          sindex++;
 #if HANDLE_MULTIBYTE
-         if (MB_CUR_MAX > 1 && string[sindex])
+         if (mb_cur_max > 1 && string[sindex])
            {
              SADD_MBQCHAR_BODY(temp, string, sindex, string_size);
            }
@@ -8002,6 +9165,9 @@ add_string:
        case '<':
        case '>':
          {
+           /* bash-4.4/bash-5.0
+              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)
              {
                sindex--;       /* add_character: label increments sindex */
@@ -8010,7 +9176,7 @@ add_string:
            else
              t_index = sindex + 1; /* skip past both '<' and LPAREN */
 
-           temp1 = extract_process_subst (string, (c == '<') ? "<(" : ">(", &t_index); /*))*/
+           temp1 = extract_process_subst (string, (c == '<') ? "<(" : ">(", &t_index, 0); /*))*/
            sindex = t_index;
 
            /* If the process substitution specification is `<()', we want to
@@ -8058,6 +9224,11 @@ add_string:
                   string[sindex+1] == '~')
            word->flags |= W_ITILDE;
 #endif
+
+         /* XXX - bash-4.4/bash-5.0 */
+         if (word->flags & W_ASSIGNARG)
+           word->flags |= W_ASSIGNRHS;         /* affects $@ */
+
          if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
            goto add_ifs_character;
          else
@@ -8084,10 +9255,12 @@ add_string:
        case '~':
          /* If the word isn't supposed to be tilde expanded, or we're not
             at the start of a word or after an unquoted : or = in an
-            assignment statement, we don't do tilde expansion. */
+            assignment statement, we don't do tilde expansion.  If we don't want
+            tilde expansion when expanding words to be passed to the arithmetic
+            evaluator, remove the check for Q_ARITH. */
          if ((word->flags & (W_NOTILDE|W_DQUOTE)) ||
              (sindex > 0 && ((word->flags & W_ITILDE) == 0)) ||
-             (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+             ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) && ((quoted & Q_ARITH) == 0)))
            {
              word->flags &= ~W_ITILDE;
              if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0)
@@ -8135,10 +9308,15 @@ add_string:
          pflags = (word->flags & W_NOCOMSUB) ? PF_NOCOMSUB : 0;
          if (word->flags & W_NOSPLIT2)
            pflags |= PF_NOSPLIT2;
+         if (word->flags & W_ASSIGNRHS)
+           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);
          has_dollar_at += temp_has_dollar_at;
+         split_on_spaces += (tword->flags & W_SPLITSPACE);
 
          if (tword == &expand_wdesc_error || tword == &expand_wdesc_fatal)
            {
@@ -8153,7 +9331,7 @@ add_string:
          if (tword && (tword->flags & W_HASQUOTEDNULL))
            had_quoted_null = 1;
 
-         temp = tword->word;
+         temp = tword ? tword->word : (char *)NULL;
          dispose_word_desc (tword);
 
          /* Kill quoted nulls; we will add them back at the end of
@@ -8181,6 +9359,7 @@ add_string:
                    sindex = t_index;
                    goto add_character;
                  }
+               last_command_exit_value = EXECUTION_FAILURE;
                report_error (_("bad substitution: no closing \"`\" in %s") , string+t_index);
                free (string);
                free (istring);
@@ -8227,6 +9406,17 @@ add_string:
             a } in ${...} is removed.  Issue 0000221 */
          if ((quoted & Q_DOLBRACE) && c == RBRACE)
            {
+             SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
+           }
+         /* This is the fix for " $@\ " */
+         else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0) && isexp == 0 && isifs (c))
+           {
+             RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size,
+                                     DEFAULT_ARRAY_SIZE);
+             istring[istring_index++] = CTLESC;
+             istring[istring_index++] = '\\';
+             istring[istring_index] = '\0';
+
              SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
            }
          else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0))
@@ -8256,15 +9446,11 @@ add_twochars:
          break;
 
        case '"':
-#if 0
-         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE))
-#else
-         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
-#endif
+         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) && ((quoted & Q_ARITH) == 0))
            goto add_character;
 
          t_index = ++sindex;
-         temp = string_extract_double_quoted (string, &sindex, 0);
+         temp = string_extract_double_quoted (string, &sindex, (word->flags & W_COMPLETE) ? SX_COMPLETE : 0);
 
          /* If the quotes surrounded the entire string, then the
             whole word was quoted. */
@@ -8277,6 +9463,16 @@ add_twochars:
              tword = alloc_word_desc ();
              tword->word = temp;
 
+             /* XXX - bash-4.4/bash-5.0 */
+             if (word->flags & W_ASSIGNARG)
+               tword->flags |= word->flags & (W_ASSIGNARG|W_ASSIGNRHS);        /* affects $@ */
+             if (word->flags & W_COMPLETE)
+               tword->flags |= W_COMPLETE;     /* for command substitutions */
+             if (word->flags & W_NOCOMSUB)
+               tword->flags |= W_NOCOMSUB;
+             if (word->flags & W_NOPROCSUB)
+               tword->flags |= W_NOPROCSUB;
+
              temp = (char *)NULL;
 
              temp_has_dollar_at = 0;   /* XXX */
@@ -8300,7 +9496,7 @@ add_twochars:
              /* "$@" (a double-quoted dollar-at) expands into nothing,
                 not even a NULL word, when there are no positional
                 parameters. */
-             if (list == 0 && has_dollar_at)
+             if (list == 0 && temp_has_dollar_at)      /* XXX - was has_dollar_at */
                {
                  quoted_dollar_at++;
                  break;
@@ -8318,9 +9514,9 @@ add_twochars:
                dequote_list (list);
 
              if (list && list->word && (list->word->flags & W_HASQUOTEDNULL))
-               had_quoted_null = 1;
+               had_quoted_null = 1;            /* XXX */
 
-             if (has_dollar_at)
+             if (temp_has_dollar_at)           /* XXX - was has_dollar_at */
                {
                  quoted_dollar_at++;
                  if (contains_dollar_at)
@@ -8348,15 +9544,10 @@ add_twochars:
            {
              if (list->next)
                {
-#if 0
-                 if (quoted_dollar_at && (word->flags & W_NOSPLIT2))
-                   temp = string_list_internal (quote_list (list), " ");
-                 else
-#endif
                  /* Testing quoted_dollar_at makes sure that "$@" is
                     split correctly when $IFS does not contain a space. */
                  temp = quoted_dollar_at
-                               ? string_list_dollar_at (list, Q_DOUBLE_QUOTES)
+                               ? string_list_dollar_at (list, Q_DOUBLE_QUOTES, 0)
                                : string_list (quote_list (list));
                  dispose_words (list);
                  goto add_string;
@@ -8388,7 +9579,10 @@ add_twochars:
            temp = (char *)NULL;
 
          /* We do not want to add quoted nulls to strings that are only
-            partially quoted; we can throw them away. */
+            partially quoted; we can throw them away.  The exception to
+            this is when we are going to be performing word splitting,
+            since we have to preserve a null argument if the next character
+            will cause word splitting. */
          if (temp == 0 && quoted_state == PARTIALLY_QUOTED && (word->flags & (W_NOSPLIT|W_NOSPLIT2)))
            continue;
 
@@ -8412,11 +9606,7 @@ add_twochars:
          /* break; */
 
        case '\'':
-#if 0
-         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE))
-#else
          if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
-#endif
            goto add_character;
 
          t_index = ++sindex;
@@ -8438,8 +9628,9 @@ add_twochars:
            remove_quoted_escapes (temp);       /* ??? */
 
          /* We do not want to add quoted nulls to strings that are only
-            partially quoted; such nulls are discarded. */
-         if (temp == 0 && (quoted_state == PARTIALLY_QUOTED))
+            partially quoted; such nulls are discarded.  See above for the
+            exception, which is when the string is going to be split. */
+         if (temp == 0 && (quoted_state == PARTIALLY_QUOTED) && (word->flags & (W_NOSPLIT|W_NOSPLIT2)))
            continue;
 
          /* If we have a quoted null expansion, add a quoted NULL to istring. */
@@ -8457,7 +9648,7 @@ add_twochars:
        default:
          /* This is the fix for " $@ " */
        add_ifs_character:
-         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (isexp == 0 && isifs (c)))
+         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (isexp == 0 && isifs (c) && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0))
            {
              if (string[sindex])       /* from old goto dollar_add_string */
                sindex++;
@@ -8469,10 +9660,10 @@ add_twochars:
              else
                {
 #if HANDLE_MULTIBYTE
-                 if (MB_CUR_MAX > 1)
+                 if (mb_cur_max > 1)
                    sindex--;
 
-                 if (MB_CUR_MAX > 1)
+                 if (mb_cur_max > 1)
                    {
                      SADD_MBQCHAR_BODY(temp, string, sindex, string_size);
                    }
@@ -8541,6 +9732,8 @@ finished_with_string:
       /* According to sh, ksh, and Posix.2, if a word expands into nothing
         and a double-quoted "$@" appears anywhere in it, then the entire
         word is removed. */
+      /* XXX - exception appears to be that quoted null strings result in
+        null arguments */
       else  if (quoted_state == UNQUOTED || quoted_dollar_at)
        list = (WORD_LIST *)NULL;
 #if 0
@@ -8565,6 +9758,8 @@ finished_with_string:
        tword->flags |= W_COMPASSIGN;   /* XXX */
       if (word->flags & W_NOGLOB)
        tword->flags |= W_NOGLOB;       /* XXX */
+      if (word->flags & W_NOBRACE)
+       tword->flags |= W_NOBRACE;      /* XXX */
       if (word->flags & W_NOEXPAND)
        tword->flags |= W_NOEXPAND;     /* XXX */
       if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
@@ -8576,6 +9771,7 @@ finished_with_string:
   else
     {
       char *ifs_chars;
+      char *tstring;
 
       ifs_chars = (quoted_dollar_at || has_dollar_at) ? ifs_value : (char *)NULL;
 
@@ -8584,12 +9780,55 @@ finished_with_string:
         positional parameters with a space, so we split on space (we have
         set ifs_chars to " \t\n" above if ifs is unset).  If IFS is set,
         string_list_dollar_at has separated the positional parameters
-        with the first character of $IFS, so we split on $IFS. */
-      if (has_dollar_at && ifs_chars)
+        with the first character of $IFS, so we split on $IFS.  If
+        SPLIT_ON_SPACES is set, we expanded $* (unquoted) with IFS either
+        unset or null, and we want to make sure that we split on spaces
+        regardless of what else has happened to IFS since the expansion,
+        or we expanded "$@" with IFS null and we need to split the positional
+        parameters into separate words. */
+      if (split_on_spaces)
+       list = list_string (istring, " ", 1);   /* XXX quoted == 1? */
+      /* If we have $@ (has_dollar_at != 0) and we are in a context where we
+        don't want to split the result (W_NOSPLIT2), and we are not quoted,
+        we have already separated the arguments with the first character of
+        $IFS.  In this case, we want to return a list with a single word
+        with the separator possibly replaced with a space (it's what other
+        shells seem to do).
+        quoted_dollar_at is internal to this function and is set if we are
+        passed an argument that is unquoted (quoted == 0) but we encounter a
+        double-quoted $@ while expanding it. */
+      else if (has_dollar_at && quoted_dollar_at == 0 && ifs_chars && quoted == 0 && (word->flags & W_NOSPLIT2))
+       {
+         /* Only split and rejoin if we have to */
+         if (*ifs_chars && *ifs_chars != ' ')
+           {
+             list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1);
+             tstring = string_list (list);
+           }
+         else
+           tstring = istring;
+         tword = make_bare_word (tstring);
+         if (tstring != istring)
+           free (tstring);
+         goto set_word_flags;
+       }
+      /* This is the attempt to make $* in an assignment context (a=$*) and
+        array variables subscripted with * in an assignment context (a=${foo[*]})
+        behave similarly.  It has side effects that, though they increase
+        compatibility with other shells, are not backwards compatible. */
+#if 0
+      else if (has_dollar_at && quoted == 0 && ifs_chars && (word->flags & W_ASSIGNRHS))
+       {
+         tword = make_bare_word (istring);
+         goto set_word_flags;
+       }
+#endif
+      else if (has_dollar_at && ifs_chars)
        list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1);
       else
        {
          tword = make_bare_word (istring);
+set_word_flags:
          if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (quoted_state == WHOLLY_QUOTED))
            tword->flags |= W_QUOTED;
          if (word->flags & W_ASSIGNMENT)
@@ -8598,6 +9837,8 @@ finished_with_string:
            tword->flags |= W_COMPASSIGN;
          if (word->flags & W_NOGLOB)
            tword->flags |= W_NOGLOB;
+         if (word->flags & W_NOBRACE)
+           tword->flags |= W_NOBRACE;
          if (word->flags & W_NOEXPAND)
            tword->flags |= W_NOEXPAND;
          if (had_quoted_null && QUOTED_NULL (istring))
@@ -8746,6 +9987,9 @@ setifs (v)
   ifs_var = v;
   ifs_value = (v && value_cell (v)) ? value_cell (v) : " \t\n";
 
+  ifs_is_set = ifs_var != 0;
+  ifs_is_null = ifs_is_set && (*ifs_value == 0);
+
   /* Should really merge ifs_cmap with sh_syntaxtab.  XXX - doesn't yet
      handle multibyte chars in IFS */
   memset (ifs_cmap, '\0', sizeof (ifs_cmap));
@@ -9058,7 +10302,6 @@ glob_expand_word_list (tlist, eflags)
          for (glob_index = 0; glob_array[glob_index]; glob_index++)
            {
              tword = make_bare_word (glob_array[glob_index]);
-             tword->flags |= W_GLOBEXP;        /* XXX */
              glob_list = make_word_list (tword, glob_list);
            }
 
@@ -9069,6 +10312,7 @@ glob_expand_word_list (tlist, eflags)
            }
          else if (fail_glob_expansion != 0)
            {
+             last_command_exit_value = EXECUTION_FAILURE;
              report_error (_("no match: %s"), tlist->word->word);
              exp_jump_to_top_level (DISCARD);
            }
@@ -9123,13 +10367,20 @@ brace_expand_word_list (tlist, eflags)
     {
       next = tlist->next;
 
+      if (tlist->word->flags & W_NOBRACE)
+        {
+/*itrace("brace_expand_word_list: %s: W_NOBRACE", tlist->word->word);*/
+         PREPEND_LIST (tlist, output_list);
+         continue;
+        }
+
       if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG))
         {
 /*itrace("brace_expand_word_list: %s: W_COMPASSIGN|W_ASSIGNARG", tlist->word->word);*/
          PREPEND_LIST (tlist, output_list);
          continue;
         }
-          
+
       /* Only do brace expansion if the word has a brace character.  If
         not, just add the word list element to BRACES and continue.  In
         the common case, at least when running shell scripts, this will
@@ -9143,14 +10394,18 @@ brace_expand_word_list (tlist, eflags)
 
          for (eindex = 0; temp_string = expansions[eindex]; eindex++)
            {
-             w = make_word (temp_string);
+             w = alloc_word_desc ();
+             w->word = temp_string;
+
              /* If brace expansion didn't change the word, preserve
                 the flags.  We may want to preserve the flags
                 unconditionally someday -- XXX */
              if (STREQ (temp_string, tlist->word->word))
                w->flags = tlist->word->flags;
+             else
+               w = make_word_flags (w, temp_string);
+
              output_list = make_word_list (w, output_list);
-             free (expansions[eindex]);
            }
          free (expansions);
 
@@ -9176,23 +10431,32 @@ brace_expand_word_list (tlist, eflags)
 /* Take WORD, a compound associative array assignment, and internally run
    'declare -A w', where W is the variable name portion of WORD. */
 static int
-make_internal_declare (word, option)
+make_internal_declare (word, option, cmd)
      char *word;
      char *option;
+     char *cmd;
 {
-  int t;
+  int t, r;
   WORD_LIST *wl;
   WORD_DESC *w;
 
   w = make_word (word);
 
   t = assignment (w->word, 0);
-  w->word[t] = '\0';
+  if (w->word[t] == '=')
+    {
+      w->word[t] = '\0';
+      if (w->word[t - 1] == '+')       /* cut off any append op */
+       w->word[t - 1] = '\0';
+    }
 
   wl = make_word_list (w, (WORD_LIST *)NULL);
   wl = make_word_list (make_word (option), wl);
 
-  return (declare_builtin (wl));  
+  r = declare_builtin (wl);
+
+  dispose_words (wl);
+  return r;
 }  
 #endif
 
@@ -9201,12 +10465,16 @@ shell_expand_word_list (tlist, eflags)
      WORD_LIST *tlist;
      int eflags;
 {
-  WORD_LIST *expanded, *orig_list, *new_list, *next, *temp_list;
+  WORD_LIST *expanded, *orig_list, *new_list, *next, *temp_list, *wcmd;
   int expanded_something, has_dollar_at;
   char *temp_string;
 
   /* 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;
+
   for (orig_list = tlist; tlist; tlist = next)
     {
       temp_string = tlist->word->word;
@@ -9223,9 +10491,77 @@ shell_expand_word_list (tlist, eflags)
       if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG))
        {
          int t;
+         char opts[16], opti;
+
+         opti = 0;
+         if (tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL|W_ASSIGNARRAY))
+           opts[opti++] = '-';
+
+         if ((tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL)) == (W_ASSIGNASSOC|W_ASSNGLOBAL))
+           {
+             opts[opti++] = 'g';
+             opts[opti++] = 'A';
+           }
+         else if (tlist->word->flags & W_ASSIGNASSOC)
+           opts[opti++] = 'A';
+         else if ((tlist->word->flags & (W_ASSIGNARRAY|W_ASSNGLOBAL)) == (W_ASSIGNARRAY|W_ASSNGLOBAL))
+           {
+             opts[opti++] = 'g';
+             opts[opti++] = 'a';
+           }
+         else if (tlist->word->flags & W_ASSIGNARRAY)
+           opts[opti++] = 'a';
+         else if (tlist->word->flags & W_ASSNGLOBAL)
+           opts[opti++] = 'g';
+
+         /* If 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;
+           }
 
-         if (tlist->word->flags & W_ASSIGNASSOC)
-           make_internal_declare (tlist->word->word, "-A");
+         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)
@@ -9237,7 +10573,9 @@ shell_expand_word_list (tlist, eflags)
          /* Now transform the word as ksh93 appears to do and go on */
          t = assignment (tlist->word->word, 0);
          tlist->word->word[t] = '\0';
-         tlist->word->flags &= ~(W_ASSIGNMENT|W_NOSPLIT|W_COMPASSIGN|W_ASSIGNARG|W_ASSIGNASSOC);
+         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);
        }
 #endif
 
@@ -9302,7 +10640,9 @@ shell_expand_word_list (tlist, eflags)
    process substitution, word splitting, and pathname expansion, according
    to the bits set in EFLAGS.  Words with the W_QUOTED or W_NOSPLIT bits
    set, or for which no expansion is done, do not undergo word splitting.
-   Words with the W_NOGLOB bit set do not undergo pathname expansion. */
+   Words with the W_NOGLOB bit set do not undergo pathname expansion; words
+   with W_NOBRACE set do not undergo brace expansion (see
+   brace_expand_word_list above). */
 static WORD_LIST *
 expand_word_list_internal (list, eflags)
      WORD_LIST *list;
@@ -9310,7 +10650,9 @@ expand_word_list_internal (list, eflags)
 {
   WORD_LIST *new_list, *temp_list;
   int tint;
+  char *savecmd;
 
+  tempenv_assign_error = 0;
   if (list == 0)
     return ((WORD_LIST *)NULL);
 
@@ -9326,8 +10668,10 @@ expand_word_list_internal (list, eflags)
                 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. */
                  if (tint == 0)
@@ -9391,10 +10735,12 @@ expand_word_list_internal (list, eflags)
       
       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)
@@ -9416,13 +10762,5 @@ expand_word_list_internal (list, eflags)
       subst_assign_varlist = (WORD_LIST *)NULL;
     }
 
-#if 0
-  tint = list_length (new_list) + 1;
-  RESIZE_MALLOCED_BUFFER (glob_argv_flags, 0, tint, glob_argv_flags_size, 16);
-  for (tint = 0, temp_list = new_list; temp_list; temp_list = temp_list->next)
-    glob_argv_flags[tint++] = (temp_list->word->flags & W_GLOBEXP) ? '1' : '0';
-  glob_argv_flags[tint] = '\0';
-#endif
-
   return (new_list);
 }