]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - arrayfunc.c
fix nofork comsub overwriting currently executing command; free readline undo list...
[thirdparty/bash.git] / arrayfunc.c
index 959ccd711914c5110e83d4a4ac4d46e8142ca3f0..d56be4283d90286110085d4d87941344f055edb8 100644 (file)
@@ -1,6 +1,6 @@
 /* arrayfunc.c -- High-level array functions used by other parts of the shell. */
 
-/* Copyright (C) 2001-2011 Free Software Foundation, Inc.
+/* Copyright (C) 2001-2024 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
 #include "bashintl.h"
 
 #include "shell.h"
+#include "execute_cmd.h"
 #include "pathexp.h"
 
 #include "shmbutil.h"
+#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR)
+#  include <mbstr.h>           /* mbschr */
+#endif
 
 #include "builtins/common.h"
 
-extern char *this_command_name;
-extern int last_command_exit_value;
-extern int array_needs_making;
+#ifndef LBRACK
+#  define LBRACK '['
+#  define RBRACK ']'
+#endif
+
+/* This variable means to not expand associative or indexed array subscripts
+   more than once, when performing variable expansion. */
+int array_expand_once = 0;
+
+static SHELL_VAR *bind_array_var_internal (SHELL_VAR *, arrayind_t, char *, const char *, int);
+static SHELL_VAR *assign_array_element_internal (SHELL_VAR *, const char *, char *, char *, int, const char *, int, array_eltstate_t *);
 
-static SHELL_VAR *bind_array_var_internal __P((SHELL_VAR *, arrayind_t, char *, char *, int));
-static SHELL_VAR *assign_array_element_internal __P((SHELL_VAR *, char *, char *, char *, int, char *, int));
+static void assign_assoc_from_kvlist (SHELL_VAR *, WORD_LIST *, HASH_TABLE *, int);
 
-static char *quote_assign __P((const char *));
-static void quote_array_assignment_chars __P((WORD_LIST *));
-static char *array_value_internal __P((char *, int, int, int *, arrayind_t *));
+static char *quote_assign (const char *);
+static void quote_array_assignment_chars (WORD_LIST *);
+static char *quote_compound_array_word (char *, int);
+static char *array_value_internal (const char *, int, int, array_eltstate_t *);
 
 /* Standard error message to use when encountering an invalid array subscript */
 const char * const bash_badsub_errmsg = N_("bad array subscript");
@@ -59,8 +71,7 @@ const char * const bash_badsub_errmsg = N_("bad array subscript");
 /* Convert a shell variable to an array variable.  The original value is
    saved as array[0]. */
 SHELL_VAR *
-convert_var_to_array (var)
-     SHELL_VAR *var;
+convert_var_to_array (SHELL_VAR *var)
 {
   char *oldval;
   ARRAY *array;
@@ -82,7 +93,14 @@ convert_var_to_array (var)
     array_needs_making++;
 
   VSETATTR (var, att_array);
-  VUNSETATTR (var, att_invisible);
+  if (oldval)
+    VUNSETATTR (var, att_invisible);
+
+  /* Make sure it's not marked as an associative array any more */
+  VUNSETATTR (var, att_assoc);
+
+  /* Since namerefs can't be array variables, turn off nameref attribute */
+  VUNSETATTR (var, att_nameref);
 
   return var;
 }
@@ -90,8 +108,7 @@ convert_var_to_array (var)
 /* Convert a shell variable to an array variable.  The original value is
    saved as array[0]. */
 SHELL_VAR *
-convert_var_to_assoc (var)
-     SHELL_VAR *var;
+convert_var_to_assoc (SHELL_VAR *var)
 {
   char *oldval;
   HASH_TABLE *hash;
@@ -113,18 +130,40 @@ convert_var_to_assoc (var)
     array_needs_making++;
 
   VSETATTR (var, att_assoc);
-  VUNSETATTR (var, att_invisible);
+  if (oldval)
+    VUNSETATTR (var, att_invisible);
+
+  /* Make sure it's not marked as an indexed array any more */
+  VUNSETATTR (var, att_array);
+
+  /* Since namerefs can't be array variables, turn off nameref attribute */
+  VUNSETATTR (var, att_nameref);
 
   return var;
 }
 
+/* Copy the array (ARRAY *) or assoc (HASH_TABLE *) from variable V1 to V2,
+   and return V2. */
+SHELL_VAR *
+arrayvar_copyval (SHELL_VAR *v1, SHELL_VAR *v2)
+{
+  FREE (value_cell (v2));
+  VUNSETATTR (v2, (att_array | att_assoc));
+  if (array_p (v1))
+    {
+      var_setarray (v2, array_copy (array_cell (v1)));
+      VSETATTR (v2, att_array);
+    }
+  else if (assoc_p (v1))
+    {
+      var_setassoc (v2, assoc_copy (assoc_cell (v1)));
+      VSETATTR (v2, att_assoc);
+    }
+  return v2;
+}
+
 char *
-make_array_variable_value (entry, ind, key, value, flags)
-     SHELL_VAR *entry;
-     arrayind_t ind;
-     char *key;
-     char *value;
-     int flags;
+make_array_variable_value (SHELL_VAR *entry, arrayind_t ind, const char *key, const char *value, int flags)
 {
   SHELL_VAR *dentry;
   char *newval;
@@ -158,14 +197,41 @@ make_array_variable_value (entry, ind, key, value, flags)
 
   return newval;
 }
-  
+
+/* Assign HASH[KEY]=VALUE according to FLAGS. ENTRY is an associative array
+   variable; HASH is the hash table to assign into. HASH may or may not be
+   the hash table associated with ENTRY; if it's not, the caller takes care
+   of it.
+   XXX - make sure that any dynamic associative array variables recreate the
+   hash table on each assignment. BASH_CMDS and BASH_ALIASES already do this */
+static SHELL_VAR *
+bind_assoc_var_internal (SHELL_VAR *entry, HASH_TABLE *hash, char *key, const char *value, int flags)
+{
+  char *newval;
+
+  /* Use the existing array contents to expand the value */
+  newval = make_array_variable_value (entry, 0, key, value, flags);
+
+  if (entry->assign_func)
+    {
+      (*entry->assign_func) (entry, newval, 0, key);
+      FREE (key);
+    }
+  else
+    assoc_insert (hash, key, newval);
+
+  FREE (newval);
+
+  VUNSETATTR (entry, att_invisible);   /* no longer invisible */
+
+  /* check mark_modified_variables if we ever want to export array vars */
+  return (entry);
+}
+
+/* Perform ENTRY[IND]=VALUE or ENTRY[KEY]=VALUE. This is not called for every
+   assignment to an associative array; see assign_compound_array_list below. */
 static SHELL_VAR *
-bind_array_var_internal (entry, ind, key, value, flags)
-     SHELL_VAR *entry;
-     arrayind_t ind;
-     char *key;
-     char *value;
-     int flags;
+bind_array_var_internal (SHELL_VAR *entry, arrayind_t ind, char *key, const char *value, int flags)
 {
   char *newval;
 
@@ -179,6 +245,9 @@ bind_array_var_internal (entry, ind, key, value, flags)
     array_insert (array_cell (entry), ind, newval);
   FREE (newval);
 
+  VUNSETATTR (entry, att_invisible);   /* no longer invisible */
+
+  /* check mark_modified_variables if we ever want to export array vars */
   return (entry);
 }
 
