]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - arrayfunc.c
Bash-5.2 patch 26: fix typo when specifying readline's custom color prefix
[thirdparty/bash.git] / arrayfunc.c
index 0d644b121f6cecaadd1d603c21a2cf07acca1e0f..412562276df315057549bcf89ef48c409c44bea0 100644 (file)
@@ -1,22 +1,22 @@
 /* arrayfunc.c -- High-level array functions used by other parts of the shell. */
 
-/* Copyright (C) 2001-2003 Free Software Foundation, Inc.
+/* Copyright (C) 2001-2021 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
-   Bash is free software; you can redistribute it and/or modify it under
-   the terms of the GNU General Public License as published by the Free
-   Software Foundation; either version 2, or (at your option) any later
-   version.
+   Bash is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
-   Bash is distributed in the hope that it will be useful, but WITHOUT ANY
-   WARRANTY; without even the implied warranty of MERCHANTABILITY or
-   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-   for more details.
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
-   You should have received a copy of the GNU General Public License along
-   with Bash; see the file COPYING.  If not, write to the Free Software
-   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 #include "config.h"
 
 #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;
+#ifndef LBRACK
+#  define LBRACK '['
+#  define RBRACK ']'
+#endif
+
+/* This variable means to not expand associative array subscripts more than
+   once, when performing variable expansion. */
+int assoc_expand_once = 0;
+
+/* Ditto for indexed array subscripts -- currently unused */
+int array_expand_once = 0;
 
-static void quote_array_assignment_chars __P((WORD_LIST *));
-static char *array_value_internal __P((char *, int, int, int *));
+static SHELL_VAR *bind_array_var_internal PARAMS((SHELL_VAR *, arrayind_t, char *, char *, int));
+static SHELL_VAR *assign_array_element_internal PARAMS((SHELL_VAR *, char *, char *, char *, int, char *, int, array_eltstate_t *));
+
+static void assign_assoc_from_kvlist PARAMS((SHELL_VAR *, WORD_LIST *, HASH_TABLE *, int));
+
+static char *quote_assign PARAMS((const char *));
+static void quote_array_assignment_chars PARAMS((WORD_LIST *));
+static char *quote_compound_array_word PARAMS((char *, int));
+static char *array_value_internal PARAMS((const char *, int, int, array_eltstate_t *));
 
 /* Standard error message to use when encountering an invalid array subscript */
-char *bash_badsub_errmsg = N_("bad array subscript");
+const char * const bash_badsub_errmsg = N_("bad array subscript");
 
 /* **************************************************************** */
 /*                                                                 */
@@ -72,13 +93,161 @@ convert_var_to_array (var)
   var->assign_func = (sh_var_assign_func_t *)NULL;
 
   INVALIDATE_EXPORTSTR (var);
+  if (exported_p (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;
+}
+
+/* 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;
+{
+  char *oldval;
+  HASH_TABLE *hash;
+
+  oldval = value_cell (var);
+  hash = assoc_create (0);
+  if (oldval)
+    assoc_insert (hash, savestring ("0"), oldval);
+
+  FREE (value_cell (var));
+  var_setassoc (var, hash);
+
+  /* these aren't valid anymore */
+  var->dynamic_value = (sh_var_value_func_t *)NULL;
+  var->assign_func = (sh_var_assign_func_t *)NULL;
+
+  INVALIDATE_EXPORTSTR (var);
+  if (exported_p (var))
+    array_needs_making++;
+
+  VSETATTR (var, att_assoc);
+  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;
 }
 
