mirror of https://github.com/n-hys/bash.git
588 lines
12 KiB
C
588 lines
12 KiB
C
/*
|
|
* 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-2020 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#if defined (ARRAY_VARS)
|
|
|
|
#if defined (HAVE_UNISTD_H)
|
|
# ifdef _MINIX
|
|
# include <sys/types.h>
|
|
# endif
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include "bashansi.h"
|
|
|
|
#include "shell.h"
|
|
#include "array.h"
|
|
#include "assoc.h"
|
|
#include "builtins/common.h"
|
|
|
|
static WORD_LIST *assoc_to_word_list_internal PARAMS((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, pflags)
|
|
HASH_TABLE *hash;
|
|
arrayind_t start, nelem;
|
|
int starsub, quoted, pflags;
|
|
{
|
|
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)
|
|
{
|
|
dispose_words (save);
|
|
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, pflags);
|
|
|
|
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;
|
|
{
|
|
char *t;
|
|
int pchar, qflags, pflags;
|
|
WORD_LIST *wl, *save;
|
|
|
|
if (h == 0 || assoc_empty (h))
|
|
return ((char *)NULL);
|
|
|
|
wl = assoc_to_word_list (h);
|
|
if (wl == 0)
|
|
return (char *)NULL;
|
|
|
|
for (save = wl; wl; wl = wl->next)
|
|
{
|
|
t = pat_subst (wl->word->word, pat, rep, mflags);
|
|
FREE (wl->word->word);
|
|
wl->word->word = t;
|
|
}
|
|
|
|
pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
|
|
qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
|
|
pflags = (mflags & MATCH_ASSIGNRHS) == MATCH_ASSIGNRHS ? PF_ASSIGNRHS : 0;
|
|
|
|
t = string_list_pos_params (pchar, save, qflags, pflags);
|
|
dispose_words (save);
|
|
|
|
return t;
|
|
}
|
|
|
|
char *
|
|
assoc_modcase (h, pat, modop, mflags)
|
|
HASH_TABLE *h;
|
|
char *pat;
|
|
int modop;
|
|
int mflags;
|
|
{
|
|
char *t;
|
|
int pchar, qflags, pflags;
|
|
WORD_LIST *wl, *save;
|
|
|
|
if (h == 0 || assoc_empty (h))
|
|
return ((char *)NULL);
|
|
|
|
wl = assoc_to_word_list (h);
|
|
if (wl == 0)
|
|
return ((char *)NULL);
|
|
|
|
for (save = wl; wl; wl = wl->next)
|
|
{
|
|
t = sh_modcase (wl->word->word, pat, modop);
|
|
FREE (wl->word->word);
|
|
wl->word->word = t;
|
|
}
|
|
|
|
pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
|
|
qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
|
|
pflags = (mflags & MATCH_ASSIGNRHS) == MATCH_ASSIGNRHS ? PF_ASSIGNRHS : 0;
|
|
|
|
t = string_list_pos_params (pchar, save, qflags, pflags);
|
|
dispose_words (save);
|
|
|
|
return t;
|
|
}
|
|
|
|
char *
|
|
assoc_to_kvpair (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[rlen = 0] = '\0';
|
|
|
|
for (i = 0; i < hash->nbuckets; i++)
|
|
for (tlist = hash_items (i, hash); tlist; tlist = tlist->next)
|
|
{
|
|
if (ansic_shouldquote (tlist->key))
|
|
istr = ansic_quote (tlist->key, 0, (int *)0);
|
|
else 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;
|
|
|
|
vstr = tlist->data ? (ansic_shouldquote ((char *)tlist->data) ?
|
|
ansic_quote ((char *)tlist->data, 0, (int *)0) :
|
|
sh_double_quote ((char *)tlist->data))
|
|
: (char *)0;
|
|
|
|
elen = STRLEN (istr) + 4 + STRLEN (vstr);
|
|
RESIZE_MALLOCED_BUFFER (ret, rlen, (elen+1), rsize, rsize);
|
|
|
|
strcpy (ret+rlen, istr);
|
|
rlen += STRLEN (istr);
|
|
ret[rlen++] = ' ';
|
|
if (vstr)
|
|
{
|
|
strcpy (ret + rlen, vstr);
|
|
rlen += STRLEN (vstr);
|
|
}
|
|
else
|
|
{
|
|
strcpy (ret + rlen, "\"\"");
|
|
rlen += 2;
|
|
}
|
|
ret[rlen++] = ' ';
|
|
|
|
if (istr != tlist->key)
|
|
FREE (istr);
|
|
|
|
FREE (vstr);
|
|
}
|
|
|
|
RESIZE_MALLOCED_BUFFER (ret, rlen, 1, rsize, 8);
|
|
ret[rlen] = '\0';
|
|
|
|
if (quoted)
|
|
{
|
|
vstr = sh_single_quote (ret);
|
|
free (ret);
|
|
ret = vstr;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
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 (ansic_shouldquote (tlist->key))
|
|
istr = ansic_quote (tlist->key, 0, (int *)0);
|
|
else 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;
|
|
|
|
vstr = tlist->data ? (ansic_shouldquote ((char *)tlist->data) ?
|
|
ansic_quote ((char *)tlist->data, 0, (int *)0) :
|
|
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 */
|