Question: Bash associative array ordering

Question

Bash associative array ordering

Answers 2
Added at 2016-12-19 21:12
Tags
Question

If I wanted to pass the keys and values of an associative array in bash separately, and used something like

./foo.py -k "${!args[@]}" -v "${args[@]}"

would they come out in the same order? I don't really care what other the k=v pairs are stored in, but I do need to know if I could count on the keys and values coming out such that the 3rd item in the key array is in fact the key for the 3rd item in the value array.

I know that associative arrays are "unordered" and that whatever order you add them to the array is irrelevant to how they're output, but I'm wondering if the underlying storage behavior means they will always output in the same order.

Answers to

Bash associative array ordering

nr: #1 dodano: 2016-12-20 01:12

Arrays are not the solution to this problem, especially not associative arrays. Even if they did come out in the same order, you would have multiple keys for a single -k option, resulting in syntax error. Also arrays are a Bashism, and not defined by POSIX. A better solution would be something like this:

./foo.py -k key1,key2,key3 -v val1,val2,val3

Then surely Python can split the input strings? I did something similar with POSIX shell:

tr , '\n' > keys <<eof
$1
eof
tr , '\n' > vals <<eof
$2
eof
paste -d '\n' keys values |
while
  read key
  read val
do
  printf 'key: %s, val: %s\n' "$key" "$val"
done

Of course this would be much easier with Python, as you could just split the strings directly into arrays without using files.

nr: #2 dodano: 2016-12-20 17:12

It appears that the answer is yes, the keys and values will always be in the same order, based on the code I found in Bash version 4.3, assoc.c, available here. The keys and values of the array are retrieved by the functions assoc_keys_to_word_list and assoc_to_word_list, respectively. Both of these functions delegate to assoc_to_word_list_internal, which runs the same loop in both cases, and only differentiates the type of item being retreived based on the t parameter (lines 482-503):

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 *));
}

In case you are wondering, make_word_list is defined in array.c/h. It just appends a new WORD_LIST node to the existing linked list.

While this provides no contractual guarantee that the behavior you expect will always be supported, it is a pretty good indication that you can use your calling convention safely, at least for now. The fact that associative arrays are Bash-only makes the implementation more of a valid reference.

Source Show
◀ Wstecz