+char *
+make_array_variable_value (entry, ind, key, value, flags)
+     SHELL_VAR *entry;
+     arrayind_t ind;
+     char *key;
+     char *value;
+     int flags;
+{
+  SHELL_VAR *dentry;
+  char *newval;
+
+  /* If we're appending, we need the old value of the array reference, so
+     fake out make_variable_value with a dummy SHELL_VAR */
+  if (flags & ASS_APPEND)
+    {
+      dentry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+      dentry->name = savestring (entry->name);
+      if (assoc_p (entry))
+       newval = assoc_reference (assoc_cell (entry), key);
+      else
+       newval = array_reference (array_cell (entry), ind);
+      if (newval)
+       dentry->value = savestring (newval);
+      else
+       {
+         dentry->value = (char *)xmalloc (1);
+         dentry->value[0] = '\0';
+       }
+      dentry->exportstr = 0;
+      dentry->attributes = entry->attributes & ~(att_array|att_assoc|att_exported);
+      /* Leave the rest of the members uninitialized; the code doesn't look
+        at them. */
+      newval = make_variable_value (dentry, value, flags);      
+      dispose_variable (dentry);
+    }
+  else
+    newval = make_variable_value (entry, 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 (entry, hash, key, value, flags)
+     SHELL_VAR *entry;
+     HASH_TABLE *hash;
+     char *key;
+     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);
+  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;
+{
+  char *newval;
+
+  newval = make_array_variable_value (entry, ind, key, value, flags);
+
+  if (entry->assign_func)
+    (*entry->assign_func) (entry, newval, ind, key);
+  else if (assoc_p (entry))
+    assoc_insert (assoc_cell (entry), key, newval);
+  else
+    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);
+}
+
 /* Perform an array assignment name[ind]=value.  If NAME already exists and
    is not an array, and IND is 0, perform name=value instead.  If NAME exists
    and is not an array, and IND is not 0, convert it into an array with the
@@ -87,19 +256,28 @@ convert_var_to_array (var)
    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)
+bind_array_variable (name, ind, value, flags)
      char *name;
      arrayind_t ind;
      char *value;
+     int flags;
 {
   SHELL_VAR *entry;
-  char *newval;
 
-  entry = var_lookup (name, shell_variables);
+  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);
@@ -109,76 +287,226 @@ bind_array_variable (name, ind, value)
     entry = convert_var_to_array (entry);
 
   /* ENTRY is an array variable, and ARRAY points to the value. */
-  newval = make_variable_value (entry, value);
-  if (entry->assign_func)
-    (*entry->assign_func) (entry, newval, ind);
-  else
-    array_insert (array_cell (entry), ind, newval);
-  FREE (newval);
+  return (bind_array_var_internal (entry, ind, 0, value, flags));
+}
 
-  return (entry);
+SHELL_VAR *
+bind_array_element (entry, ind, value, flags)
+     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;
+{
+  if ((readonly_p (entry) && (flags&ASS_FORCE) == 0) || noassign_p (entry))
+    {
+      if (readonly_p (entry))
+       err_readonly (name);
+      return (entry);
+    }
+
+  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)
+assign_array_element (name, value, flags, estatep)
      char *name, *value;
+     int flags;
+     array_eltstate_t *estatep;
 {
   char *sub, *vname;
-  arrayind_t ind;
-  int sublen;
+  int sublen, isassoc, avflags;
   SHELL_VAR *entry;
 
-  vname = array_variable_name (name, &sub, &sublen);
+  avflags = 0;
+  if (flags & ASS_NOEXPAND)
+    avflags |= AV_NOEXPAND;
+  if (flags & ASS_ONEWORD)
+    avflags |= AV_ONEWORD;
+  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);
     }
 
-  ind = array_expand_index (sub, sublen);
-  if (ind < 0)
+  entry = assign_array_element_internal (entry, name, vname, sub, sublen, value, flags, estatep);
+
+#if ARRAY_EXPORT
+  if (entry && exported_p (entry))
     {
-      free (vname);
-      err_badarraysub (name);
-      return ((SHELL_VAR *)NULL);
+      INVALIDATE_EXPORTSTR (entry);
+      array_needs_making = 1;
     }
-
-  entry = bind_array_variable (vname, ind, value);
+#endif
 
   free (vname);
