/* arrayfunc.c -- High-level array functions used by other parts of the shell. */
-/* Copyright (C) 2001-2016 Free Software Foundation, Inc.
+/* Copyright (C) 2001-2020 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
#include "builtins/common.h"
+#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 __P((SHELL_VAR *, arrayind_t, char *, char *, int));
-static SHELL_VAR *assign_array_element_internal __P((SHELL_VAR *, char *, char *, char *, int, char *, int));
+static 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));
+
+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((const char *, int, int, int *, arrayind_t *));
+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, int *, arrayind_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);
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);
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;
return (entry);
}
- return (bind_array_var_internal (entry, 0, key, value, flags));
+ return (bind_assoc_var_internal (entry, assoc_cell (entry), key, value, flags));
}
/* Parse NAME, a lhs of an assignment statement of the form v[s], and
}
else if ((flags & 2) && array_p (var))
{
- last_command_exit_value = 1;
+ set_exit_status (EXECUTION_FAILURE);
report_error (_("%s: cannot convert indexed to associative array"), name);
return ((SHELL_VAR *)NULL);
}
(ksh93 seems to do this). */
list = parse_string_to_word_list (val, 1, "array assign");
+ /* 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)
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;
+ int free_aval;
+
+ for (list = nlist; list; list = list->next)
+ {
+ free_aval = 0;
+
+ k = list->word->word;
+ v = list->next ? list->next->word->word : 0;
+
+ if (list->next)
+ list = list->next;
+
+ akey = expand_assignment_string_to_string (k, 0);
+ aval = expand_assignment_string_to_string (v, 0);
+
+ if (akey == 0 || *akey == 0)
+ {
+ err_badarraysub (k);
+ FREE (akey);
+ continue;
+ }
+ if (aval == 0)
+ {
+ aval = (char *)xmalloc (1);
+ aval[0] = '\0'; /* like do_assignment_internal */
+ free_aval = 1;
+ }
+
+ bind_assoc_var_internal (var, h, akey, aval, flags);
+ if (free_aval)
+ free (aval);
+ }
+}
+#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, *savecmd;
int len, iflags, free_val;
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) && nlist && (nlist->word->flags & W_ASSIGNMENT) == 0 && nlist->word->word[0] != '[') /*]*/
+ {
+ 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)
{
/* Don't allow var+=(values) to make assignments in VALUES append to
if (ALL_ELEMENT_SUB (w[1]) && len == 2)
{
- last_command_exit_value = 1;
+ set_exit_status (EXECUTION_FAILURE);
if (assoc_p (var))
report_error (_("%s: invalid associative array key"), w);
else
}
else if (assoc_p (var))
{
- last_command_exit_value = 1;
+ set_exit_status (EXECUTION_FAILURE);
report_error (_("%s: %s: must use subscript when assigning associative array"), var->name, w);
continue;
}
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);
+ }
}
/* Perform a compound array assignment: VAR->name=( VALUE ). The
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));
+ ind = skipsubscript (w, 0, 0);
+ if (w[ind] != RBRACK)
+ return (sh_single_quote (w));
+
+ wlen = strlen (w);
+ w[ind] = '\0';
+ sub = sh_single_quote (w+1);
+ 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);
+ i++; /* accommodate the opening LBRACK */
+ nword[i++] = w[ind++]; /* RBRACK */
+ if (w[ind] == '+')
+ nword[i++] = w[ind++];
+ nword[i++] = w[ind++];
+ value = sh_single_quote (w + ind);
+ 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, *t;
+ int ind, wlen, i;
+
+ if (w[0] != LBRACK)
+ return (sh_single_quote (w));
+ ind = skipsubscript (w, 0, 0);
+ if (w[ind] != RBRACK)
+ return (sh_single_quote (w));
+
+ w[ind] = '\0';
+ t = expand_assignment_string_to_string (w+1, 0);
+ w[ind] = RBRACK;
+ key = sh_single_quote (t ? t : "");
+ 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);
+ value = sh_single_quote (t ? t : "");
+ 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 *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)
+ t = sh_single_quote (l->word->word);
+ 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
exp = (char *)xmalloc (len);
strncpy (exp, s, len - 1);
exp[len - 1] = '\0';
-#if 0 /* XXX - not yet -- maybe bash-5.1 */
+#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 = expand_arith_string (exp, Q_DOUBLE_QUOTES|Q_ARITH|Q_ARRAYSUB); /* XXX - Q_ARRAYSUB for future use */
savecmd = this_command_name;
this_command_name = (char *)NULL;
- val = evalexp (t, 0, &expok);
+ val = evalexp (t, EXP_EXPANDED, &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;
}
char *
-array_keys (s, quoted)
+array_keys (s, quoted, pflags)
char *s;
- int quoted;
+ int quoted, pflags;
{
int len;
char *retval, *t, *temp;
if (l == (WORD_LIST *)NULL)
return ((char *) NULL);
- if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
- {
- temp = string_list_dollar_star (l, quoted, 0);
- retval = quote_string (temp);
- free (temp);
- }
- else /* ${!name[@]} or unquoted ${!name[*]} */
- retval = string_list_dollar_at (l, quoted, 0);
+ retval = string_list_pos_params (t[0], l, quoted, pflags);
dispose_words (l);
return retval;