@@ -190,19 +259,24 @@ bind_array_var_internal (entry, ind, key, value, flags)
    If NAME does not exist, just create an array variable, no matter what
    IND's value may be. */
 SHELL_VAR *
-bind_array_variable (name, ind, value, flags)
-     char *name;
-     arrayind_t ind;
-     char *value;
-     int flags;
+bind_array_variable (const char *name, arrayind_t ind, const char *value, int flags)
 {
   SHELL_VAR *entry;
 
   entry = find_shell_variable (name);
 
+  if (entry == (SHELL_VAR *) 0)
+    {
+      /* Is NAME a nameref variable that points to an unset variable? */
+      entry = find_variable_nameref_for_create (name, 0);
+      if (entry == INVALID_NAMEREF_VALUE)
+       return ((SHELL_VAR *)0);
+      if (entry && nameref_p (entry))
+       entry = make_new_array_variable (nameref_cell (entry));
+    }
   if (entry == (SHELL_VAR *) 0)
     entry = make_new_array_variable (name);
-  else if (readonly_p (entry) || noassign_p (entry))
+  else if ((readonly_p (entry) && (flags&ASS_FORCE) == 0) || noassign_p (entry))
     {
       if (readonly_p (entry))
        err_readonly (name);
@@ -216,83 +290,112 @@ bind_array_variable (name, ind, value, flags)
 }
 
 SHELL_VAR *
-bind_array_element (entry, ind, value, flags)
-     SHELL_VAR *entry;
-     arrayind_t ind;
-     char *value;
-     int flags;
+bind_array_element (SHELL_VAR *entry, arrayind_t ind, char *value, int flags)
 {
   return (bind_array_var_internal (entry, ind, 0, value, flags));
 }
                     
 SHELL_VAR *
-bind_assoc_variable (entry, name, key, value, flags)
-     SHELL_VAR *entry;
-     char *name;
-     char *key;
-     char *value;
-     int flags;
+bind_assoc_variable (SHELL_VAR *entry, const char *name, char *key, const char *value, int flags)
 {
-  SHELL_VAR *dentry;
-  char *newval;
-
-  if (readonly_p (entry) || noassign_p (entry))
+  if ((readonly_p (entry) && (flags&ASS_FORCE) == 0) || noassign_p (entry))
     {
       if (readonly_p (entry))
        err_readonly (name);
       return (entry);
     }
 
-  return (bind_array_var_internal (entry, 0, key, value, flags));
+  return (bind_assoc_var_internal (entry, assoc_cell (entry), key, value, flags));
+}
+
+inline void
+init_eltstate (array_eltstate_t *estatep)
+{
+  if (estatep)
+    {
+      estatep->type = ARRAY_INVALID;
+      estatep->subtype = 0;
+      estatep->key = estatep->value = 0;
+      estatep->ind = INTMAX_MIN;
+    }
+}
+
+inline void
+flush_eltstate (array_eltstate_t *estatep)
+{
+  if (estatep)
+    FREE (estatep->key);
 }
 
 /* Parse NAME, a lhs of an assignment statement of the form v[s], and
-   assign VALUE to that array element by calling bind_array_variable(). */
+   assign VALUE to that array element by calling bind_array_variable().
+   Flags are ASS_ assignment flags */
 SHELL_VAR *
-assign_array_element (name, value, flags)
-     char *name, *value;
-     int flags;
+assign_array_element (const char *name, const char *value, int flags, array_eltstate_t *estatep)
 {
   char *sub, *vname;
-  int sublen;
+  int sublen, isassoc, avflags;
   SHELL_VAR *entry;
 
-  vname = array_variable_name (name, &sub, &sublen);
+  avflags = convert_assign_flags_to_arrayval_flags (flags);
+  vname = array_variable_name (name, avflags, &sub, &sublen);
 
   if (vname == 0)
     return ((SHELL_VAR *)NULL);
 
-  if ((ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']') || (sublen <= 1))
+  entry = find_variable (vname);
+  isassoc = entry && assoc_p (entry);
+
+  /* We don't allow assignment to `*' or `@' associative array keys if the
+     caller hasn't told us the subscript has already been expanded
+     (ASS_NOEXPAND). If the caller has explicitly told us it's ok
+     (ASS_ALLOWALLSUB) we allow it. */
+  if (((isassoc == 0 || (flags & (ASS_NOEXPAND|ASS_ALLOWALLSUB)) == 0) &&
+       (ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']')) ||
+      (sublen <= 1) ||
+      (sub[sublen] != '\0'))           /* sanity check */
     {
       free (vname);
       err_badarraysub (name);
       return ((SHELL_VAR *)NULL);
     }
 
-  entry = find_variable (vname);
-  entry = assign_array_element_internal (entry, name, vname, sub, sublen, value, flags);
+  entry = assign_array_element_internal (entry, name, vname, sub, sublen, value, flags, estatep);
+
+#if ARRAY_EXPORT
+  if (entry && exported_p (entry))
+    {
+      INVALIDATE_EXPORTSTR (entry);
+      array_needs_making = 1;
+    }
+#endif
 
   free (vname);
   return entry;
 }
 
+/* Assign VALUE to the index computed from SUB of length SUBLEN of array
+   VNAME. NAME is the complete variable reference. FLAGS are ASS_ assignment
+   flags. ENTRY is the SHELL_VAR corresponding to VNAME. The caller
+   initializes ESTATEP, and we set it to the values we compute. */
 static SHELL_VAR *
-assign_array_element_internal (entry, name, vname, sub, sublen, value, flags)
-     SHELL_VAR *entry;
-     char *name;               /* only used for error messages */
-     char *vname;
-     char *sub;
-     int sublen;
-     char *value;
-     int flags;
+assign_array_element_internal (SHELL_VAR *entry, const char *name, char *vname,
+                              char *sub, int sublen, const char *value,
+                              int flags, array_eltstate_t *estatep)
 {
-  char *akey;
+  char *akey, *nkey;
   arrayind_t ind;
+  char *newval;
+
+  /* rely on the caller to initialize estatep */
 
   if (entry && assoc_p (entry))
     {
       sub[sublen-1] = '\0';
-      akey = expand_assignment_string_to_string (sub, 0);      /* [ */
+      if ((flags & ASS_NOEXPAND) == 0)
+       akey = expand_subscript_string (sub, 0);        /* [ */
+      else
+       akey = savestring (sub);
       sub[sublen-1] = ']';
       if (akey == 0 || *akey == 0)
        {
@@ -300,20 +403,38 @@ assign_array_element_internal (entry, name, vname, sub, sublen, value, flags)
          FREE (akey);
          return ((SHELL_VAR *)NULL);
        }
+      if (estatep)
+       nkey = savestring (akey);       /* assoc_insert/assoc_replace frees akey */
       entry = bind_assoc_variable (entry, vname, akey, value, flags);
+      if (estatep)
+       {
+         estatep->type = ARRAY_ASSOC;
+         estatep->key = nkey;
+         estatep->value = entry ? assoc_reference (assoc_cell (entry), nkey) : 0;
+       }
     }
   else
     {
-      ind = array_expand_index (entry, sub, sublen);
+      /* convert ASS_ flags to AV_FLAGS here */
+      int avflags;
+
+      avflags = convert_assign_flags_to_arrayval_flags (flags);
+      ind = array_expand_index (entry, sub, sublen, avflags);
       /* negative subscripts to indexed arrays count back from end */
-      if (ind < 0)
-       ind = array_max_index (array_cell (entry)) + 1 + ind;
+      if (entry && ind < 0)
+       ind = (array_p (entry) ? array_max_index (array_cell (entry)) : 0) + 1 + ind;
       if (ind < 0)
        {
          err_badarraysub (name);
          return ((SHELL_VAR *)NULL);
        }
       entry = bind_array_variable (vname, ind, value, flags);
+      if (estatep)
+       {
+         estatep->type = ARRAY_INDEXED;
+         estatep->ind = ind;
+         estatep->value = entry ? array_reference (array_cell (entry), ind) : 0;
+       }
     }
 
   return (entry);
@@ -324,11 +445,10 @@ assign_array_element_internal (entry, name, vname, sub, sublen, value, flags)
    convert it to an indexed array.  If FLAGS&1 is non-zero, an existing
    variable is checked for the readonly or noassign attribute in preparation
    for assignment (e.g., by the `read' builtin).  If FLAGS&2 is non-zero, we
-   create an associative array. */
+   create an associative array. If FLAGS&4 is non-zero, we return noassign
+   variables instead of NULL because the caller wants to handle them. */
 SHELL_VAR *
-find_or_make_array_variable (name, flags)
-     char *name;
-     int flags;
+find_or_make_array_variable (const char *name, int flags)
 {
   SHELL_VAR *var;
 
@@ -337,9 +457,21 @@ find_or_make_array_variable (name, flags)
     {
       /* See if we have a nameref pointing to a variable that hasn't been
         created yet. */
-      var = find_variable_last_nameref (name);
+      var = find_variable_last_nameref (name, 1);
+      if (var && nameref_p (var) && invisible_p (var))
+       {
+         internal_warning (_("%s: removing nameref attribute"), name);
+         VUNSETATTR (var, att_nameref);
+       }
       if (var && nameref_p (var))
-       var = (flags & 2) ? make_new_assoc_variable (nameref_cell (var)) : make_new_array_variable (nameref_cell (var));
+       {
+         if (valid_nameref_value (nameref_cell (var), 2) == 0)
+           {
+             sh_invalidid (nameref_cell (var));
+             return ((SHELL_VAR *)NULL);
+           }
+         var = (flags & 2) ? make_new_assoc_variable (nameref_cell (var)) : make_new_array_variable (nameref_cell (var));
+       }
     }
 
   if (var == 0)
@@ -348,14 +480,18 @@ find_or_make_array_variable (name, flags)
     {
       if (readonly_p (var))
        err_readonly (name);
+      if ((flags & 4) && noassign_p (var))
+       return (var);
       return ((SHELL_VAR *)NULL);
     }
   else if ((flags & 2) && array_p (var))
     {
-      last_command_exit_value = 1;
+      set_exit_status (EXECUTION_FAILURE);
       report_error (_("%s: cannot convert indexed to associative array"), name);
       return ((SHELL_VAR *)NULL);
     }
+  else if (flags & 2)
+    var = assoc_p (var) ? var : convert_var_to_assoc (var);
   else if (array_p (var) == 0 && assoc_p (var) == 0)
     var = convert_var_to_array (var);
 
@@ -365,9 +501,7 @@ find_or_make_array_variable (name, flags)
 /* Perform a compound assignment statement for array NAME, where VALUE is
    the text between the parens:  NAME=( VALUE ) */
 SHELL_VAR *
-assign_array_from_string (name, value, flags)
-     char *name, *value;
-     int flags;
+assign_array_from_string (const char *name, char *value, int flags)
 {
   SHELL_VAR *var;
   int vflags;
@@ -375,10 +509,11 @@ assign_array_from_string (name, value, flags)
   vflags = 1;
   if (flags & ASS_MKASSOC)
     vflags |= 2;
+  vflags |= 4;         /* we want to handle noassign variables ourselves */
 
   var = find_or_make_array_variable (name, vflags);
-  if (var == 0)
-    return ((SHELL_VAR *)NULL);
+  if (var == 0 || noassign_p (var))
+    return (var);
 
   return (assign_array_var_from_string (var, value, flags));
 }
@@ -386,10 +521,7 @@ assign_array_from_string (name, value, flags)
 /* Sequentially assign the indices of indexed array variable VAR from the
    words in LIST. */
 SHELL_VAR *
-assign_array_var_from_word_list (var, list, flags)
-     SHELL_VAR *var;
-     WORD_LIST *list;
-     int flags;
+assign_array_var_from_word_list (SHELL_VAR *var, WORD_LIST *list, int flags)
 {
   register arrayind_t i;
   register WORD_LIST *l;
@@ -399,23 +531,31 @@ assign_array_var_from_word_list (var, list, flags)
   i = (flags & ASS_APPEND) ? array_max_index (a) + 1 : 0;
 
   for (l = list; l; l = l->next, i++)
-    if (var->assign_func)
-      (*var->assign_func) (var, l->word->word, i, 0);
-    else
-      array_insert (a, i, l->word->word);
+    {
+      if (a && i < 0)  /* overflow */
+       {
+         char *num;
+
+         num = itos (i);
+         report_error ("%s[%s]: %s", var->name, num, bash_badsub_errmsg);
+         free (num);
+         return (var);         /* XXX */
+       }
+
+      bind_array_var_internal (var, i, 0, l->word->word, flags & ~ASS_APPEND);
+    }
+
+  VUNSETATTR (var, att_invisible);     /* no longer invisible */
+
   return var;
 }
 
 WORD_LIST *
-expand_compound_array_assignment (var, value, flags)
-     SHELL_VAR *var;
-     char *value;
-     int flags;
+expand_compound_array_assignment (SHELL_VAR *var, char *value, int flags)
 {
   WORD_LIST *list, *nlist;
-  WORD_LIST *hd, *tl, *t, *n;
   char *val;
-  int ni;
+  size_t ni;
 
   /* This condition is true when invoked from the declare builtin with a
      command like
@@ -434,8 +574,20 @@ expand_compound_array_assignment (var, value, flags)
      shell expansions including pathname generation and word splitting. */
   /* First we split the string on whitespace, using the shell parser
      (ksh93 seems to do this). */
+  /* XXX - this needs a rethink, maybe use split_at_delims */
   list = parse_string_to_word_list (val, 1, "array assign");
 
+  /* If the parser has quoted CTLESC and CTNLNUL with CTLESC in unquoted
+     words, we need to remove those here because the code below assumes
+     they are there because they exist in the original word. */
+  /* XXX - if we rethink parse_string_to_word_list above, change this. */
+  for (nlist = list; nlist; nlist = nlist->next)
+    if ((nlist->word->flags & W_QUOTED) == 0)
+      remove_quoted_escapes (nlist->word->word);
+
+  /* Note that we defer expansion of the assignment statements for associative
+     arrays here, so we don't have to scan the subscript and find the ending
+     bracket twice. See the caller below. */
   if (var && assoc_p (var))
     {
       if (val != value)
@@ -462,27 +614,104 @@ expand_compound_array_assignment (var, value, flags)
   return nlist;
 }
 
-/* Callers ensure that VAR is not NULL */
-void
-assign_compound_array_list (var, nlist, flags)
-     SHELL_VAR *var;
-     WORD_LIST *nlist;
-     int flags;
+#if ASSOC_KVPAIR_ASSIGNMENT
+/* If non-zero, we split the words in kv-pair compound array assignments in
+   addition to performing the other expansions. */
+int split_kvpair_assignments = 0;
+
+/* We have a set of key-value pairs that should be expanded and split
+   (because they are not assignment statements). They are not expanded
+   and split in expand_compound_array_assignment because assoc_p (var)
+   is true. We defer the expansion until now. */
+static void
+assign_assoc_from_kvlist (SHELL_VAR *var, WORD_LIST *nlist, HASH_TABLE *h, int flags)
+{
+  WORD_LIST *list, *explist;
+  char *akey, *aval, *k, *v;
+
+  explist = split_kvpair_assignments ? expand_words_no_vars (nlist) : nlist;
+  for (list = explist; list; list = list->next)
+    {
+      k = list->word->word;
+      v = list->next ? list->next->word->word : 0;
+
+      if (list->next)
+        list = list->next;
+
+      akey = split_kvpair_assignments ? savestring (k) : expand_subscript_string (k, 0);
+      if (akey == 0 || *akey == 0)
+       {
+         err_badarraysub (k);
+         FREE (akey);
+         continue;
+       }             
+
+      aval = split_kvpair_assignments ? savestring (v) : expand_assignment_string_to_string (v, 0);
+      if (aval == 0)
+       {
+         aval = (char *)xmalloc (1);
+         aval[0] = '\0';       /* like do_assignment_internal */
+       }
+
+      bind_assoc_var_internal (var, h, akey, aval, flags);
+
+      free (aval);
+    }
+
+  if (explist != nlist)
+    dispose_words (explist);
+}
+
+/* Return non-zero if L appears to be a key-value pair associative array
+   compound assignment. */ 
+int
+kvpair_assignment_p (WORD_LIST *l)
+{
+  return (l && (l->word->flags & W_ASSIGNMENT) == 0 && l->word->word[0] != '[');       /*]*/
+}
+
+char *
+expand_and_quote_kvpair_word (const char *w)
+{
+  char *r, *s, *t;
+
+  t = w ? expand_subscript_string (w, 0) : 0;
+  s = (t && strchr (t, CTLESC)) ? quote_escapes (t) : t;
+  r = sh_single_quote (s ? s : "");
+  if (s != t)
+    free (s);
+  free (t);
+  return r;
+}
+#endif
+     
+/* Callers ensure that VAR is not NULL. Associative array assignments have not
+   been expanded when this is called, or have been expanded once and single-
+   quoted, so we don't have to scan through an unquoted expanded subscript to
+   find the ending bracket; indexed array assignments have been expanded and
+   possibly single-quoted to prevent further expansion.
+
+   If this is an associative array, we perform the assignments into NHASH and
+   set NHASH to be the value of VAR after processing the assignments in NLIST */
+int
+assign_compound_array_list (SHELL_VAR *var, WORD_LIST *nlist, int flags)
 {
   ARRAY *a;
-  HASH_TABLE *h;
+  HASH_TABLE *h, *nhash;
   WORD_LIST *list;
-  char *w, *val, *nval;
-  int len, iflags, free_val;
+  char *w, *val, *nval, *savecmd;
+  int len, iflags, free_val, any_failed;
   arrayind_t ind, last_ind;
   char *akey;
 
   a = (var && array_p (var)) ? array_cell (var) : (ARRAY *)0;
-  h = (var && assoc_p (var)) ? assoc_cell (var) : (HASH_TABLE *)0;
+  nhash = h = (var && assoc_p (var)) ? assoc_cell (var) : (HASH_TABLE *)0;
 
   akey = (char *)0;
   ind = 0;
 
+  any_failed = 0;
+
   /* Now that we are ready to assign values to the array, kill the existing
      value. */
   if ((flags & ASS_APPEND) == 0)
@@ -490,14 +719,31 @@ assign_compound_array_list (var, nlist, flags)
       if (a && array_p (var))
        array_flush (a);
       else if (h && assoc_p (var))
-       assoc_flush (h);
+       nhash = assoc_create (h->nbuckets);
     }
 
+#if ASSOC_KVPAIR_ASSIGNMENT
+  if (assoc_p (var) && kvpair_assignment_p (nlist))
+    {
+      iflags = flags & ~ASS_APPEND;
+      assign_assoc_from_kvlist (var, nlist, nhash, iflags);
+      if (nhash && nhash != h)
+       {
+         h = assoc_cell (var);
+         var_setassoc (var, nhash);
+         assoc_dispose (h);
+       }
+      return 1;                /* XXX - check return value */
+    }
+#endif
+
   last_ind = (a && (flags & ASS_APPEND)) ? array_max_index (a) + 1 : 0;
 
   for (list = nlist; list; list = list->next)
     {
-      iflags = flags;
+      /* Don't allow var+=(values) to make assignments in VALUES append to
+        existing values by default. */
+      iflags = flags & ~ASS_APPEND;
       w = list->word->word;
 
       /* We have a word of the form [ind]=value */
@@ -511,10 +757,11 @@ assign_compound_array_list (var, nlist, flags)
          /* XXX - changes for `+=' */
          if (w[len] != ']' || (w[len+1] != '=' && (w[len+1] != '+' || w[len+2] != '=')))
            {
-             if (assoc_p (var))
+             if (assoc_p (var) || last_ind < 0)
                {
                  err_badarraysub (w);
-                 continue;
+                 any_failed++;
+                 break;
                }
              nval = make_variable_value (var, w, flags);
              if (var->assign_func)
@@ -529,29 +776,33 @@ assign_compound_array_list (var, nlist, flags)
          if (len == 1)
            {
              err_badarraysub (w);
-             continue;
+             any_failed++;
+             break;
            }
 
-         if (ALL_ELEMENT_SUB (w[1]) && len == 2)
+         if (ALL_ELEMENT_SUB (w[1]) && len == 2 && array_p (var))
            {
-             last_command_exit_value = 1;
-             if (assoc_p (var))
-               report_error (_("%s: invalid associative array key"), w);
-             else
-               report_error (_("%s: cannot assign to non-numeric index"), w);
-             continue;
+             set_exit_status (EXECUTION_FAILURE);
+             report_error (_("%s: cannot assign to non-numeric index"), w);
+             any_failed++;
+             break;
            }
 
          if (array_p (var))
            {
-             ind = array_expand_index (var, w + 1, len);
+             int avflags;
+
+             /* convert ASS_ FLAGS to AV_ flags here */
+             avflags = convert_assign_flags_to_arrayval_flags (flags);
+             ind = array_expand_index (var, w + 1, len, avflags);
              /* negative subscripts to indexed arrays count back from end */
              if (ind < 0)
                ind = array_max_index (array_cell (var)) + 1 + ind;
              if (ind < 0)
                {
                  err_badarraysub (w);
-                 continue;
+                 any_failed++;
+                 break;
                }
 
              last_ind = ind;
@@ -560,14 +811,15 @@ assign_compound_array_list (var, nlist, flags)
            {
              /* This is not performed above, see expand_compound_array_assignment */
              w[len] = '\0';    /*[*/
-             akey = expand_assignment_string_to_string (w+1, 0);
+             akey = expand_subscript_string (w+1, 0);
              w[len] = ']';
              /* And we need to expand the value also, see below */
              if (akey == 0 || *akey == 0)
                {
                  err_badarraysub (w);
                  FREE (akey);
-                 continue;
+                 any_failed++;
+                 break;
                }
            }
 
@@ -582,9 +834,10 @@ assign_compound_array_list (var, nlist, flags)
        }
       else if (assoc_p (var))
        {
-         last_command_exit_value = 1;
+         set_exit_status (EXECUTION_FAILURE);
          report_error (_("%s: %s: must use subscript when assigning associative array"), var->name, w);
-         continue;
+         any_failed++;
+         break;
        }
       else             /* No [ind]=value, just a stray `=' */
        {
@@ -592,51 +845,94 @@ assign_compound_array_list (var, nlist, flags)
          val = w;
        }
 
+      if (array_p (var) && ind < 0)    /* overflow */
+       {
+         char *num;
+
+         num = itos (ind);
+         report_error ("%s[%s]: %s", var->name, num, bash_badsub_errmsg);
+         free (num);
+         return 0;
+       }
+
       free_val = 0;
       /* See above; we need to expand the value here */
       if (assoc_p (var))
        {
          val = expand_assignment_string_to_string (val, 0);
+         if (val == 0)
+           {
+             val = (char *)xmalloc (1);
+             val[0] = '\0';    /* like do_assignment_internal */
+           }
          free_val = 1;
        }
 
+      savecmd = this_command_name;
       if (integer_p (var))
-       this_command_name = (char *)NULL;       /* no command name for errors */
-      bind_array_var_internal (var, ind, akey, val, iflags);
+       this_command_name = 0;  /* no command name for errors */
+      if (assoc_p (var))
+       bind_assoc_var_internal (var, nhash, akey, val, iflags);
+      else
+       bind_array_var_internal (var, ind, akey, val, iflags);
       last_ind++;
+      this_command_name = savecmd;
 
       if (free_val)
        free (val);
     }
+
+  if (assoc_p (var) && nhash && nhash != h)
+    {
+      h = assoc_cell (var);
+      var_setassoc (var, nhash);
+      assoc_dispose (h);
+    }
+
+#if ARRAY_EXPORT
+  if (var && exported_p (var))
+    {
+      INVALIDATE_EXPORTSTR (var);
+      array_needs_making = 1;
+    }
+#endif
+
+  return (any_failed ? 0 : 1);
 }
 
 /* Perform a compound array assignment:  VAR->name=( VALUE ).  The
-   VALUE has already had the parentheses stripped. */
+   VALUE has already had the parentheses stripped. FLAGS are ASS_
+   assignment flags. */
 SHELL_VAR *
-assign_array_var_from_string (var, value, flags)
-     SHELL_VAR *var;
-     char *value;
-     int flags;
+assign_array_var_from_string (SHELL_VAR *var, char *value, int flags)
 {
   WORD_LIST *nlist;
+  int aflags, r;
 
   if (value == 0)
     return var;
 
   nlist = expand_compound_array_assignment (var, value, flags);
-  assign_compound_array_list (var, nlist, flags);
+  /* This is were we set ASS_NOEXPAND and ASS_ONEWORD if we need to, since
+     expand_compound_array_assignment performs word expansions. Honors
+     array_expand_once; allows @ and * as associative array keys. */
+  aflags = flags | (array_expand_once ? ASS_NOEXPAND : 0) | ASS_ALLOWALLSUB;
+  r = assign_compound_array_list (var, nlist, aflags);
 
   if (nlist)
     dispose_words (nlist);
-  return (var);
+
+  if (var)
+    VUNSETATTR (var, att_invisible);   /* no longer invisible */
+
+  return (r == 0 ? (SHELL_VAR *)0 : var);
 }
 
 /* Quote globbing chars and characters in $IFS before the `=' in an assignment
    statement (usually a compound array assignment) to protect them from
    unwanted filename expansion or word splitting. */
 static char *
-quote_assign (string)
-     const char *string;
+quote_assign (const char *string)
 {
   size_t slen;
   int saw_eq;
@@ -677,11 +973,136 @@ quote_assign (string)
   return temp;
 }
 
+/* Take a word W of the form [IND]=VALUE and transform it to ['IND']='VALUE'
+   to prevent further expansion. This is called for compound assignments to
+   indexed arrays. W has already undergone word expansions. If W has no [IND]=,
+   just single-quote and return it. */
+static char *
+quote_compound_array_word (char *w, int type)
+{
+  char *nword, *sub, *value, *t;
+  int ind, wlen, i;
+
+  if (w[0] != LBRACK)
+    return (sh_single_quote (w));      /* XXX - quote CTLESC */
+  ind = skipsubscript (w, 0, 0);
+  if (w[ind] != RBRACK)
+    return (sh_single_quote (w));      /* XXX - quote CTLESC */
+
+  wlen = strlen (w);
+  w[ind] = '\0';
+  t = (strchr (w+1, CTLESC)) ? quote_escapes (w+1) : w+1;
+  sub = sh_single_quote (t);
+  if (t != w+1)
+   free (t);
+  w[ind] = RBRACK;
+
+  nword = xmalloc (wlen * 4 + 5);      /* wlen*4 is max single quoted length */
+  nword[0] = LBRACK;
+  i = STRLEN (sub);
+  memcpy (nword+1, sub, i);
+  free (sub);
+  i++;                         /* accommodate the opening LBRACK */
+  nword[i++] = w[ind++];       /* RBRACK */
+  if (w[ind] == '+')
+    nword[i++] = w[ind++];
+  nword[i++] = w[ind++];
+  t = (strchr (w+ind, CTLESC)) ? quote_escapes (w+ind) : w+ind;
+  value = sh_single_quote (t);
+  if (t != w+ind)
+   free (t);
+  strcpy (nword + i, value);
+  free (value);
+
+  return nword;
+}
+
+/* Expand the key and value in W, which is of the form [KEY]=VALUE, and
+   reconstruct W with the expanded and single-quoted version:
+   ['expanded-key']='expanded-value'. If there is no [KEY]=, single-quote the
+   word and return it. Very similar to previous function, but does not assume
+   W has already been expanded, and expands the KEY and VALUE separately.
+   Used for compound assignments to associative arrays that are arguments to
+   declaration builtins (declare -A a=( list )). */
+char *
+expand_and_quote_assoc_word (char *w, int type)
+{
+  char *nword, *key, *value, *s, *t;
+  int ind, wlen, i;
+
+  if (w[0] != LBRACK)
+    return (sh_single_quote (w));      /* XXX - quote_escapes */
+  ind = skipsubscript (w, 0, 0);
+  if (w[ind] != RBRACK)
+    return (sh_single_quote (w));      /* XXX - quote_escapes */
+
+  w[ind] = '\0';
+  t = expand_subscript_string (w+1, 0);
+  s = (t && strchr (t, CTLESC)) ? quote_escapes (t) : t;
+  key = sh_single_quote (s ? s : "");
+  if (s != t)
+    free (s);
+  w[ind] = RBRACK;
+  free (t);
+
+  wlen = STRLEN (key);
+  nword = xmalloc (wlen + 5);
+  nword[0] = LBRACK;
+  memcpy (nword+1, key, wlen);
+  i = wlen + 1;                        /* accommodate the opening LBRACK */
+
+  nword[i++] = w[ind++];       /* RBRACK */
+  if (w[ind] == '+')
+    nword[i++] = w[ind++];
+  nword[i++] = w[ind++];
+
+  t = expand_assignment_string_to_string (w+ind, 0);
+  s = (t && strchr (t, CTLESC)) ? quote_escapes (t) : t;
+  value = sh_single_quote (s ? s : "");
+  if (s != t)
+    free (s);
+  free (t);
+  nword = xrealloc (nword, wlen + 5 + STRLEN (value));
+  strcpy (nword + i, value);
+
+  free (key);
+  free (value);
+
+  return nword;
+}
+
+/* For each word in a compound array assignment, if the word looks like
+   [ind]=value, single-quote ind and value, but leave the brackets and
+   the = sign (and any `+') alone. If it's not an assignment, just single-
+   quote the word. This is used for indexed arrays. */
+void
+quote_compound_array_list (WORD_LIST *list, int type)
+{
+  char *s, *t;
+  WORD_LIST *l;
+
+  for (l = list; l; l = l->next)
+    {
+      if (l->word == 0 || l->word->word == 0)
+       continue;       /* should not happen, but just in case... */
+      if ((l->word->flags & W_ASSIGNMENT) == 0)
+       {
+         s = (strchr (l->word->word, CTLESC)) ? quote_escapes (l->word->word) : l->word->word;
+         t = sh_single_quote (s);
+         if (s != l->word->word)
+           free (s);
+       }
+      else 
+       t = quote_compound_array_word (l->word->word, type);
+      free (l->word->word);
+      l->word->word = t;
+    }
+}
+
 /* For each word in a compound array assignment, if the word looks like
    [ind]=value, quote globbing chars and characters in $IFS before the `='. */
 static void
-quote_array_assignment_chars (list)
-     WORD_LIST *list;
+quote_array_assignment_chars (WORD_LIST *list)
 {
   char *nword;
   WORD_LIST *l;
@@ -709,33 +1130,40 @@ quote_array_assignment_chars (list)
 /* This function is called with SUB pointing to just after the beginning
    `[' of an array subscript and removes the array element to which SUB
    expands from array VAR.  A subscript of `*' or `@' unsets the array. */
+/* If FLAGS&1 (VA_NOEXPAND) we don't expand the subscript; we just use it
+   as-is. If FLAGS&VA_ONEWORD, we don't try to use skipsubscript to parse
+   the subscript, we just assume the subscript ends with a close bracket,
+   if one is present, and use what's inside the brackets. */
 int
-unbind_array_element (var, sub)
-     SHELL_VAR *var;
-     char *sub;
+unbind_array_element (SHELL_VAR *var, char *sub, int flags)
 {
-  int len;
   arrayind_t ind;
   char *akey;
   ARRAY_ELEMENT *ae;
+  int avflags;
 
-  len = skipsubscript (sub, 0, 0);
-  if (sub[len] != ']' || len == 0)
-    {
-      builtin_error ("%s[%s: %s", var->name, sub, _(bash_badsub_errmsg));
-      return -1;
-    }
-  sub[len] = '\0';
+  /* Assume that the caller (unset_builtin) passes us a null-terminated SUB,
+     so we don't have to use VA_ONEWORD or parse the subscript again with
+     skipsubscript(). */
 
   if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0)
     {
-      unbind_variable (var->name);
-      return (0);
+      if (array_p (var) || assoc_p (var))
+       {
+         if (flags & VA_ALLOWALL)
+           {
+             unbind_variable (var->name);      /* XXX -- {array,assoc}_flush ? */
+             return (0);
+           }
+         /* otherwise we fall through and try to unset element `@' or `*' */
+       }
+      else
+       return -2;      /* don't allow this to unset scalar variables */
     }
 
   if (assoc_p (var))
     {
-      akey = expand_assignment_string_to_string (sub, 0);     /* [ */
+      akey = (flags & VA_NOEXPAND) ? sub : expand_subscript_string (sub, 0);
       if (akey == 0 || *akey == 0)
        {
          builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg));
@@ -743,11 +1171,35 @@ unbind_array_element (var, sub)
          return -1;
        }
       assoc_remove (assoc_cell (var), akey);
-      free (akey);
+      if (akey != sub)
+       free (akey);
     }
-  else
+  else if (array_p (var))
     {
-      ind = array_expand_index (var, sub, len+1);
+      if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0)
+       {
+         /* We can go several ways here:
+               1) remove the array (backwards compatible)
+               2) empty the array (new behavior)
+               3) do nothing; treat the `@' or `*' as an expression and throw
+                  an error
+         */
+         /* Behavior 1 */
+         if (shell_compatibility_level <= 51)
+           {
+             unbind_variable (name_cell (var));
+             return 0;
+           }
+         else /* Behavior 2 */
+           {
+             array_flush (array_cell (var));
+             return 0;
+           }
+         /* Fall through for behavior 3 */
+       }
+
+      avflags = convert_validarray_flags_to_arrayval_flags (flags);
+      ind = array_expand_index (var, sub, strlen (sub) + 1, avflags);
       /* negative subscripts to indexed arrays count back from end */
       if (ind < 0)
        ind = array_max_index (array_cell (var)) + 1 + ind;
@@ -760,6 +1212,20 @@ unbind_array_element (var, sub)
       if (ae)
        array_dispose_element (ae);
     }