+  return entry;
+}
+
+static SHELL_VAR *
+assign_array_element_internal (entry, name, vname, sub, sublen, value, flags, estatep)
+     SHELL_VAR *entry;
+     char *name;               /* only used for error messages */
+     char *vname;
+     char *sub;
+     int sublen;
+     char *value;
+     int flags;
+     array_eltstate_t *estatep;
+{
+  char *akey, *nkey;
+  arrayind_t ind;
+  char *newval;
+
+  /* rely on the caller to initialize estatep */
+
+  if (entry && assoc_p (entry))
+    {
+      sub[sublen-1] = '\0';
+      if ((flags & ASS_NOEXPAND) == 0)
+       akey = expand_subscript_string (sub, 0);        /* [ */
+      else
+       akey = savestring (sub);
+      sub[sublen-1] = ']';
+      if (akey == 0 || *akey == 0)
+       {
+         err_badarraysub (name);
+         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, 0);
+      /* negative subscripts to indexed arrays count back from end */
+      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);
 }
 
 /* Find the array variable corresponding to NAME.  If there is no variable,
    create a new array variable.  If the variable exists but is not an array,
-   convert it to an indexed array.  If CHECK_FLAGS is non-zero, an existing
+   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). */
+   for assignment (e.g., by the `read' builtin).  If FLAGS&2 is non-zero, we
+   create an associative array. */
 SHELL_VAR *
-find_or_make_array_variable (name, check_flags)
+find_or_make_array_variable (name, flags)
      char *name;
-     int check_flags;
+     int flags;
 {
   SHELL_VAR *var;
 
   var = find_variable (name);
+  if (var == 0)
+    {
+      /* See if we have a nameref pointing to a variable that hasn't been
+        created yet. */
+      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))
+       {
+         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)
-    var = make_new_array_variable (name);
-  else if (check_flags && (readonly_p (var) || noassign_p (var)))
+    var = (flags & 2) ? make_new_assoc_variable (name) : make_new_array_variable (name);
+  else if ((flags & 1) && (readonly_p (var) || noassign_p (var)))
     {
       if (readonly_p (var))
        err_readonly (name);
       return ((SHELL_VAR *)NULL);
     }
-  else if (array_p (var) == 0)
+  else if ((flags & 2) && array_p (var))
+    {
+      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);
 
   return (var);
@@ -187,62 +515,66 @@ find_or_make_array_variable (name, check_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)
+assign_array_from_string (name, value, flags)
      char *name, *value;
+     int flags;
 {
   SHELL_VAR *var;
+  int vflags;
+
+  vflags = 1;
+  if (flags & ASS_MKASSOC)
+    vflags |= 2;
 
-  var = find_or_make_array_variable (name, 1);
+  var = find_or_make_array_variable (name, vflags);
   if (var == 0)
     return ((SHELL_VAR *)NULL);
 
-  return (assign_array_var_from_string (var, value));
+  return (assign_array_var_from_string (var, 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)
+assign_array_var_from_word_list (var, list, flags)
      SHELL_VAR *var;
      WORD_LIST *list;
+     int flags;
 {
   register arrayind_t i;
   register WORD_LIST *l;
   ARRAY *a;
 
-  for (a = array_cell (var), l = list, i = 0; l; l = l->next, i++)
-    if (var->assign_func)
-      (*var->assign_func) (var, l->word->word, i);
-    else
-      array_insert (a, i, l->word->word);
+  a = array_cell (var);
+  i = (flags & ASS_APPEND) ? array_max_index (a) + 1 : 0;
+
+  for (l = list; l; l = l->next, i++)
+    bind_array_var_internal (var, i, 0, l->word->word, flags & ~ASS_APPEND);
+
+  VUNSETATTR (var, att_invisible);     /* no longer invisible */
+
   return var;
 }
 
-/* Perform a compound array assignment:  VAR->name=( VALUE ).  The
-   VALUE has already had the parentheses stripped. */
-SHELL_VAR *
-assign_array_var_from_string (var, value)
+WORD_LIST *
+expand_compound_array_assignment (var, value, flags)
      SHELL_VAR *var;
      char *value;
+     int flags;
 {
-  ARRAY *a;
   WORD_LIST *list, *nlist;
-  char *w, *val, *nval;
-  int ni, len;
-  arrayind_t ind, last_ind;
-
-  if (value == 0)
-    return var;
+  char *val;
+  int ni;
 
-  /* If this is called from declare_builtin, value[0] == '(' and
-     xstrchr(value, ')') != 0.  In this case, we need to extract
-     the value from between the parens before going on. */
+  /* This condition is true when invoked from the declare builtin with a
+     command like
+       declare -a d='([1]="" [2]="bdef" [5]="hello world" "test")' */
   if (*value == '(')   /*)*/
     {
       ni = 1;
       val = extract_array_assignment_list (value, &ni);
       if (val == 0)
-       return var;
+       return (WORD_LIST *)NULL;
     }
   else
     val = value;
@@ -251,10 +583,31 @@ assign_array_var_from_string (var, value)
      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)
+       free (val);
+      return list;
+    }
+
   /* If we're using [subscript]=value, we need to quote each [ and ] to
-     prevent unwanted filename expansion. */
+     prevent unwanted filename expansion.  This doesn't need to be done
+     for associative array expansion, since that uses a different expansion
+     function (see assign_compound_array_list below). */
   if (list)
     quote_array_assignment_chars (list);
 
@@ -267,27 +620,154 @@ assign_array_var_from_string (var, value)
   if (val != value)
     free (val);
 
-  a = array_cell (var);
+  return nlist;
+}
+
+#if ASSOC_KVPAIR_ASSIGNMENT
+static void
+assign_assoc_from_kvlist (var, nlist, h, flags)
+     SHELL_VAR *var;
+     WORD_LIST *nlist;
+     HASH_TABLE *h;
+     int flags;
+{
+  WORD_LIST *list;
+  char *akey, *aval, *k, *v;
+
+  for (list = nlist; list; list = list->next)
+    {
+      k = list->word->word;
+      v = list->next ? list->next->word->word : 0;
+
+      if (list->next)
+        list = list->next;
+
+      akey = expand_subscript_string (k, 0);
+      if (akey == 0 || *akey == 0)
+       {
+         err_badarraysub (k);
+         FREE (akey);
+         continue;
+       }             
+
+      aval = 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);
+    }
+}
+
+/* Return non-zero if L appears to be a key-value pair associative array
+   compound assignment. */ 
+int
+kvpair_assignment_p (l)
+     WORD_LIST *l;
+{
+  return (l && (l->word->flags & W_ASSIGNMENT) == 0 && l->word->word[0] != '[');       /*]*/
+}
+
+char *
+expand_and_quote_kvpair_word (w)
+     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 */
+void
+assign_compound_array_list (var, nlist, flags)
+     SHELL_VAR *var;
+     WORD_LIST *nlist;
+     int flags;
+{
+  ARRAY *a;
+  HASH_TABLE *h, *nhash;
+  WORD_LIST *list;
+  char *w, *val, *nval, *savecmd;
+  int len, iflags, free_val;
+  arrayind_t ind, last_ind;
+  char *akey;
+
+  a = (var && array_p (var)) ? array_cell (var) : (ARRAY *)0;
+  nhash = h = (var && assoc_p (var)) ? assoc_cell (var) : (HASH_TABLE *)0;
+
+  akey = (char *)0;
+  ind = 0;
 
   /* Now that we are ready to assign values to the array, kill the existing
      value. */
-  if (a)
-    array_flush (a);
+  if ((flags & ASS_APPEND) == 0)
+    {
+      if (a && array_p (var))
+       array_flush (a);
+      else if (h && assoc_p (var))
+       nhash = assoc_create (h->nbuckets);
+    }
+
+  last_ind = (a && (flags & ASS_APPEND)) ? array_max_index (a) + 1 : 0;
+
+#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;
+    }
+#endif
 
-  for (last_ind = 0, list = nlist; list; list = list->next)
+  for (list = nlist; list; list = list->next)
     {
+      /* 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 */
       if ((list->word->flags & W_ASSIGNMENT) && w[0] == '[')
        {
-         len = skipsubscript (w, 0);
+         /* Don't have to handle embedded quotes specially any more, since
+            associative array subscripts have not been expanded yet (see
+            above). */
+         len = skipsubscript (w, 0, 0);
 
-         if (w[len] != ']' || w[len+1] != '=')
+         /* XXX - changes for `+=' */
+         if (w[len] != ']' || (w[len+1] != '=' && (w[len+1] != '+' || w[len+2] != '=')))
            {
-             nval = make_variable_value (var, w);
+             if (assoc_p (var))
+               {
+                 err_badarraysub (w);
+                 continue;
+               }
+             nval = make_variable_value (var, w, flags);
              if (var->assign_func)
-               (*var->assign_func) (var, nval, last_ind);
+               (*var->assign_func) (var, nval, last_ind, 0);
              else
                array_insert (a, last_ind, nval);
              FREE (nval);
@@ -301,20 +781,56 @@ assign_array_var_from_string (var, value)
              continue;
            }
 
-         if (ALL_ELEMENT_SUB (w[1]) && len == 2)
+         if (ALL_ELEMENT_SUB (w[1]) && len == 2 && array_p (var))
            {
+             set_exit_status (EXECUTION_FAILURE);
              report_error (_("%s: cannot assign to non-numeric index"), w);
              continue;
            }
 
-         ind = array_expand_index (w + 1, len);
-         if (ind < 0)
+         if (array_p (var))
            {
-             err_badarraysub (w);
-             continue;
+             ind = array_expand_index (var, w + 1, len, 0);
+             /* 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;
+               }
+
+             last_ind = ind;
+           }
+         else if (assoc_p (var))
+           {
+             /* This is not performed above, see expand_compound_array_assignment */
+             w[len] = '\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;
+               }
            }
