/* arrayfunc.c -- High-level array functions used by other parts of the shell. */
/* 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 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.
You should have received a copy of the GNU General Public License
along with Bash. If not, see .
*/
#include "config.h"
#if defined (ARRAY_VARS)
#if defined (HAVE_UNISTD_H)
# include
#endif
#include
#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 /* mbschr */
#endif
#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 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 */
const char * const bash_badsub_errmsg = N_("bad array subscript");
/* **************************************************************** */
/* */
/* Functions to manipulate array variables and perform assignments */
/* */
/* **************************************************************** */
/* Convert a shell variable to an array variable. The original value is
saved as array[0]. */
SHELL_VAR *
convert_var_to_array (var)
SHELL_VAR *var;
{
char *oldval;
ARRAY *array;
oldval = value_cell (var);
array = array_create ();
if (oldval)
array_insert (array, 0, oldval);
FREE (value_cell (var));
var_setarray (var, array);
/* 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_array);
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
existing value as name[0].
If NAME does not exist, just create an array variable, no matter what
IND's value may be. */
SHELL_VAR *
bind_array_variable (name, ind, value, flags)
char *name;
arrayind_t ind;
char *value;
int flags;
{
SHELL_VAR *entry;
entry = find_shell_variable (name);
if (entry == (SHELL_VAR *) 0)
{
/* Is NAME a nameref variable that points to an unset variable? */
entry = find_variable_nameref_for_create (name, 0);
if (entry == INVALID_NAMEREF_VALUE)
return ((SHELL_VAR *)0);
if (entry && nameref_p (entry))
entry = make_new_array_variable (nameref_cell (entry));
}
if (entry == (SHELL_VAR *) 0)
entry = make_new_array_variable (name);
else if ((readonly_p (entry) && (flags&ASS_FORCE) == 0) || noassign_p (entry))
{
if (readonly_p (entry))
err_readonly (name);
return (entry);
}
else if (array_p (entry) == 0)
entry = convert_var_to_array (entry);
/* ENTRY is an array variable, and ARRAY points to the value. */
return (bind_array_var_internal (entry, ind, 0, value, flags));
}
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().
Flags are ASS_ assignment flags */
SHELL_VAR *
assign_array_element (name, value, flags, estatep)
char *name, *value;
int flags;
array_eltstate_t *estatep;
{
char *sub, *vname;
int sublen, isassoc, avflags;
SHELL_VAR *entry;
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);
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 = 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';
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 FLAGS&1 is non-zero, an existing
variable is checked for the readonly or noassign attribute in preparation
for assignment (e.g., by the `read' builtin). If FLAGS&2 is non-zero, we
create an associative array. */
SHELL_VAR *
find_or_make_array_variable (name, flags)
char *name;
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 = (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 ((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);
}
/* Perform a compound assignment statement for array NAME, where VALUE is
the text between the parens: NAME=( VALUE ) */
SHELL_VAR *
assign_array_from_string (name, value, flags)
char *name, *value;
int flags;
{
SHELL_VAR *var;
int vflags;
vflags = 1;
if (flags & ASS_MKASSOC)
vflags |= 2;
var = find_or_make_array_variable (name, vflags);
if (var == 0)
return ((SHELL_VAR *)NULL);
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, flags)
SHELL_VAR *var;
WORD_LIST *list;
int flags;
{
register arrayind_t i;
register WORD_LIST *l;
ARRAY *a;
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;
}
WORD_LIST *
expand_compound_array_assignment (var, value, flags)
SHELL_VAR *var;
char *value;
int flags;
{
WORD_LIST *list, *nlist;
char *val;
int ni;
/* 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 (WORD_LIST *)NULL;
}
else
val = value;
/* Expand the value string into a list of words, performing all the
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. 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);
/* Now that we've split it, perform the shell expansions on each
word in the list. */
nlist = list ? expand_words_no_vars (list) : (WORD_LIST *)NULL;
dispose_words (list);
if (val != value)
free (val);
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_subscript_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 ((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 (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] == '[')
{
/* 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] != '=')))
{
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, 0);
else
array_insert (a, last_ind, nval);
FREE (nval);
last_ind++;
continue;
}
if (len == 1)
{
err_badarraysub (w);
continue;
}
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;
}
if (array_p (var))
{
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;
}
}
/* 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 `=' */
{
ind = last_ind;
val = w;
}
free_val = 0;
/* See above; we need to expand the value here */
if (assoc_p (var))
{
val = expand_subscript_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 */
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
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_subscript_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
quote_array_assignment_chars (list)
WORD_LIST *list;
{
char *nword;
WORD_LIST *l;
for (l = list; l; l = l->next)
{
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;
nword = quote_assign (l->word->word);
free (l->word->word);
l->word->word = nword;
l->word->flags |= W_NOGLOB; /* XXX - W_NOSPLIT also? */
}
}
/* 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, flags)
SHELL_VAR *var;
char *sub;
int flags;
{
arrayind_t ind;
char *akey;
ARRAY_ELEMENT *ae;
/* 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)
{
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 = (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 if (array_p (var))
{
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);
}
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;
}
/* Format and output an array assignment in compound form VAR=(VALUES),
suitable for re-use as input. */
void
print_array_assignment (var, quoted)
SHELL_VAR *var;
int quoted;
{
char *vstr;
vstr = array_to_assign (array_cell (var), quoted);
if (vstr == 0)
printf ("%s=%s\n", var->name, quoted ? "'()'" : "()");
else
{
printf ("%s=%s\n", var->name, vstr);
free (vstr);
}
}
/* 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 */
/* */
/***********************************************************************/
/* 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
tokenize_array_reference (name, flags, subp)
char *name;
int flags;
char **subp;
{
char *t;
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;
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)
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 (var, s, len, flags)
SHELL_VAR *var;
char *s;
int len;
int flags;
{
char *exp, *t, *savecmd;
int expok, eflag;
arrayind_t val;
exp = (char *)xmalloc (len);
strncpy (exp, s, len - 1);
exp[len - 1] = '\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;
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)
{
set_exit_status (EXECUTION_FAILURE);
if (no_longjmp_on_fatal_error)
return 0;
top_level_cleanup ();
jump_to_top_level (DISCARD);
}
return val;
}
/* Return the name of the variable specified by S without any subscript.
If SUBP is 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. This returns newly-allocated memory. */
char *
array_variable_name (s, flags, subp, lenp)
const char *s;
int flags;
char **subp;
int *lenp;
{
char *t, *ret;
int ind, ni, ssflags;
t = mbschr (s, '[');
if (t == 0)
{
if (subp)
*subp = t;
if (lenp)
*lenp = 0;
return ((char *)NULL);
}
ind = t - s;
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);
if (subp)
*subp = t;
if (lenp)
*lenp = 0;
return ((char *)NULL);
}
*t = '\0';
ret = savestring (s);
*t++ = '['; /* ] */
if (subp)
*subp = t;
if (lenp)
*lenp = ni - ind;
return ret;
}
/* Return the variable specified by S without any subscript. If SUBP is
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, flags, subp, lenp)
const char *s;
int flags;
char **subp;
int *lenp;
{
char *t;
SHELL_VAR *var;
t = array_variable_name (s, flags, subp, lenp);
if (t == 0)
return ((SHELL_VAR *)NULL);
var = find_variable (t); /* XXX - handle namerefs here? */
free (t);
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, flags, estatep)
const char *s;
int quoted, flags;
array_eltstate_t *estatep;
{
int len, isassoc, subtype;
arrayind_t ind;
char *akey;
char *retval, *t, *temp;
WORD_LIST *l;
SHELL_VAR *var;
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 0
if (var == 0)
return (char *)NULL;
#endif
if (len == 0)
return ((char *)NULL); /* error message already printed */
isassoc = var && assoc_p (var);
/* [ */
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 (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)
{
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, 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, (flags & AV_ASSIGNRHS) ? PF_ASSIGNRHS : 0);
dispose_words (l);
}
else
{
if (estatep)
estatep->subtype = 0;
if (var == 0 || array_p (var) || assoc_p (var) == 0)
{
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)
{
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 (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;
}
/* Return a string containing the elements described by the array and
subscript contained in S, obeying quoting for subscripts * and @. */
char *
array_value (s, quoted, flags, estatep)
const char *s;
int quoted, flags;
array_eltstate_t *estatep;
{
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 (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, flags, estatep)
const char *s;
int flags;
array_eltstate_t *estatep;
{
char *retval;
retval = array_value_internal (s, 0, flags, estatep);
return retval;
}
char *
array_keys (s, quoted, pflags)
char *s;
int quoted, pflags;
{
int len;
char *retval, *t, *temp;
WORD_LIST *l;
SHELL_VAR *var;
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))
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);
retval = string_list_pos_params (t[0], l, quoted, pflags);
dispose_words (l);
return retval;
}
#endif /* ARRAY_VARS */