+  else /* array_p (var) == 0 && assoc_p (var) == 0 */
+    {
+      akey = this_command_name;
+      avflags = convert_validarray_flags_to_arrayval_flags (flags);
+      ind = array_expand_index (var, sub, strlen (sub) + 1, avflags);
+      this_command_name = akey;
+      if (ind == 0)
+       {
+         unbind_variable (var->name);
+         return (0);
+       }
+      else
+       return -2;      /* any subscript other than 0 is invalid with scalar variables */
+    }
 
   return 0;
 }
@@ -767,9 +1233,7 @@ unbind_array_element (var, sub)
 /* Format and output an array assignment in compound form VAR=(VALUES),
    suitable for re-use as input. */
 void
-print_array_assignment (var, quoted)
-     SHELL_VAR *var;
-     int quoted;
+print_array_assignment (SHELL_VAR *var, int quoted)
 {
   char *vstr;
 
@@ -787,9 +1251,7 @@ print_array_assignment (var, quoted)
 /* Format and output an associative array assignment in compound form
    VAR=(VALUES), suitable for re-use as input. */
 void
-print_assoc_assignment (var, quoted)
-     SHELL_VAR *var;
-     int quoted;
+print_assoc_assignment (SHELL_VAR *var, int quoted)
 {
   char *vstr;
 
@@ -811,56 +1273,118 @@ print_assoc_assignment (var, quoted)
 /***********************************************************************/
 
 /* Return 1 if NAME is a properly-formed array reference v[sub]. */
+
+/* When NAME is a properly-formed array reference and a non-null argument SUBP
+   is supplied, '[' and ']' that enclose the subscript are replaced by '\0',
+   and the pointer to the subscript in NAME is assigned to *SUBP, so that NAME
+   and SUBP can be later used as the array name and the subscript,
+   respectively.  When SUBP is the null pointer, the original string NAME will
+   not be modified. */
+/* We need to reserve 1 for FLAGS, which we pass to skipsubscript. */
 int
-valid_array_reference (name)
-     char *name;
+tokenize_array_reference (const char *name, int flags, char **subp)
 {
   char *t;
-  int r, len;
+  int r, len, isassoc, ssflags;
+  SHELL_VAR *entry;
 
   t = mbschr (name, '[');      /* ] */
+  isassoc = 0;
   if (t)
     {
       *t = '\0';
-      r = legal_identifier (name);
+      r = valid_identifier (name);
+      if (flags & VA_NOEXPAND) /* Don't waste a lookup if we don't need one */
+       isassoc = (entry = find_variable (name)) && assoc_p (entry);      
       *t = '[';
       if (r == 0)
        return 0;
-      /* Check for a properly-terminated non-blank subscript. */
-      len = skipsubscript (t, 0, 0);
-      if (t[len] != ']' || len == 1)
+
+      ssflags = 0;
+      if (isassoc && ((flags & (VA_NOEXPAND|VA_ONEWORD)) == (VA_NOEXPAND|VA_ONEWORD)))
+       len = strlen (t) - 1;
+      else if (isassoc)
+       {
+         if (flags & VA_NOEXPAND)
+           ssflags |= 1;
+         len = skipsubscript (t, 0, ssflags);
+       }
+      else
+       /* Check for a properly-terminated non-null subscript. */
+       len = skipsubscript (t, 0, 0);          /* arithmetic expression */
+
+      if (t[len] != ']' || len == 1 || t[len+1] != '\0')
        return 0;
+
+#if 0
+      /* Could check and allow subscripts consisting only of whitespace for
+        existing associative arrays, using isassoc */
       for (r = 1; r < len; r++)
        if (whitespace (t[r]) == 0)
-         return 1;
-      return 0;
+         break;
+      if (r == len)
+       return 0; /* Fail if the subscript contains only whitespaces. */
+#endif
+
+      if (subp)
+       {
+         t[0] = t[len] = '\0';
+         *subp = t + 1;
+       }
+
+      /* This allows blank subscripts */
+      return 1;
     }
   return 0;
 }
 
-/* Expand the array index beginning at S and extending LEN characters. */
+/* Return 1 if NAME is a properly-formed array reference v[sub]. */
+
+/* We need to reserve 1 for FLAGS, which we pass to skipsubscript. */
+int
+valid_array_reference (const char *name, int flags)
+{
+  return tokenize_array_reference (name, flags, (char **)NULL);
+}
+
+/* Expand the array index beginning at S and extending LEN characters. FLAGS
+   are AV_ flags saying how to compute the array value. */
 arrayind_t
-array_expand_index (var, s, len)
-     SHELL_VAR *var;
-     char *s;
-     int len;
+array_expand_index (SHELL_VAR *var, const char *s, int len, int flags)
 {
-  char *exp, *t;
-  int expok;
+  char *exp, *t, *savecmd;
+  int expok, eflag;
   arrayind_t val;
 
   exp = (char *)xmalloc (len);
   strncpy (exp, s, len - 1);
   exp[len - 1] = '\0';
-  t = expand_arith_string (exp, 0);
+#if 1  /* TAG: bash-5.3 */
+#if 0
+  if (shell_compatibility_level <= 52 || (flags & AV_NOEXPAND) == 0)
+#else
+  if ((flags & AV_NOEXPAND) == 0)
+#endif
+    t = expand_arith_string (exp, Q_DOUBLE_QUOTES|Q_ARITH|Q_ARRAYSUB); /* XXX - Q_ARRAYSUB for future use */
+  else
+    t = exp;
+#else
+  t = expand_arith_string (exp, Q_DOUBLE_QUOTES|Q_ARITH|Q_ARRAYSUB);   /* XXX - Q_ARRAYSUB for future use */
+#endif
+  savecmd = this_command_name;
   this_command_name = (char *)NULL;
-  val = evalexp (t, &expok);
-  free (t);
+  eflag = (shell_compatibility_level > 51) ? 0 : EXP_EXPANDED;
+  val = evalexp (t, eflag, &expok);    /* XXX - was 0 but we expanded exp already */
+  this_command_name = savecmd;
+  if (t != exp)
+    free (t);
   free (exp);
   if (expok == 0)
     {
-      last_command_exit_value = EXECUTION_FAILURE;
+      set_exit_status (EXECUTION_FAILURE);
 
+      if (no_longjmp_on_fatal_error)
+       return 0;
       top_level_cleanup ();      
       jump_to_top_level (DISCARD);
     }
@@ -872,12 +1396,10 @@ array_expand_index (var, s, len)
    in *SUBP. If LENP is non-null, the length of the subscript is returned
    in *LENP.  This returns newly-allocated memory. */
 char *
-array_variable_name (s, subp, lenp)
-     char *s, **subp;
-     int *lenp;
+array_variable_name (const char *s, int flags, char **subp, int *lenp)
 {
   char *t, *ret;
-  int ind, ni;
+  int ind, ni, ssflags;
 
   t = mbschr (s, '[');
   if (t == 0)
@@ -889,7 +1411,15 @@ array_variable_name (s, subp, lenp)
       return ((char *)NULL);
     }
   ind = t - s;
-  ni = skipsubscript (s, ind, 0);
+  if ((flags & (AV_NOEXPAND|AV_ONEWORD)) == (AV_NOEXPAND|AV_ONEWORD))
+    ni = strlen (s) - 1;
+  else
+    {
+      ssflags = 0;
+      if (flags & AV_NOEXPAND)
+       ssflags |= 1;
+      ni = skipsubscript (s, ind, ssflags);
+    }
   if (ni <= ind + 1 || s[ni] != ']')
     {
       err_badarraysub (s);
@@ -916,20 +1446,18 @@ array_variable_name (s, subp, lenp)
    non-null, return a pointer to the start of the subscript in *SUBP.
    If LENP is non-null, the length of the subscript is returned in *LENP. */
 SHELL_VAR *
-array_variable_part (s, subp, lenp)
-     char *s, **subp;
-     int *lenp;
+array_variable_part (const char *s, int flags, char **subp, int *lenp)
 {
   char *t;
   SHELL_VAR *var;
 
-  t = array_variable_name (s, subp, lenp);
+  t = array_variable_name (s, flags, subp, lenp);
   if (t == 0)
     return ((SHELL_VAR *)NULL);
-  var = find_variable (t);
+  var = find_variable (t);             /* XXX - handle namerefs here? */
 
   free (t);
-  return (var == 0 || invisible_p (var)) ? (SHELL_VAR *)0 : var;
+  return var;  /* now return invisible variables; caller must handle */
 }
 
 #define INDEX_ERROR() \
@@ -953,19 +1481,16 @@ array_variable_part (s, subp, lenp)
    is non-null it gets 1 if the array reference is name[*], 2 if the
    reference is name[@], and 0 otherwise. */
 static char *
-array_value_internal (s, quoted, flags, rtype, indp)
-     char *s;
-     int quoted, flags, *rtype;
-     arrayind_t *indp;
+array_value_internal (const char *s, int quoted, int flags, array_eltstate_t *estatep)
 {
-  int len;
+  int len, isassoc, subtype;
   arrayind_t ind;
   char *akey;
   char *retval, *t, *temp;
   WORD_LIST *l;
   SHELL_VAR *var;
 
-  var = array_variable_part (s, &t, &len);
+  var = array_variable_part (s, flags, &t, &len);      /* XXX */
 
   /* Expand the index, even if the variable doesn't exist, in case side
      effects are needed, like ${w[i++]} where w is unset. */
@@ -977,54 +1502,73 @@ array_value_internal (s, quoted, flags, rtype, indp)
   if (len == 0)
     return ((char *)NULL);     /* error message already printed */
 
+  isassoc = var && assoc_p (var);
   /* [ */
   akey = 0;
-  if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
+  subtype = 0;
+  if (estatep)
+    estatep->value = (char *)NULL;
+
+  /* Backwards compatibility: we only change the behavior of A[@] and A[*]
+     for associative arrays, and the caller has to request it. */
+  if ((isassoc == 0 || (flags & AV_ATSTARKEYS) == 0) && ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
     {
-      if (rtype)
-       *rtype = (t[0] == '*') ? 1 : 2;
+      if (estatep)
+       estatep->subtype = (t[0] == '*') ? 1 : 2;
       if ((flags & AV_ALLOWALL) == 0)
        {
          err_badarraysub (s);
          return ((char *)NULL);
        }
-      else if (var == 0 || value_cell (var) == 0)      /* XXX - check for invisible_p(var) ? */
+      else if (var == 0 || value_cell (var) == 0)
+       return ((char *)NULL);
+      else if (invisible_p (var))
        return ((char *)NULL);
       else if (array_p (var) == 0 && assoc_p (var) == 0)
-       l = add_string_to_list (value_cell (var), (WORD_LIST *)NULL);
+        {
+          if (estatep)
+           estatep->type = ARRAY_SCALAR;
+         l = add_string_to_list (value_cell (var), (WORD_LIST *)NULL);
+        }
       else if (assoc_p (var))
        {
+         if (estatep)
+           estatep->type = ARRAY_ASSOC;
          l = assoc_to_word_list (assoc_cell (var));
          if (l == (WORD_LIST *)NULL)
            return ((char *)NULL);
        }
       else
        {
+         if (estatep)
+           estatep->type = ARRAY_INDEXED;
          l = array_to_word_list (array_cell (var));
          if (l == (WORD_LIST *)NULL)
            return ((char *) NULL);
        }
 
+      /* Caller of array_value takes care of inspecting estatep->subtype and
+         duplicating retval if subtype == 0, so this is not a memory leak */
       if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
        {
-         temp = string_list_dollar_star (l);
-         retval = quote_string (temp);         /* XXX - leak here */
+         temp = string_list_dollar_star (l, quoted, (flags & AV_ASSIGNRHS) ? PF_ASSIGNRHS : 0);
+         retval = quote_string (temp);
          free (temp);
        }
       else     /* ${name[@]} or unquoted ${name[*]} */
-       retval = string_list_dollar_at (l, quoted);     /* XXX - leak here */
+       retval = string_list_dollar_at (l, quoted, (flags & AV_ASSIGNRHS) ? PF_ASSIGNRHS : 0);
 
       dispose_words (l);
     }
   else
     {
-      if (rtype)
-       *rtype = 0;
+      if (estatep)
+       estatep->subtype = 0;
       if (var == 0 || array_p (var) || assoc_p (var) == 0)
        {
-         if ((flags & AV_USEIND) == 0 || indp == 0)
+         if ((flags & AV_USEIND) == 0 || estatep == 0)
            {
-             ind = array_expand_index (var, t, len);
+             ind = array_expand_index (var, t, len, flags);
              if (ind < 0)
                {
                  /* negative subscripts to indexed arrays count back from end */
@@ -1033,16 +1577,25 @@ array_value_internal (s, quoted, flags, rtype, indp)
                  if (ind < 0)
                    INDEX_ERROR();
                }
-             if (indp)
-               *indp = ind;
+             if (estatep)
+               estatep->ind = ind;
            }
-         else if (indp)
-           ind = *indp;
+         else if (estatep && (flags & AV_USEIND))
+           ind = estatep->ind;
+         if (estatep && var)
+           estatep->type = array_p (var) ? ARRAY_INDEXED : ARRAY_SCALAR;
        }
       else if (assoc_p (var))
        {
          t[len - 1] = '\0';
-         akey = expand_assignment_string_to_string (t, 0);     /* [ */
+         if (estatep)
+           estatep->type = ARRAY_ASSOC;
+         if ((flags & AV_USEIND) && estatep && estatep->key)
+           akey = savestring (estatep->key);
+         else if ((flags & AV_NOEXPAND) == 0)
+           akey = expand_subscript_string (t, 0);      /* [ */
+         else
+           akey = savestring (t);
          t[len - 1] = ']';
          if (akey == 0 || *akey == 0)
            {
@@ -1050,21 +1603,34 @@ array_value_internal (s, quoted, flags, rtype, indp)
              INDEX_ERROR();
            }
        }
-     
-      if (var == 0 || value_cell (var) == 0)   /* XXX - check invisible_p(var) ? */
+
+      if (var == 0 || value_cell (var) == 0)
+       {
+         FREE (akey);
+         return ((char *)NULL);
+       }
+      else if (invisible_p (var))
        {
-          FREE (akey);
+         FREE (akey);
          return ((char *)NULL);
        }
       if (array_p (var) == 0 && assoc_p (var) == 0)
-       return (ind == 0 ? value_cell (var) : (char *)NULL);
+       retval = (ind == 0) ? value_cell (var) : (char *)NULL;
       else if (assoc_p (var))
         {
          retval = assoc_reference (assoc_cell (var), akey);
-         free (akey);
+         if (estatep && estatep->key && (flags & AV_USEIND))
+           free (akey);                /* duplicated estatep->key */
+         else if (estatep)
+           estatep->key = akey;        /* XXX - caller must manage */
+         else                          /* not saving it anywhere */
+           free (akey);
         }
       else
        retval = array_reference (array_cell (var), ind);
+
+      if (estatep)
+       estatep->value = retval;
     }
 
   return retval;
@@ -1073,12 +1639,12 @@ array_value_internal (s, quoted, flags, rtype, indp)
 /* Return a string containing the elements described by the array and
    subscript contained in S, obeying quoting for subscripts * and @. */
 char *
-array_value (s, quoted, flags, rtype, indp)
-     char *s;
-     int quoted, flags, *rtype;
-     arrayind_t *indp;
+array_value (const char *s, int quoted, int flags, array_eltstate_t *estatep)
 {
-  return (array_value_internal (s, quoted, flags|AV_ALLOWALL, rtype, indp));
+  char *retval;
+
+  retval = array_value_internal (s, quoted, flags|AV_ALLOWALL, estatep);
+  return retval;
 }
 
 /* Return the value of the array indexing expression S as a single string.
@@ -1086,25 +1652,23 @@ array_value (s, quoted, flags, rtype, indp)
    is used by other parts of the shell such as the arithmetic expression
    evaluator in expr.c. */
 char *
-get_array_value (s, flags, rtype, indp)
-     char *s;
-     int flags, *rtype;
-     arrayind_t *indp;
+get_array_value (const char *s, int flags, array_eltstate_t *estatep)
 {
-  return (array_value_internal (s, 0, flags, rtype, indp));
+  char *retval;
+
+  retval = array_value_internal (s, 0, flags, estatep);
+  return retval;
 }
 
 char *
-array_keys (s, quoted)
-     char *s;
-     int quoted;
+array_keys (const char *s, int quoted, int pflags)
 {
   int len;
   char *retval, *t, *temp;
   WORD_LIST *l;
   SHELL_VAR *var;
 
-  var = array_variable_part (s, &t, &len);
+  var = array_variable_part (s, 0, &t, &len);
 
   /* [ */
   if (var == 0 || ALL_ELEMENT_SUB (t[0]) == 0 || t[1] != ']')
@@ -1122,14 +1686,7 @@ array_keys (s, quoted)
   if (l == (WORD_LIST *)NULL)
     return ((char *) NULL);
 
-  if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
-    {
-      temp = string_list_dollar_star (l);
-      retval = quote_string (temp);
-      free (temp);
-    }
-  else /* ${!name[@]} or unquoted ${!name[*]} */
-    retval = string_list_dollar_at (l, quoted);
+  retval = string_list_pos_params (t[0], l, quoted, pflags);
 
   dispose_words (l);
   return retval;