-         last_ind = ind;
-         val = w + len + 2;
+
+         /* XXX - changes for `+=' -- just accept the syntax.  ksh93 doesn't do this */
+         if (w[len + 1] == '+' && w[len + 2] == '=')
+           {
+             iflags |= ASS_APPEND;
+             val = w + len + 3;
+           }
+         else
+           val = w + len + 2;      
+       }
+      else if (assoc_p (var))
+       {
+         set_exit_status (EXECUTION_FAILURE);
+         report_error (_("%s: %s: must use subscript when assigning associative array"), var->name, w);
+         continue;
        }
       else             /* No [ind]=value, just a stray `=' */
        {
@@ -322,150 +838,371 @@ assign_array_var_from_string (var, value)
          val = w;
        }
 
+      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 */
-      nval = make_variable_value (var, val);
-      if (var->assign_func)
-       (*var->assign_func) (var, nval, ind);
+      if (assoc_p (var))
+       bind_assoc_var_internal (var, nhash, akey, val, iflags);
       else
-       array_insert (a, ind, nval);
-      FREE (nval);
+       bind_array_var_internal (var, ind, akey, val, iflags);
       last_ind++;
+      this_command_name = savecmd;
+
+      if (free_val)
+       free (val);
     }
 
-  dispose_words (nlist);
+  if (assoc_p (var) && nhash && nhash != h)
+    {
+      h = assoc_cell (var);
+      var_setassoc (var, nhash);
+      assoc_dispose (h);
+    }
+}
+
+/* Perform a compound array assignment:  VAR->name=( VALUE ).  The
+   VALUE has already had the parentheses stripped. */
+SHELL_VAR *
+assign_array_var_from_string (var, value, flags)
+     SHELL_VAR *var;
+     char *value;
+     int flags;
+{
+  WORD_LIST *nlist;
+
+  if (value == 0)
+    return var;
+
+  nlist = expand_compound_array_assignment (var, value, flags);
+  assign_compound_array_list (var, nlist, flags);
+
+  if (nlist)
+    dispose_words (nlist);
+
+  if (var)
+    VUNSETATTR (var, att_invisible);   /* no longer invisible */
+
   return (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;
+{
+  size_t slen;
+  int saw_eq;
+  char *temp, *t, *subs;
+  const char *s, *send;
+  int ss, se;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string);
+  send = string + slen;
+
+  t = temp = (char *)xmalloc (slen * 2 + 1);
+  saw_eq = 0;
+  for (s = string; *s; )
+    {
+      if (*s == '=')
+       saw_eq = 1;
+      if (saw_eq == 0 && *s == '[')            /* looks like a subscript */
+       {
+         ss = s - string;
+         se = skipsubscript (string, ss, 0);
+         subs = substring (s, ss, se);
+         *t++ = '\\';
+         strcpy (t, subs);
+         t += se - ss;
+         *t++ = '\\';
+         *t++ = ']';
+         s += se + 1;
+         free (subs);
+         continue;
+       }
+      if (saw_eq == 0 && (glob_char_p (s) || isifs (*s)))
+       *t++ = '\\';
+
+      COPY_CHAR_P (t, s, send);
+    }
+  *t = '\0';
+  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 (w, type)
+     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);
+
+  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 (w, type)
+     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, quote the `[' and `]' before the `=' to protect them from
-   unwanted filename expansion. */
-static void
-quote_array_assignment_chars (list)
+   [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 (list, type)
      WORD_LIST *list;
+     int type;
 {
-  char *s, *t, *nword;
-  int saw_eq;
+  char *s, *t;
   WORD_LIST *l;
 
   for (l = list; l; l = l->next)
     {
-      if (l->word == 0 || l->word->word == 0 || l->word->word[0] == '\0')
+      if (l->word == 0 || l->word->word == 0)
        continue;       /* should not happen, but just in case... */
-      /* Don't bother if it doesn't look like [ind]=value */
-      if (l->word->word[0] != '[' || xstrchr (l->word->word, '=') == 0) /* ] */
-       continue;
-      s = nword = (char *)xmalloc (strlen (l->word->word) * 2 + 1);
-      saw_eq = 0;
-      for (t = l->word->word; *t; )
+      if ((l->word->flags & W_ASSIGNMENT) == 0)
        {
-         if (*t == '=')
-           saw_eq = 1;
-         if (saw_eq == 0 && (*t == '[' || *t == ']'))
-           *s++ = '\\';
-         *s++ = *t++;
+         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);
        }
-      *s = '\0';
+      else 
+       t = quote_compound_array_word (l->word->word, type);
       free (l->word->word);
-      l->word->word = nword;
+      l->word->word = t;
     }
 }
 
