/* 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 */