/* * assoc.c - functions to manipulate associative arrays * * Associative arrays are standard shell hash tables. * * Chet Ramey * chet@ins.cwru.edu */ /* Copyright (C) 2008,2009,2011 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) # ifdef _MINIX # include # endif # include #endif #include #include "bashansi.h" #include "shell.h" #include "array.h" #include "assoc.h" #include "builtins/common.h" static WORD_LIST *assoc_to_word_list_internal __P((HASH_TABLE *, int)); /* assoc_create == hash_create */ void assoc_dispose (hash) HASH_TABLE *hash; { if (hash) { hash_flush (hash, 0); hash_dispose (hash); } } void assoc_flush (hash) HASH_TABLE *hash; { hash_flush (hash, 0); } int assoc_insert (hash, key, value) HASH_TABLE *hash; char *key; char *value; { BUCKET_CONTENTS *b; b = hash_search (key, hash, HASH_CREATE); if (b == 0) return -1; /* If we are overwriting an existing element's value, we're not going to use the key. Nothing in the array assignment code path frees the key string, so we can free it here to avoid a memory leak. */ if (b->key != key) free (key); FREE (b->data); b->data = value ? savestring (value) : (char *)0; return (0); } /* Like assoc_insert, but returns b->data instead of freeing it */ PTR_T assoc_replace (hash, key, value) HASH_TABLE *hash; char *key; char *value; { BUCKET_CONTENTS *b; PTR_T t; b = hash_search (key, hash, HASH_CREATE); if (b == 0) return (PTR_T)0; /* If we are overwriting an existing element's value, we're not going to use the key. Nothing in the array assignment code path frees the key string, so we can free it here to avoid a memory leak. */ if (b->key != key) free (key); t = b->data; b->data = value ? savestring (value) : (char *)0; return t; } void assoc_remove (hash, string) HASH_TABLE *hash; char *string; { BUCKET_CONTENTS *b; b = hash_remove (string, hash, 0); if (b) { free ((char *)b->data); free (b->key); free (b); } } char * assoc_reference (hash, string) HASH_TABLE *hash; char *string; { BUCKET_CONTENTS *b; if (hash == 0) return (char *)0; b = hash_search (string, hash, 0); return (b ? (char *)b->data : 0); } /* Quote the data associated with each element of the hash table ASSOC, using quote_string */ HASH_TABLE * assoc_quote (h) HASH_TABLE *h; { int i; BUCKET_CONTENTS *tlist; char *t; if (h == 0 || assoc_empty (h)) return ((HASH_TABLE *)NULL); for (i = 0; i < h->nbuckets; i++) for (tlist = hash_items (i, h); tlist; tlist = tlist->next) { t = quote_string ((char *)tlist->data); FREE (tlist->data); tlist->data = t; } return h; } /* Quote escape characters in the data associated with each element of the hash table ASSOC, using quote_escapes */ HASH_TABLE * assoc_quote_escapes (h) HASH_TABLE *h; { int i; BUCKET_CONTENTS *tlist; char *t; if (h == 0 || assoc_empty (h)) return ((HASH_TABLE *)NULL); for (i = 0; i < h->nbuckets; i++) for (tlist = hash_items (i, h); tlist; tlist = tlist->next) { t = quote_escapes ((char *)tlist->data); FREE (tlist->data); tlist->data = t; } return h; } HASH_TABLE * assoc_dequote (h) HASH_TABLE *h; { int i; BUCKET_CONTENTS *tlist; char *t; if (h == 0 || assoc_empty (h)) return ((HASH_TABLE *)NULL); for (i = 0; i < h->nbuckets; i++) for (tlist = hash_items (i, h); tlist; tlist = tlist->next) { t = dequote_string ((char *)tlist->data); FREE (tlist->data); tlist->data = t; } return h; } HASH_TABLE * assoc_dequote_escapes (h) HASH_TABLE *h; { int i; BUCKET_CONTENTS *tlist; char *t; if (h == 0 || assoc_empty (h)) return ((HASH_TABLE *)NULL); for (i = 0; i < h->nbuckets; i++) for (tlist = hash_items (i, h); tlist; tlist = tlist->next) { t = dequote_escapes ((char *)tlist->data); FREE (tlist->data); tlist->data = t; } return h; } HASH_TABLE * assoc_remove_quoted_nulls (h) HASH_TABLE *h; { int i; BUCKET_CONTENTS *tlist; char *t; if (h == 0 || assoc_empty (h)) return ((HASH_TABLE *)NULL); for (i = 0; i < h->nbuckets; i++) for (tlist = hash_items (i, h); tlist; tlist = tlist->next) { t = remove_quoted_nulls ((char *)tlist->data); tlist->data = t; } return h; } /* * Return a string whose elements are the members of array H beginning at * the STARTth element and spanning NELEM members. Null elements are counted. */ char * assoc_subrange (hash, start, nelem, starsub, quoted) HASH_TABLE *hash; arrayind_t start, nelem; int starsub, quoted; { WORD_LIST *l, *save, *h, *t; int i, j; char *ret; if (assoc_empty (hash)) return ((char *)NULL); save = l = assoc_to_word_list (hash); if (save == 0) return ((char *)NULL); for (i = 1; l && i < start; i++) l = l->next; if (l == 0) return ((char *)NULL); for (j = 0,h = t = l; l && j < nelem; j++) { t = l; l = l->next; } t->next = (WORD_LIST *)NULL; ret = string_list_pos_params (starsub ? '*' : '@', h, quoted); if (t != l) t->next = l; dispose_words (save); return (ret); } char * assoc_patsub (h, pat, rep, mflags) HASH_TABLE *h; char *pat, *rep; int mflags; { BUCKET_CONTENTS *tlist; int i, slen; HASH_TABLE *h2; char *t, *sifs, *ifs; if (h == 0 || assoc_empty (h)) return ((char *)NULL); h2 = assoc_copy (h); for (i = 0; i < h2->nbuckets; i++) for (tlist = hash_items (i, h2); tlist; tlist = tlist->next) { t = pat_subst ((char *)tlist->data, pat, rep, mflags); FREE (tlist->data); tlist->data = t; } if (mflags & MATCH_QUOTED) assoc_quote (h2); else assoc_quote_escapes (h2); if (mflags & MATCH_STARSUB) { assoc_remove_quoted_nulls (h2); sifs = ifs_firstchar ((int *)NULL); t = assoc_to_string (h2, sifs, 0); free (sifs); } else if (mflags & MATCH_QUOTED) { /* ${array[@]} */ sifs = ifs_firstchar (&slen); ifs = getifs (); if (ifs == 0 || *ifs == 0) { if (slen < 2) sifs = xrealloc (sifs, 2); sifs[0] = ' '; sifs[1] = '\0'; } t = assoc_to_string (h2, sifs, 0); free(sifs); } else t = assoc_to_string (h2, " ", 0); assoc_dispose (h2); return t; } char * assoc_modcase (h, pat, modop, mflags) HASH_TABLE *h; char *pat; int modop; int mflags; { BUCKET_CONTENTS *tlist; int i, slen; HASH_TABLE *h2; char *t, *sifs, *ifs; if (h == 0 || assoc_empty (h)) return ((char *)NULL); h2 = assoc_copy (h); for (i = 0; i < h2->nbuckets; i++) for (tlist = hash_items (i, h2); tlist; tlist = tlist->next) { t = sh_modcase ((char *)tlist->data, pat, modop); FREE (tlist->data); tlist->data = t; } if (mflags & MATCH_QUOTED) assoc_quote (h2); else assoc_quote_escapes (h2); if (mflags & MATCH_STARSUB) { assoc_remove_quoted_nulls (h2); sifs = ifs_firstchar ((int *)NULL); t = assoc_to_string (h2, sifs, 0); free (sifs); } else if (mflags & MATCH_QUOTED) { /* ${array[@]} */ sifs = ifs_firstchar (&slen); ifs = getifs (); if (ifs == 0 || *ifs == 0) { if (slen < 2) sifs = xrealloc (sifs, 2); sifs[0] = ' '; sifs[1] = '\0'; } t = assoc_to_string (h2, sifs, 0); free(sifs); } else t = assoc_to_string (h2, " ", 0); assoc_dispose (h2); return t; } char * assoc_to_assign (hash, quoted) HASH_TABLE *hash; int quoted; { char *ret; char *istr, *vstr; int i, rsize, rlen, elen; BUCKET_CONTENTS *tlist; if (hash == 0 || assoc_empty (hash)) return (char *)0; ret = xmalloc (rsize = 128); ret[0] = '('; rlen = 1; for (i = 0; i < hash->nbuckets; i++) for (tlist = hash_items (i, hash); tlist; tlist = tlist->next) { #if 1 if (sh_contains_shell_metas (tlist->key)) istr = sh_double_quote (tlist->key); else if (ALL_ELEMENT_SUB (tlist->key[0]) && tlist->key[1] == '\0') istr = sh_double_quote (tlist->key); else istr = tlist->key; #else istr = tlist->key; #endif vstr = tlist->data ? sh_double_quote ((char *)tlist->data) : (char *)0; elen = STRLEN (istr) + 8 + STRLEN (vstr); RESIZE_MALLOCED_BUFFER (ret, rlen, (elen+1), rsize, rsize); ret[rlen++] = '['; strcpy (ret+rlen, istr); rlen += STRLEN (istr); ret[rlen++] = ']'; ret[rlen++] = '='; if (vstr) { strcpy (ret + rlen, vstr); rlen += STRLEN (vstr); } ret[rlen++] = ' '; if (istr != tlist->key) FREE (istr); FREE (vstr); } RESIZE_MALLOCED_BUFFER (ret, rlen, 1, rsize, 8); ret[rlen++] = ')'; ret[rlen] = '\0'; if (quoted) { vstr = sh_single_quote (ret); free (ret); ret = vstr; } return ret; } static WORD_LIST * assoc_to_word_list_internal (h, t) HASH_TABLE *h; int t; { WORD_LIST *list; int i; BUCKET_CONTENTS *tlist; char *w; if (h == 0 || assoc_empty (h)) return((WORD_LIST *)NULL); list = (WORD_LIST *)NULL; for (i = 0; i < h->nbuckets; i++) for (tlist = hash_items (i, h); tlist; tlist = tlist->next) { w = (t == 0) ? (char *)tlist->data : (char *)tlist->key; list = make_word_list (make_bare_word(w), list); } return (REVERSE_LIST(list, WORD_LIST *)); } WORD_LIST * assoc_to_word_list (h) HASH_TABLE *h; { return (assoc_to_word_list_internal (h, 0)); } WORD_LIST * assoc_keys_to_word_list (h) HASH_TABLE *h; { return (assoc_to_word_list_internal (h, 1)); } char * assoc_to_string (h, sep, quoted) HASH_TABLE *h; char *sep; int quoted; { BUCKET_CONTENTS *tlist; int i; char *result, *t, *w; WORD_LIST *list, *l; if (h == 0) return ((char *)NULL); if (assoc_empty (h)) return (savestring ("")); result = NULL; l = list = NULL; /* This might be better implemented directly, but it's simple to implement by converting to a word list first, possibly quoting the data, then using list_string */ for (i = 0; i < h->nbuckets; i++) for (tlist = hash_items (i, h); tlist; tlist = tlist->next) { w = (char *)tlist->data; if (w == 0) continue; t = quoted ? quote_string (w) : savestring (w); list = make_word_list (make_bare_word(t), list); FREE (t); } l = REVERSE_LIST(list, WORD_LIST *); result = l ? string_list_internal (l, sep) : savestring (""); dispose_words (l); return result; } #endif /* ARRAY_VARS */