-/* This function assumes s[i] == '['; returns with s[ret] == ']' if
-   an array subscript is correctly parsed. */
-int
-skipsubscript (s, i)
-     const char *s;
-     int i;
+/* 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;
 {
-  int count, c;
-#if defined (HANDLE_MULTIBYTE)
-  mbstate_t state, state_bak;
-  size_t slength, mblength;
-  size_t mb_cur_max;
-#endif
+  char *nword;
+  WORD_LIST *l;
 
-#if defined (HANDLE_MULTIBYTE)
-  memset (&state, '\0', sizeof (mbstate_t));
-  slength = strlen (s + i);
-  mb_cur_max = MB_CUR_MAX;
-#endif
-  
-  count = 1;
-  while (count)
+  for (l = list; l; l = l->next)
     {
-      /* Advance one (possibly multibyte) character in S starting at I. */
-#if defined (HANDLE_MULTIBYTE)
-      if (mb_cur_max > 1)
-       {
-         state_bak = state;
-         mblength = mbrlen (s + i, slength, &state);
-
-         if (MB_INVALIDCH (mblength))
-           {
-             state = state_bak;
-             i++;
-             slength--;
-           }
-         else if (MB_NULLWCH (mblength))
-           return i;
-         else
-           {
-             i += mblength;
-             slength -= mblength;
-           }
-       }
-      else
-#endif
-      ++i;
-
-      c = s[i];
+      if (l->word == 0 || l->word->word == 0 || l->word->word[0] == '\0')
+       continue;       /* should not happen, but just in case... */
+      /* Don't bother if it hasn't been recognized as an assignment or
+        doesn't look like [ind]=value */
+      if ((l->word->flags & W_ASSIGNMENT) == 0)
+       continue;
+      if (l->word->word[0] != '[' || mbschr (l->word->word, '=') == 0) /* ] */
+       continue;
 
