/* arrayfunc.c -- High-level array functions used by other parts of the shell. */
-/* Copyright (C) 2001-2009 Free Software Foundation, Inc.
+/* Copyright (C) 2001-2021 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 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 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 SHELL_VAR *bind_array_var_internal __P((SHELL_VAR *, arrayind_t, char *, char *, int));
+static void assign_assoc_from_kvlist PARAMS((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 *));
+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 */
const char * const bash_badsub_errmsg = N_("bad array subscript");
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;
}
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;
}
-static SHELL_VAR *
-bind_array_var_internal (entry, ind, key, value, flags)
+char *
+make_array_variable_value (entry, ind, key, value, flags)
SHELL_VAR *entry;
arrayind_t ind;
char *key;
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))
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);
}
{
SHELL_VAR *entry;
- 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);
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)
+assign_array_element (name, value, flags, estatep)
char *name, *value;
int flags;
+ array_eltstate_t *estatep;
{
- char *sub, *vname, *akey;
- arrayind_t ind;
- int sublen;
+ char *sub, *vname;
+ 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);
}
- entry = find_variable (vname);
+ 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;
+}
+
+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';
- 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)
{
- free (vname);
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 (sub, sublen);
+ 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)
{
- free (vname);
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;
+ }
}
- free (vname);
return (entry);
}
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 = (flags & 2) ? make_new_assoc_variable (name) : make_new_array_variable (name);
}
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);
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);
+ bind_array_var_internal (var, i, 0, l->word->word, flags & ~ASS_APPEND);
+
+ VUNSETATTR (var, att_invisible); /* no longer invisible */
+
return var;
}
char *val;
int ni;
- /* I don't believe this condition is ever true any more. */
+ /* 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;
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);
return nlist;
}
-/* Callers ensure that VAR is not NULL */
+#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;
int flags;
{
ARRAY *a;
- HASH_TABLE *h;
+ HASH_TABLE *h, *nhash;
WORD_LIST *list;
- char *w, *val, *nval;
- int len, iflags;
+ 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;
- 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;
if (a && array_p (var))
array_flush (a);
else if (h && assoc_p (var))
- assoc_flush (h);
+ 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 (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 */
if ((list->word->flags & W_ASSIGNMENT) && w[0] == '[')
{
- len = skipsubscript (w, 0, (var && assoc_p (var) != 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);
/* XXX - changes for `+=' */
if (w[len] != ']' || (w[len+1] != '=' && (w[len+1] != '+' || w[len+2] != '=')))
continue;
}
- if (ALL_ELEMENT_SUB (w[1]) && len == 2)
+ if (ALL_ELEMENT_SUB (w[1]) && len == 2 && array_p (var))
{
- if (assoc_p (var))
- report_error (_("%s: invalid associative array key"), w);
- else
- report_error (_("%s: cannot assign to non-numeric index"), w);
+ set_exit_status (EXECUTION_FAILURE);
+ report_error (_("%s: cannot assign to non-numeric index"), w);
continue;
}
if (array_p (var))
{
- ind = array_expand_index (w + 1, len);
+ 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);
}
else if (assoc_p (var))
{
- akey = substring (w, 1, len);
+ /* 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;
}
}
val = w + len + 3;
}
else
- val = w + len + 2;
+ 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;
}
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 */
- bind_array_var_internal (var, ind, akey, val, iflags);
+ 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 (nlist)
dispose_words (nlist);
+
+ if (var)
+ VUNSETATTR (var, att_invisible); /* no longer invisible */
+
return (var);
}
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, 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;
+ 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
{
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 doesn't look like [ind]=value */
+ /* 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;
+
nword = quote_assign (l->word->word);
free (l->word->word);
l->word->word = nword;
+ l->word->flags |= W_NOGLOB; /* XXX - W_NOSPLIT also? */
}
}
/* 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, 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));
+ FREE (akey);
return -1;
}
assoc_remove (assoc_cell (var), akey);
+ if (akey != sub)
+ free (akey);
}
- else
+ else if (array_p (var))
{
- ind = array_expand_index (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 */
+ }
+ 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));
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;
}
/***********************************************************************/
/* 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 = 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, 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_arith_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);
}
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 = mbschr (s, '[');
if (t == 0)
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);
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[*], 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. */
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 = (t[0] == '*') ? 1 : 2;
- 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 || 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);
+ 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;
+ if (estatep)
+ estatep->subtype = 0;
if (var == 0 || array_p (var) || assoc_p (var) == 0)
{
- ind = array_expand_index (t, len);
- if (ind < 0)
+ if ((flags & AV_USEIND) == 0 || estatep == 0)
{
-index_error:
- if (var)
- err_badarraysub (var->name);
- else
+ ind = array_expand_index (var, t, len, flags);
+ if (ind < 0)
{
- t[-1] = '\0';
- err_badarraysub (s);
- t[-1] = '['; /* ] */
+ /* 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();
}
- return ((char *)NULL);
+ 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';
- 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)
- goto index_error;
+ {
+ FREE (akey);
+ INDEX_ERROR();
+ }
+ }
+
+ if (var == 0 || value_cell (var) == 0)
+ {
+ FREE (akey);
+ return ((char *)NULL);
+ }
+ else if (invisible_p (var))
+ {
+ FREE (akey);
+ return ((char *)NULL);
}
-
- if (var == 0)
- 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);
+ {
+ 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;
/* 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 (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))
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;