-      if (c == 0)
-        break;
-      else if (c == '[')
-       count++;
-      else if (c == ']')
-       count--;
+      nword = quote_assign (l->word->word);
+      free (l->word->word);
+      l->word->word = nword;
+      l->word->flags |= W_NOGLOB;      /* XXX - W_NOSPLIT also? */
     }
-
-  return i;
 }
 
+/* skipsubscript moved to subst.c to use private functions. 2009/02/24. */
+
 /* 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)
+unbind_array_element (var, sub, flags)
      SHELL_VAR *var;
      char *sub;
+     int flags;
 {
-  int len;
   arrayind_t ind;
+  char *akey;
   ARRAY_ELEMENT *ae;
 
-  len = skipsubscript (sub, 0);
-  if (sub[len] != ']' || 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)
     {
-      builtin_error ("%s[%s: %s", var->name, sub, _(bash_badsub_errmsg));
-      return -1;
+      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 */
     }
-  sub[len] = '\0';
 
-  if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0)
+  if (assoc_p (var))
     {
-      unbind_variable (var->name);
-      return (0);
+      akey = (flags & VA_NOEXPAND) ? sub : expand_subscript_string (sub, 0);
+      if (akey == 0 || *akey == 0)
+       {
+         builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg));
+         FREE (akey);
+         return -1;
+       }
+      assoc_remove (assoc_cell (var), akey);
+      if (akey != sub)
+       free (akey);
     }
-  ind = array_expand_index (sub, len+1);
-  if (ind < 0)
+  else if (array_p (var))
     {
-      builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg));
-      return -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 */
+       }
+      ind = array_expand_index (var, sub, strlen (sub) + 1, 0);
+      /* negative subscripts to indexed arrays count back from end */
+      if (ind < 0)
+       ind = array_max_index (array_cell (var)) + 1 + ind;
+      if (ind < 0)
+       {
+         builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg));
+         return -1;
+       }
+      ae = array_remove (array_cell (var), ind);
+      if (ae)
+       array_dispose_element (ae);
     }
-  ae = array_remove (array_cell (var), ind);
-  if (ae)
-    array_dispose_element (ae);
+  else /* array_p (var) == 0 && assoc_p (var) == 0 */
+    {
+      akey = this_command_name;
+      ind = array_expand_index (var, sub, strlen (sub) + 1, 0);
+      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;
 }
 
@@ -489,6 +1226,26 @@ 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;
+{
+  char *vstr;
+
+  vstr = assoc_to_assign (assoc_cell (var), quoted);
+
+  if (vstr == 0)
+    printf ("%s=%s\n", var->name, quoted ? "'()'" : "()");
+  else
+    {
+      printf ("%s=%s\n", var->name, vstr);
+      free (vstr);
+    }
+}
+
 /***********************************************************************/
 /*                                                                    */
 /* Utility functions to manage arrays and their contents for expansion */
@@ -496,54 +1253,125 @@ print_array_assignment (var, quoted)
 /***********************************************************************/
 
 /* Return 1 if NAME is a properly-formed array reference v[sub]. */
+
+/* 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)
+tokenize_array_reference (name, flags, subp)
      char *name;
+     int flags;
+     char **subp;
 {
   char *t;
-  int r, len;
+  int r, len, isassoc, ssflags;
+  SHELL_VAR *entry;
 
-  t = xstrchr (name, '[');     /* ] */
+  t = mbschr (name, '[');      /* ] */
+  isassoc = 0;
   if (t)
     {
       *t = '\0';
       r = legal_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);
-      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;
 }
 
+/* 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 (name, flags)
+     const char *name;
+     int flags;
+{
+  return tokenize_array_reference ((char *)name, flags, (char **)NULL);
+}
+
 /* Expand the array index beginning at S and extending LEN characters. */
 arrayind_t
-array_expand_index (s, len)
+array_expand_index (var, s, len, flags)
+     SHELL_VAR *var;
      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_string_to_string (exp, 0);
+#if 0  /* TAG: maybe bash-5.2 */
+  if ((flags & AV_NOEXPAND) == 0)
+    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);
     }
   return val;
@@ -554,14 +1382,16 @@ array_expand_index (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;
+array_variable_name (s, flags, subp, lenp)
+     const char *s;
+     int flags;
+     char **subp;
      int *lenp;
 {
   char *t, *ret;
-  int ind, ni;
+  int ind, ni, ssflags;
 
-  t = xstrchr (s, '[');
+  t = mbschr (s, '[');
   if (t == 0)
     {
       if (subp)
@@ -571,7 +1401,15 @@ array_variable_name (s, subp, lenp)
       return ((char *)NULL);
     }
   ind = t - s;
-  ni = skipsubscript (s, ind);
+  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);
@@ -598,39 +1436,58 @@ 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;
+array_variable_part (s, flags, subp, lenp)
+     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() \
+  do \
+    { \
+      if (var) \
+       err_badarraysub (var->name); \
+      else \
+       { \
+         t[-1] = '\0'; \
+         err_badarraysub (s); \
+         t[-1] = '[';  /* ] */\
+       } \
+      return ((char *)NULL); \
+    } \
+  while (0)
+
 /* Return a string containing the elements in the array and subscript
    described by S.  If the subscript is * or @, obeys quoting rules akin
    to the expansion of $* and $@ including double quoting.  If RTYPE
-   is non-null it gets 1 if the array reference is name[@] or name[*]
-   and 0 otherwise. */
+   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, allow_all, rtype)
-     char *s;
-     int quoted, allow_all, *rtype;
+array_value_internal (s, quoted, flags, estatep)
+     const char *s;
+     int quoted, 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. */
@@ -642,60 +1499,135 @@ array_value_internal (s, quoted, allow_all, rtype)
   if (len == 0)
     return ((char *)NULL);     /* error message already printed */
 
+  isassoc = var && assoc_p (var);
   /* [ */
-  if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
+  akey = 0;
+  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 = 1;
-      if (allow_all == 0)
+      if (estatep)
+       estatep->subtype = (t[0] == '*') ? 1 : 2;
+      if ((flags & AV_ALLOWALL) == 0)
        {
          err_badarraysub (s);
          return ((char *)NULL);
        }
-      else if (var == 0)
+      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)
-       l = add_string_to_list (value_cell (var), (WORD_LIST *)NULL);
+      else if (array_p (var) == 0 && assoc_p (var) == 0)
+        {
+          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);
+         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);
+       retval = string_list_dollar_at (l, quoted, (flags & AV_ASSIGNRHS) ? PF_ASSIGNRHS : 0);
 
       dispose_words (l);
     }
   else
     {
-      if (rtype)
-       *rtype = 0;
-      ind = array_expand_index (t, len);
-      if (ind < 0)
+      if (estatep)
+       estatep->subtype = 0;
+      if (var == 0 || array_p (var) || assoc_p (var) == 0)
        {
-         if (var)
-           err_badarraysub (var->name);
+         if ((flags & AV_USEIND) == 0 || estatep == 0)
+           {
+             ind = array_expand_index (var, t, len, flags);
+             if (ind < 0)
+               {
+                 /* negative subscripts to indexed arrays count back from end */
+                 if (var && array_p (var))
+                   ind = array_max_index (array_cell (var)) + 1 + ind;
+                 if (ind < 0)
+                   INDEX_ERROR();
+               }
+             if (estatep)
+               estatep->ind = ind;
+           }
+         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';
+         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)
            {
-             t[-1] = '\0';
-             err_badarraysub (s);
-             t[-1] = '[';      /* ] */
+             FREE (akey);
+             INDEX_ERROR();
            }
+       }
+
+      if (var == 0 || value_cell (var) == 0)
+       {
+         FREE (akey);
          return ((char *)NULL);
        }
-      if (var == 0)
-       return ((char *)NULL);
-      if (array_p (var) == 0)
-       return (ind == 0 ? value_cell (var) : (char *)NULL);
-      retval = array_reference (array_cell (var), ind);
+      else if (invisible_p (var))
+       {
+         FREE (akey);
+         return ((char *)NULL);
+       }
+      if (array_p (var) == 0 && assoc_p (var) == 0)
+       retval = (ind == 0) ? value_cell (var) : (char *)NULL;
+      else if (assoc_p (var))
+        {
+         retval = assoc_reference (assoc_cell (var), 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;
@@ -704,58 +1636,62 @@ array_value_internal (s, quoted, allow_all, rtype)
 /* 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, rtype)
-     char *s;
-     int quoted, *rtype;
+array_value (s, quoted, flags, estatep)
+     const char *s;
+     int quoted, flags;
+     array_eltstate_t *estatep;
 {
-  return (array_value_internal (s, quoted, 1, rtype));
+  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.
-   If ALLOW_ALL is 0, do not allow `@' and `*' subscripts.  This is used
-   by other parts of the shell such as the arithmetic expression evaluator
-   in expr.c. */
+   If (FLAGS & AV_ALLOWALL) is 0, do not allow `@' and `*' subscripts.  This
+   is used by other parts of the shell such as the arithmetic expression
+   evaluator in expr.c. */
 char *
-get_array_value (s, allow_all, rtype)
-     char *s;
-     int allow_all, *rtype;
+get_array_value (s, flags, estatep)
+     const char *s;
+     int flags;
+     array_eltstate_t *estatep;
 {
-  return (array_value_internal (s, 0, allow_all, rtype));
+  char *retval;
+
+  retval = array_value_internal (s, 0, flags, estatep);
+  return retval;
 }
 
 char *
-array_keys (s, quoted)
+array_keys (s, quoted, pflags)
      char *s;
-     int quoted;
+     int quoted, 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] != ']')
     return (char *)NULL;
 
-  if (array_p (var) == 0)
+  if (var_isset (var) == 0 || invisible_p (var))
+    return (char *)NULL;
+
+  if (array_p (var) == 0 && assoc_p (var) == 0)
     l = add_string_to_list ("0", (WORD_LIST *)NULL);
+  else if (assoc_p (var))
+    l = assoc_keys_to_word_list (assoc_cell (var));
   else
-    {
-      l = array_keys_to_word_list (array_cell (var));
-      if (l == (WORD_LIST *)NULL)
-        return ((char *) NULL);
-    }
+    l = array_keys_to_word_list (array_cell (var));
+  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;