changeset 534:46c4798b9b31

farray.sh: new implementation of arrays and alists. This implementation allows the main array and alist variables to be "local".
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 09 Sep 2024 12:33:00 +0200
parents 07071afd9ae5
children 33b55434d039
files share/local-bsdtools/farray.sh
diffstat 1 files changed, 1595 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/local-bsdtools/farray.sh	Mon Sep 09 12:33:00 2024 +0200
@@ -0,0 +1,1595 @@
+#!/bin/sh
+# -*- indent-tabs-mode: nil; -*-
+#:
+#: A simple library to emulate simple arrays (one-dimensional) and alists
+#: in a POSIX shell.
+#:
+#: :Author:    Franz Glasner
+#: :Copyright: (c) 2024 Franz Glasner.
+#:             All rights reserved.
+#: :License:   BSD 3-Clause "New" or "Revised" License.
+#:             See LICENSE for details.
+#:             If you cannot find LICENSE see
+#:             <https://opensource.org/licenses/BSD-3-Clause>
+#: :ID:        @(#)@@SIMPLEVERSIONTAG@@
+#:
+#: This implementation is somewhat inspired by
+#: https://unix.stackexchange.com/questions/137566/arrays-in-unix-bourne-shell
+#:
+#: Is implements one-dimensional array with one-based indexing.
+#:
+#: Hints and rules for arrays:
+#:
+#: - Every array has a NAME.
+#: - One-based indexing is used.
+#: - The array with name NAME is associated with a primary global or local
+#:   shell variable with the same name. It contains a token TOKEN that is
+#:   used in variable names that make up the backing store of the array.
+#: - Array elements for array NAME that has associated TOKEN are stored in
+#:   global variables named ``_farr_A_<TOKEN>_<index-number>`` (values)
+#:   and ``_farr_A_<TOKEN>__`` (length).
+#: - An array name must conform to shell variable naming conventions.
+#: - Currently the number of of elements of an array must be >= 0.
+#: - Variable NAME can also be a local variable. In this case it MUST
+#:   be initialized with an empty value first.
+#: - Any unset global variables ``_farr_A_<TOKEN>__`` variable is a severe
+#:   error normally and forces an immediate error ``exit``.
+#:   Exceptions to this rule are documented.
+#:
+#:
+#: This module also contains a very rough implementation of an alist
+#: (aka dict, aka map).
+#: This is implemented somewhat similar to arrays.
+#:
+#: Hints and rules for alists:
+#:
+#: - Every alist has a NAME.
+#: - One-based indexing is used internally.
+#: - The alist with name NAME is associated with a primary global or local
+#:   shell variable with the same name. It contains a token TOKEN that is
+#:   used in variable names that make up the backing stores of the alist.
+#: - The length of the alist is stored in a global ``_farr_KV_<token>__``.
+#: - Every key-value pair is stored at an INDEX.
+#: - Keys are stored in an associated global ``_farr_K_<TOKEN>_<INDEX>``.
+#: - Values are stored in an associated global ``_farr_V_<TOKEN>_<INDEX>``.
+#: - An alist name must conform to shell variable naming conventions.
+#: - Currently the number of of elements of an alist must be >= 0.
+#: - Variable NAME can also be a local variable. In this case it MUST
+#:   be initialized with an empty value first.
+#: - Any unset global variables ``_farr_KV_<TOKEN>__`` variable is a severe
+#:   error normally and forces an immediate fatal error exit by calling
+#:   ``exit``.
+#: - The same is true if ``_farr_K_<TOKEN>_<INDEX>`` or
+#:   ``_farr_V_<TOKEN>_<INDEX>`` is unexpectedly unset.
+#:   Exceptions to this rule are documented.
+#:
+#: Important:
+#:   All names that start with ``_farr_`` or ``__farr_`` are reserved
+#:   for private use in this module.
+#:   Do not use such names in your scripts.
+#:
+#:   Public functions start with ``farray_`` or ``falist_``.
+#:
+
+
+_farr_array_prefix=_farr_A_
+_farr_alist_prefix=_farr_KV_
+_farr_alist_key_prefix=_farr_K_
+_farr_alist_value_prefix=_farr_V_
+
+
+#:
+#: Internal error for fatal errors.
+#:
+#: Args:
+#:   $@ (str): The error messages.
+#:
+_farr_fatal() {
+    echo "ERROR:" "$@" 1>&2
+    exit 70    # EX_SOFTWARE
+}
+
+
+:
+#: Internal error for other error message.
+#:
+#: Args:
+#:   $@ (str): The error messages.
+#:
+_farr_err() {
+    echo "ERROR:" "$@" 1>&2
+}
+
+
+#:
+#: Quote the given input to be safely used in evals with "Dollar-Single Quotes"
+#:
+#: Args:
+#:   $1: The value to be quoted.
+#:
+#:
+#: From FreeBSD's :manpage:`sh(1)`:
+#:
+#:   Dollar-Single Quotes
+#:
+#:     Enclosing characters between ``$'`` and ``'`` preserves the literal
+#:     meaning of all characters except backslashes and single quotes.
+#:
+#:     A backslash introduces a C-style escape sequence.
+#:     Most important here:
+#:
+#:       ``\\``
+#:                Literal backslash
+#:
+#:       ``\'``
+#:                Literal single-quote
+#:
+_farr_quote_for_eval_dsq() {
+    printf "%s" "${1}" \
+        | LC_ALL=C /usr/bin/sed -e $'s/\\\\/\\\\\\\\/g' -e $'s/\'/\\\\\'/g'
+    #                              escape a backslash      escape a single quote
+    # '    # make Emacs happy for correct syntax highlighting
+}
+
+
+#:
+#: Create a new array.
+#:
+#: It is assumed that the array does not exist already.
+#:
+#: Args:
+#:   $1 (str): The name of the array.
+#:             Must conform to shell variable naming conventions.
+#:   $2... (optional): Initialization values that will be appended to the
+#:                     freshly created array.
+#:
+#: Exit:
+#:   Iff the array already exists in some fashion (token and/or storage).
+#:
+farray_create() {
+    local __farr_name
+
+    local __farr_token __farr_gvrname __farr_len
+    local __farr_el
+
+    __farr_name="${1-}"
+    [ -z "${__farr_name}" ] && _farr_fatal "missing farray name"
+    eval __farr_token=\"\${${__farr_name}-}\"
+    [ -n "${__farr_token}" ] && _farr_fatal "object \`${__farr_name}' already created (token \`${__farr_token}')"
+
+    __farr_token="$(/usr/bin/hexdump -v -e '/1 "%02x"' -n 16 '/dev/urandom')"
+
+    __farr_gvrname=${_farr_array_prefix}${__farr_token}
+    shift
+
+    # Check whether the variable already exists
+    eval __farr_len=\${${__farr_gvrname}__+SET}
+    [ -n "${__farr_len}" ] && _farr_fatal "farray \`${__farr_name}' already exists: existing token \`${__farr_token}'"
+
+    # Really create the storage by initializing its length
+    eval ${__farr_gvrname}__=0
+    # And associate the token with the array name
+    eval ${__farr_name}="${__farr_token}"
+
+    for __farr_el in "$@"; do
+	farray_append ${__farr_name} "${__farr_el}"
+    done
+}
+
+
+#:
+#: Internal helper to get all the metadata for an array.
+#:
+#: Args:
+#:   $1 (str): The name of the array
+#:
+#: Output (Globals):
+#:   __farr_name (str): The name of the array.
+#:   __farr_token (str): The token that is the value of the name.
+#:   __farr_gvrname (str): The variable prefix for all items.
+#:   __farr_len (int): The length of the array.
+#:
+#: Exit:
+#:   Iff the array does not exist in some fashion (token and/or storage).
+#:
+_farr_array_get_meta() {
+    __farr_name="${1-}"
+    [ -z "${__farr_name}" ] && _farr_fatal "missing farray name"
+    eval __farr_token=\"\${${__farr_name}-}\"
+    [ -z "${__farr_token}" ] && _farr_fatal "farray \`${__farr_name}' does not exist: token empty"
+    __farr_gvrname="${_farr_array_prefix}${__farr_token}"
+
+    eval __farr_len=\${${__farr_gvrname}__:+SET}
+    [ -z "${__farr_len}" ] && _farr_fatal "farray \`${__farr_name}' does not exist: no storage for token \`${__farr_token}'"
+    eval __farr_len="\$((\${${__farr_gvrname}__} + 0))"
+    return 0
+}
+
+
+#:
+#: Internal helper to try to get all the metadata for an array.
+#:
+#: Args:
+#:   $1 (str): The name of the array
+#:
+#: Output (Globals):
+#:   __farr_name (str): The name of the array.
+#:   __farr_token (str): The token that is the value of the name.
+#:   __farr_gvrname (str): The variable prefix for all items.
+#:   __farr_len (int): The length of the array
+#:
+#: Returns:
+#:   0 if the array exists, 1 if something is missing.
+#:
+#: Exit:
+#:   Iff the array name is not given
+#:
+_farr_array_tryget_meta() {
+    __farr_name="${1-}"
+    [ -z "${__farr_name}" ] && _farr_fatal "missing farray name"
+    __farr_token=""
+    __farr_gvrname=""
+    __farr_len=""
+    eval __farr_token=\"\${${__farr_name}-}\"
+    if [ -z "${__farr_token}" ]; then
+        _farr_err "farray \`${__farr_name}' does not exist: token empty"
+        return 1;
+    fi
+    __farr_gvrname="${_farr_array_prefix}${__farr_token}"
+
+    eval __farr_len=\${${__farr_gvrname}__:+SET}
+    if [ -z "${__farr_len}" ]; then
+        _farr_err "farray \`${__farr_name}' does not exist: no storage for token \`${__farr_token}'"
+        return 1
+    fi
+    eval __farr_len="\$((\${${__farr_gvrname}__} + 0))"
+    return 0
+}
+
+
+#:
+#: Get the length of an array and put it into a variable
+#:
+#: Args:
+#:   $1 (str): The name of the variable to put the length into.
+#:   $2 (str): The name of the array.
+#:
+farray_length() {
+    local __farr_varname __farr_name
+
+    local __farr_token __farr_gvrname __farr_len
+
+    __farr_varname="${1-}"
+    [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
+    shift
+    _farr_array_get_meta "$@"
+    eval ${__farr_varname}=${__farr_len}
+}
+
+
+#:
+#: Get the length of an array and print it to stdout.
+#:
+#: Args:
+#:   $1 (str): The name of the array.
+#:
+#: Output (stdout):
+#:   The number of elements of the array.
+#:   If the array does not exist the output is -1.
+#:
+#: Returns:
+#:   0 (truthy)
+#:
+farray_print_length() {
+    local __farr_vn
+
+    ( farray_length __farr_vn "$@" && printf "%s" "${__farr_vn}"; ) \
+    || printf "%s" "-1"
+}
+
+
+#:
+#: Append a value to an existing array.
+#:
+#: Args:
+#:   $1 (str): The name of the existing array.
+#:   $2 (optional): The value to append. If the value is not given the null
+#:                  value will be appended.
+#:
+farray_append() {
+    local __farr_name __farr_value
+
+    local __farr_token __farr_gvrname __farr_len __farr_len_1
+
+    _farr_array_get_meta "$@"
+
+    __farr_value="${2-}"
+
+    __farr_len_1=$((${__farr_len} + 1))
+
+    #
+    # Set value
+    # Escape properly: use $' ' and escape any backslashes and single quotes.
+    #
+    eval ${__farr_gvrname}_${__farr_len_1}=\$\'"$(_farr_quote_for_eval_dsq "${__farr_value}")"\'
+    # the implementation below line does not escape properly
+    #   eval ${__farr_gvrname}_${__farr_len_1}="\"${__farr_value}\""
+
+    # Set new array length
+    eval ${__farr_gvrname}__=${__farr_len_1}
+}
+
+
+#:
+#: Set an array at an index to a value.
+#:
+#: Args:
+#:   $1 (str): The name of the existing array.
+#:   $2 (int): The index.
+#:   $3 (optional): The value to set. If the value is not given the null
+#:                  will be appended.
+#:
+#: No holes are allowed in an array: only values at existing indices are
+#: allowed. As an exception a value can be appended if the given index
+#: is exactly the current length + 1.
+#:
+farray_set() {
+    local __farr_name __farr_index __farr_value
+
+    local __farr_token __farr_gvrname __farr_len __farr_len_1
+
+    _farr_array_get_meta "$@"
+    __farr_index="${2-}"
+    [ -z "${__farr_index}" ] && _farr_fatal "no valid index for set given"
+    # make it to a number
+    __farr_index=$((${__farr_index} + 0))
+    __farr_value="${3-}"
+
+    # For proper quoting: see farray_append
+    if [ \( ${__farr_index} -ge 1 \) -a \( ${__farr_index} -le ${__farr_len} \) ]; then
+        # replace a value at an existing index
+        eval ${__farr_gvrname}_${__farr_index}=\$\'"$(_farr_quote_for_eval_dsq "${__farr_value}")"\'
+    else
+        __farr_len_1=$((${__farr_len} + 1))
+        if [ ${__farr_index} -eq ${__farr_len_1} ]; then
+            # append value
+            eval ${__farr_gvrname}_${__farr_len_1}=\$\'"$(_farr_quote_for_eval_dsq "${__farr_value}")"\'
+            # and set new length
+            eval ${__farr_gvrname}__=${__farr_len_1}
+        else
+            _farr_fatal "array index out of bounds (cannot create holes)"
+        fi
+    fi
+}
+
+
+#:
+#: Get an array value from a given index and put it into given variable.
+#:
+#: Args:
+#:   $1 (str): The name of the variable to put the value into.
+#:   $2 (str): The name of the existing array.
+#:   $3 (int): The index.
+#:
+farray_get() {
+    local __farr_varname __farr_name __farr_index
+
+    local __farr_token __farr_gvrname __farr_len
+
+    __farr_varname="${1-}"
+    [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
+    shift
+    _farr_array_get_meta "$@"
+    __farr_index="${2-}"
+    [ -z "${__farr_index}" ] && _farr_fatal "no valid index given"
+    # make it to a number
+    __farr_index=$((${__farr_index} + 0))
+
+    # check index range
+    if [ \( "${__farr_index}" -lt 1 \) -o \( "${__farr_index}" -gt ${__farr_len} \) ]; then
+	_farr_fatal "array index out of bounds"
+    fi
+
+    eval ${__farr_varname}=\"\${${__farr_gvrname}_${__farr_index}}\"
+}
+
+
+#:
+#: Try to get an array value from a given index into a variable
+#:
+#: Args:
+#:   $1 (str): The name of the variable to put the value into.
+#:   $2 (str): The name of the existing array.
+#:   $3 (int): The index.
+#:
+#: Returns:
+#:   0 (truthy) on success,
+#:   1 (falsy) if the given index is out of bounds (i.e. EOD).
+#:
+#: Exit:
+#:   Other errors (missing array name, missing index value) are considered
+#:   fatal and call `_farr_fatal` (i.e. `exit`).
+#:
+farray_tryget() {
+    local __farr_varname __farr_name __farr_index
+
+    local __farr_token __farr_gvrname __farr_len
+
+    __farr_varname="${1-}"
+    [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
+    shift
+
+    _farr_array_get_meta "$@"
+    __farr_index="${2-}"
+    [ -z "${__farr_index}" ] && _farr_fatal "no valid index given"
+    # make it to a number
+    __farr_index=$((${__farr_index} + 0))
+
+    # check index range
+    if [ \( "${__farr_index}" -lt 1 \) -o \( "${__farr_index}" -gt ${__farr_len} \) ]; then
+	return 1
+    fi
+
+    eval ${__farr_varname}=\"\${${__farr_gvrname}_${__farr_index}}\"
+    return 0
+}
+
+
+#:
+#: Delete an array value at a given index.
+#:
+#: Args:
+#:   $1 (str): The name of the existing array.
+#:   $2 (int): The index to delete to.
+#:
+farray_del() {
+    local __farr_name __farr_index
+
+    local __farr_token __farr_gvrname __farr_len
+    local __farr_new_len __farr_idx __farr_idx_1 __farr_value
+
+    _farr_array_get_meta "$@"
+    __farr_index="${2-}"
+    [ -z "${__farr_index}" ] && _farr_fatal "no valid index given"
+    # make it to a number
+    __farr_index=$((${__farr_index} + 0))
+
+    # check index range
+    if [ \( "${__farr_index}" -lt 1 \) -o \( "${__farr_index}" -gt ${__farr_len} \) ]; then
+	_farr_fatal "array index out of bounds"
+    fi
+
+    __farr_new_len=$((${__farr_len} - 1))
+    __farr_idx=${__farr_index}
+    __farr_idx_1=$((${__farr_idx} + 1))
+    while [ ${__farr_idx} -lt ${__farr_len} ]; do
+        # copy the following value to the current index
+        eval __farr_value=\"\${${__farr_gvrname}_${__farr_idx_1}}\"
+        eval ${__farr_gvrname}_${__farr_idx}=\$\'"$(_farr_quote_for_eval_dsq "${__farr_value}")"\'
+        __farr_idx=$((${__farr_idx} + 1))
+        __farr_idx_1=$((${__farr_idx} + 1))
+    done
+    # Drop the last item
+    eval unset ${__farr_gvrname}_${__farr_idx}
+    # Set the new length
+    eval ${__farr_gvrname}__=${__farr_new_len}
+}
+
+
+#:
+#: Empty an existing array.
+#:
+#: Args:
+#:   $1 (str): The name of the existing array.
+#:
+farray_clear() {
+    local __farr_name
+
+    local __farr_token __farr_gvrname __farr_len __farr_idx
+
+    _farr_array_get_meta "$@"
+
+    __farr_idx=1
+    while [ ${__farr_idx} -le ${__farr_len} ]; do
+	eval unset ${__farr_gvrname}_${__farr_idx}
+	__farr_idx=$((${__farr_idx} + 1))
+    done
+
+    # Length is now zero
+    eval ${__farr_gvrname}__=0
+}
+
+
+#:
+#: Destroy and unset an array and all its elements.
+#:
+#: Args:
+#:   $1 (str): The name of an array. The array may exist or not.
+#:
+#: Returns:
+#:   - A truthy value if the array existed and has been deleted.
+#:   - A falsy value if the array does not exist.
+#:
+farray_destroy() {
+    local __farr_name
+
+    local __farr_token __farr_gvrname __farr_len
+    local __farr_idx
+
+    if ! _farr_array_tryget_meta "$@" ; then
+        return 1
+    fi
+    # Remove "storage"
+    __farr_idx=1
+    while [ ${__farr_idx} -le ${__farr_len} ]; do
+	eval unset ${__farr_gvrname}_${__farr_idx}
+	__farr_idx=$((${__farr_idx} + 1))
+    done
+
+    # Remove length itselt
+    eval unset ${__farr_gvrname}__
+    # Clean out the array name from the token
+    eval ${__farr_name}=\"\"
+}
+
+
+#:
+#: Determine whether a value is found within the array
+#:
+#: Args:
+#:   $1: The name of an existing array.
+#:   $2: The value to search for.
+#:
+#: Returns:
+#:   0 (truish) if the argument value is found within the given array,
+#:   1 (falsy) if the value is not found.
+#:
+farray_contains() {
+    local __farr_name __farr_searched_value
+
+    local __farr_token __farr_gvrname __farr_len
+    local __farr_idx __farr_existing_value
+
+    _farr_array_get_meta "$@"
+    __farr_searched_value="${2+SET}"
+    [ -z "${__farr_searched_value}" ] && _farr_fatal "no search value given"
+    __farr_searched_value="$2"
+
+    __farr_idx=1
+    while [ ${__farr_idx} -le ${__farr_len} ]; do
+        eval __farr_existing_value=\"\${${__farr_gvrname}_${__farr_idx}}\"
+        [ "${__farr_existing_value}" = "${__farr_searched_value}" ] && return 0
+	__farr_idx=$((${__farr_idx} + 1))
+    done
+    return 1
+}
+
+
+#:
+#: Try to find the index of a given value in an existing array.
+#:
+#: Args:
+#:   $1 (str): The name of a variable where to put the found index into
+#:   $2 (str): The name of an existing array
+#:   $3: The value to search for
+#:   $4 (int, optional): The start index to search for (inclusive)
+#:   $5 (int, optional):  The index to stop (inclusive)
+#:
+#: Output (stdout):
+#:   The index number where the value is found -- if any
+#:
+#: Returns:
+#:   - 0 (truish) if the argument value is found within the given array
+#:     and index constraints
+#:   - 1 (falsy) otherwise
+#:
+farray_find() {
+    local __farr_varname __farr_name __farr_searched_value
+    local __farr_start __farr_end
+
+    local __farr_token __farr_gvrname __farr_len
+    local __farr_cur_idx __farr_existing_value
+
+    __farr_varname="${1-}"
+    [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
+    shift
+
+    _farr_array_get_meta "$@"
+
+    __farr_searched_value="${2+SET}"
+    [ -z "${__farr_searched_value}" ] && _farr_fatal "no search value given"
+    __farr_searched_value="$2"
+
+    __farr_start=$((${4-1} + 0))
+    __farr_end=$((${5-${__farr_len}} + 0))
+
+    __farr_cur_idx=${__farr_start}
+    while [ ${__farr_cur_idx} -le ${__farr_end} ]; do
+        eval __farr_existing_value=\"\${${__farr_gvrname}_${__farr_cur_idx}}\"
+        if [ "${__farr_existing_value}" = "${__farr_searched_value}" ]; then
+            #printf "%d" ${__farr_cur_idx}
+            eval ${__farr_varname}=${__farr_cur_idx}
+            return 0
+        fi
+	__farr_cur_idx=$((${__farr_cur_idx} + 1))
+    done
+    return 1
+}
+
+
+#:
+#: Join all the elements in given array with a separator and put the result
+#: into a variable.
+#:
+#: Args:
+#:   $1 (str): The name of a variable where to put joined string value into.
+#:   $2 (str): The name of an existing array.
+#:   $3 (str, optional): The separator (default: a space `` ``).
+#:
+farray_join() {
+    local __farr_varname __farr_name __farr_separator
+
+    local __farr_token __farr_gvrname __farr_len __farr_join_idx
+    local __farr_command __farr_real_separator __farr_current_value
+
+    __farr_varname="${1-}"
+    [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
+    shift
+
+    _farr_array_get_meta "$@"
+
+    __farr_separator="${2-" "}"
+
+    __farr_real_separator=""
+    __farr_command=""
+
+    __farr_join_idx=1
+    while [ ${__farr_join_idx} -le ${__farr_len} ]; do
+	eval __farr_current_value=\"\${${__farr_gvrname}_${__farr_join_idx}}\"
+	__farr_command="${__farr_command}${__farr_real_separator}${__farr_current_value}"
+	__farr_real_separator="${__farr_separator}"
+	__farr_join_idx=$((${__farr_join_idx} + 1))
+    done
+    eval ${__farr_varname}=\"\${__farr_command}\"
+}
+
+
+#:
+#: Join all the elements in given array with a space `` `` character while
+#: escaping properly for feeding into shell's ``eval`` and put it into a
+#: variable.
+#:
+#: It is meant that ``eval "$(farray_join_for_eval ARRAY)"`` is safe:
+#: every item of the array becomes a word when the eval evaluates the
+#: joined string.
+#:
+#: Args:
+#:   $1 (str): The name of a variable where to put joined and properly encoded
+#:             string value into.
+#:   $2 (str): The name of an existing array.
+#:
+#: Output (stdout):
+#:   The result string.
+#:
+farray_join_for_eval() {
+    local __farr_varname  __farr_name
+
+    local __farr_token __farr_gvrname __farr_len
+    local __farr_join_idx __farr_command __farr_real_separator
+    local __farr_current_value
+
+    __farr_varname="${1-}"
+    [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
+    shift
+
+    _farr_array_get_meta "$@"
+
+    __farr_real_separator=""
+    __farr_command=""
+
+    __farr_join_idx=1
+    while [ ${__farr_join_idx} -le ${__farr_len} ]; do
+	eval __farr_current_value=\"\${${__farr_gvrname}_${__farr_join_idx}}\"
+	__farr_command="${__farr_command}${__farr_real_separator}\$'$(_farr_quote_for_eval_dsq "${__farr_current_value}")'"
+	__farr_real_separator=' '
+	__farr_join_idx=$((${__farr_join_idx} + 1))
+    done
+    eval ${__farr_varname}=\"\${__farr_command}\"
+}
+
+
+#:
+#: Join all the elements in given array with a space `` `` character while
+#: escaping properly for feeding into shell's ``eval``.
+#:
+#: ``eval "$(farray_join_for_eval ARRAY)"`` is safe:
+#: every item of the array becomes a word when the eval evaluates the
+#: joined string.
+#:
+#: Args:
+#:   $1 (str): The name of an existing array.
+#:
+#: Output (stdout):
+#:   The joined and properly encoded string.
+#:
+farray_print_join_for_eval() {
+    local __farr_name
+
+    local __farr_token __farr_gvrname __farr_len
+    local __farr_join_idx __farr_current_value
+
+    _farr_array_get_meta "$@"
+
+    __farr_join_idx=1
+    while [ ${__farr_join_idx} -le ${__farr_len} ]; do
+	eval __farr_current_value=\"\${${__farr_gvrname}_${__farr_join_idx}}\"
+        if [ ${__farr_join_idx} -gt 1 ]; then
+            printf "%s" " "
+        fi
+        printf "%s" "\$'$(_farr_quote_for_eval_dsq "${__farr_current_value}")'"
+	__farr_join_idx=$((${__farr_join_idx} + 1))
+    done
+}
+
+
+#:
+#: Call a function for every element in an array starting at the first index.
+#:
+#: The function to be called must accept three arguments:
+#: - the array name
+#: - the current index
+#: - the element value at the current index
+#:
+#: The iteration stops if the called function returns a falsy value.
+#:
+#: Args:
+#:   $1 (str): The name of an existing array.
+#:   $2 (str): The name of a function to be called with three arguments.
+#:
+#: Warning:
+#:   If the number of elements changes while being in `farray_for_each` then
+#:   the behaviour is undefined.
+#:   The current implementation determines the length of the array once
+#:   at the start of execution.
+#:
+farray_for_each() {
+    local __farr_name __farr_callback
+
+    local __farr_token __farr_gvrname __farr_len __farr_idx __farr_rv
+
+    _farr_array_get_meta "$@"
+
+    __farr_callback="${2-}"
+    [ -z "${__farr_callback}" ] && _farr_fatal "missing callback function name"
+
+    __farr_idx=1
+    while [ ${__farr_idx} -le ${__farr_len} ]; do
+	eval "${__farr_callback} ${__farr_name} ${__farr_idx} \"\${${__farr_gvrname}_${__farr_idx}}\""
+	__farr_rv=$?
+	[ ${__farr_rv} -ne 0 ] && return ${__farr_rv}
+	__farr_idx=$((${__farr_idx} + 1))
+    done
+    return 0
+}
+
+
+#:
+#: Like `farray_for_each`, but the callback function is called in reversed
+#: order -- beginning with the last index.
+#:
+farray_reversed_for_each() {
+    local __farr_name __farr_callback
+
+    local __farr_token __farr_gvrname __farr_len __farr_idx __farr_rv
+
+    __farr_callback="${2-}"
+    [ -z "${__farr_callback}" ] && _farr_fatal "missing callback function name"
+
+    __farr_idx=${__farr_len}
+    while [ ${__farr_idx} -gt 0 ]; do
+	eval "${__farr_callback} ${__farr_name} ${__farr_idx} \"\${${__farr_gvrname}_${__farr_idx}}\""
+	__farr_rv=$?
+	[ ${__farr_rv} -ne 0 ] && return ${__farr_rv}
+	__farr_idx=$((${__farr_idx} - 1))
+    done
+    return 0
+}
+
+
+#:
+#: Print the contents of an array to stderr.
+#:
+#: Args:
+#:   $1 (str): The name of an array. The array may exist or not.
+#:
+#: Returns:
+#:   0
+#:
+farray_debug() {
+    local __farr_name
+
+    local __farr_token __farr_gvrname __farr_len
+
+    if ! _farr_array_tryget_meta "$@"; then
+        echo "DEBUG: no (meta-)data for farray \`${1-}'" 1>&2
+        return 0
+    fi
+
+    echo "DEBUG: array \`${__farr_name}' has length ${__farr_len}" 1>&2
+    if [ ${__farr_len} -gt 0 ]; then
+        echo "DEBUG:   its contents:" 1>&2
+        farray_for_each ${__farr_name} _farr_debug_print_value
+    fi
+    return 0
+}
+
+
+#:
+#: Debug output helper for `farray_debug`.
+#:
+_farr_debug_print_value() {
+    printf "DEBUG:     %s: \`%s'\\n" "$2" "$3" 1>&2
+    return 0
+}
+
+
+#:
+#: Create a new alist.
+#:
+#: Args:
+#:   $1 (str): The name of the alist.
+#:             Must conform to shell variable naming conventions.
+#:
+#: Exit:
+#:   Iff the alist already exists.
+#:
+falist_create() {
+    local __farr_name
+
+    local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
+
+    __farr_name="${1-}"
+    [ -z "${__farr_name}" ] && _farr_fatal "missing falist name"
+    eval __farr_token=\"\${${__farr_name}-}\"
+    [ -n "${__farr_token}" ] && _farr_fatal "object \`${__farr_name}' already created (token \`${__farr_token}')"
+
+    __farr_token="$(/usr/bin/hexdump -v -e '/1 "%02x"' -n 16 '/dev/urandom')"
+
+    __farr_objname=${_farr_alist_prefix}${__farr_token}
+    __farr_keyname=${_farr_alist_key_prefix}${__farr_token}
+    __farr_valname=${_farr_alist_value_prefix}${__farr_token}
+
+    # Check whether the variable already exists
+    eval __farr_len=\${${__farr_objname}__+SET}
+    [ -n "${__farr_len}" ] && _farr_fatal "falist \`${__farr_name}' already exists: existing token \`${__farr_token}'"
+
+    # Really create the storage by initializing its length
+    eval ${__farr_objname}__=0
+
+    # And associate the token with the array name
+    eval ${__farr_name}="${__farr_token}"
+}
+
+
+#:
+#: Internal helper to get all the metadata for an alist
+#:
+#: Args:
+#:   $1 (str): The name of the alist.
+#:
+#: Output (Globals):
+#:   __farr_name (str):
+#:   __farr_token (str):
+#:   __farr_objname (str):
+#:   __farr_keyname (str):
+#:   __farr_valname (str):
+#:   __farr_len (int): The length of the array
+#:
+#: Exit:
+#:   Iff the array does not exist in some fashion (token and/or storage).
+#:
+_farr_alist_get_meta() {
+    __farr_name="${1-}"
+    [ -z "${__farr_name}" ] && _farr_fatal "missing farray name"
+    eval __farr_token=\"\${${__farr_name}-}\"
+    [ -z "${__farr_token}" ] && _farr_fatal _farr_err "falist \`${__farr_name}' does not exist: token empty"
+
+    __farr_objname="${_farr_alist_prefix}${__farr_token}"
+    __farr_keyname=${_farr_alist_key_prefix}${__farr_token}
+    __farr_valname=${_farr_alist_value_prefix}${__farr_token}
+
+    eval __farr_len=\${${__farr_objname}__:+SET}
+    [ -z "${__farr_len}" ] && _farr_fatal "falist \`${__farr_name}' does not exist: no object for token \`${__farr_token}'"
+    eval __farr_len="\$((\${${__farr_objname}__} + 0))"
+    return 0
+}
+
+
+#:
+#: Internal helper to try to get all the metadata for an alist.
+#:
+#: Args:
+#:   $1 (str): The name of the array
+#:
+#: Output (Globals):
+#:   __farr_name (str): The name of the alist.
+#:   __farr_token (str): The token that is the value of the name.
+#:   __farr_objname (str): The variable prefix for the length ("object").
+#:   __farr_keyname (str): The variable prefix for all item keys.
+#:   __farr_valname (str): The variable prefix for all item values.
+#:   __farr_len (int): The length of the alist.
+#:
+#: Returns:
+#:   0 if the array exists, 1 if something is missing.
+#:
+#: Exit:
+#:   Iff the array name is not given
+#:
+_farr_alist_tryget_meta() {
+    __farr_name="${1-}"
+    [ -z "${__farr_name}" ] && _farr_fatal "missing farray name"
+    __farr_token=""
+    __farr_objname=""
+    __farr_keyname=""
+    __farr_valname=""
+    __farr_len=""
+    eval __farr_token=\"\${${__farr_name}-}\"
+    if [ -z "${__farr_token}" ]; then
+        _farr_err "falist \`${__farr_name}' does not exist: token empty"
+        return 1;
+    fi
+
+    __farr_objname="${_farr_alist_prefix}${__farr_token}"
+    __farr_keyname=${_farr_alist_key_prefix}${__farr_token}
+    __farr_valname=${_farr_alist_value_prefix}${__farr_token}
+
+    eval __farr_len=\${${__farr_objname}__:+SET}
+    if [ -z "${__farr_len}" ]; then
+        _farr_err "falist \`${__farr_name}' does not exist: no object for token \`${__farr_token}'"
+        return 1
+    fi
+    eval __farr_len="\$((\${${__farr_objname}__} + 0))"
+    return 0
+}
+
+
+#:
+#: Get the length of an alist and put it into a variable.
+#:
+#: Args:
+#:   $1 (str): The name of the variable to put the length info.
+#:   $2 (str): The name of the alist.
+#:
+falist_length() {
+    local __farr_varname __farr_name
+
+    local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
+
+    __farr_varname="${1-}"
+    [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
+    shift
+    _farr_alist_get_meta "$@"
+    eval ${__farr_varname}=${__farr_len}
+}
+
+
+#:
+#: Get the length of an alist and print it to stdout.
+#:
+#: Args:
+#:   $1 (str): The name of the alist.
+#:
+#: Output (stdout):
+#:   The number of elements in the alist.
+#:   If the array does not exist the output is -1.
+#:
+#: Returns:
+#:   0 (truthy)
+#:
+falist_print_length() {
+    local __farr_vn
+
+    ( falist_length __farr_vn "$@" && printf "%s" "${__farr_vn}"; ) \
+    || printf "%s" "-1"
+}
+
+
+#:
+#: Empty an existing alist.
+#:
+#: Args:
+#:   $1 (str): The name of the existing alist.
+#:
+falist_clear() {
+    local __farr_name
+
+    local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
+    local __farr_idx
+
+    _farr_alist_get_meta "$@"
+
+    # Remove "storage"
+    __farr_idx=1
+    while [ ${__farr_idx} -le ${__farr_len} ]; do
+        eval unset ${__farr_valname}_${__farr_idx}
+        eval unset ${__farr_keyname}_${__farr_idx}
+        __farr_idx=$((${__farr_idx} + 1))
+    done
+
+    # Reset object (length) itself
+    eval ${__farr_objname}__=0
+    return 0
+}
+
+
+#:
+#: Destroy and unset an alist
+#:
+#: Args:
+#:   $1 (str): The name of an alist. The alist may exist or not.
+#:
+#: Returns:
+#:   - A truthy value if the alist existed and has been deleted.
+#:   - A falsy value if the alist does not exist.
+#:
+falist_destroy() {
+    local __farr_name
+
+    local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
+    local __farr_idx
+
+    if ! _farr_alist_tryget_meta "$@" ; then
+        return 1
+    fi
+
+    # Remove "storage"
+    __farr_idx=1
+    while [ ${__farr_idx} -le ${__farr_len} ]; do
+        eval unset ${__farr_valname}_${__farr_idx}
+        eval unset ${__farr_keyname}_${__farr_idx}
+        __farr_idx=$((${__farr_idx} + 1))
+    done
+
+    # Remove object (length) itselt
+    eval unset ${__farr_objname}__
+    # Clean out the alist name from the token
+    eval ${__farr_name}=\"\"
+    return 0
+}
+
+
+#:
+#: Map a key to a value.
+#:
+#: Args:
+#:   $1 (str): The name of an existing alist.
+#:   $2: The key.
+#:   $3: The value.
+#:
+#: Exit:
+#:   If one of the underlying arrays that implement the alist does not exist
+#:   or if an internal inconsistency will be detected.
+#:
+falist_set() {
+    local __farr_name __farr_key __farr_value
+
+    local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
+    local __farr_idx __farr_elkey
+
+    _farr_alist_get_meta "$@"
+    [ $# -lt 2 ] && _farr_fatal "missing key"
+    __farr_key="$2"
+    [ $# -lt 3 ] && _farr_fatal "missing value"
+    __farr_value="$3"
+
+    __farr_idx=1
+    while [ ${__farr_idx} -le ${__farr_len} ]; do
+        eval __farr_elkey=\"\${${__farr_keyname}_${__farr_idx}+SET}\"
+        if [ -n "${__farr_elkey}" ]; then
+            eval __farr_elkey=\"\${${__farr_keyname}_${__farr_idx}}\"
+            if [ "${__farr_elkey}" = "${__farr_key}" ]; then
+                eval ${__farr_valname}_${__farr_idx}=\$\'"$(_farr_quote_for_eval_dsq "${__farr_value}")"\'
+                return 0
+            fi
+        else
+            _farr_fatal "key unexpectedly unset (index ${__farr_idx})" 1>&2
+        fi
+        __farr_idx=$((${__farr_idx} + 1))
+    done
+    #
+    # Not yet found: "append" ..
+    #
+    # NOTE: __farr_idx already the new correct length
+    #
+    __farr_len=${__farr_idx}
+    #   ... the key/value pairs to storage
+    eval ${__farr_keyname}_${__farr_len}=\$\'"$(_farr_quote_for_eval_dsq "${__farr_key}")"\'
+    eval ${__farr_valname}_${__farr_len}=\$\'"$(_farr_quote_for_eval_dsq "${__farr_value}")"\'
+    #   ... the new length
+    eval ${__farr_objname}__=${__farr_len}
+    return 0
+}
+
+
+#:
+#: Get the value that is associated with a key and put it into a variable.
+#:
+#: Args:
+#:   $1 (str): The name of the variable to put the value into.
+#:   $2 (str): The name of an existing alist.
+#:   $3: The key.
+#:
+#: Exit:
+#:   - If the key is not found.
+#:   - If one of the underlying arrays that implement the alist does not exist.
+#:     or if an internal inconsistency will be detected.
+#:
+falist_get() {
+    local __farr_varname __farr_name __farr_key
+
+    local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
+    local __farr_idx __farr_getkey
+
+    __farr_varname="${1-}"
+    [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
+    shift
+    _farr_alist_get_meta "$@"
+    [ $# -lt 2 ] && _farr_fatal "missing key"
+    __farr_key="$2"
+
+    __farr_idx=1
+    while [ ${__farr_idx} -le ${__farr_len} ]; do
+        eval __farr_getkey=\"\${${__farr_keyname}_${__farr_idx}+SET}\"
+        if [ -n "${__farr_getkey}" ]; then
+            eval __farr_getkey=\"\${${__farr_keyname}_${__farr_idx}}\"
+            if [ "${__farr_getkey}" = "${__farr_key}" ]; then
+                eval ${__farr_varname}=\"\${${__farr_valname}_${__farr_idx}}\"
+                return 0
+            fi
+        else
+            _farr_fatal "key unexpectedly unset (index ${__farr_idx})" 1>&2
+        fi
+        __farr_idx=$((${__farr_idx} + 1))
+    done
+    _farr_fatal "alist \`${__farr_name}': key \`${__farr_key}' not found"
+}
+
+
+#:
+#: Try to get the value that is associated with a key and store it in a
+#: variable.
+#:
+#: Args:
+#:   $1 (str): The name of the variable where to put the value into.
+#:   $2 (str): The name of an existing alist.
+#:   $3: The key.
+#:
+#: Returns:
+#:   0 (truthy) on success,
+#:   1 (falsy) if the given key is not found.
+#:
+falist_tryget() {
+    local __farr_varname __farr_name __farr_key
+
+    local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
+    local __farr_idx __farr_getkey
+
+    __farr_varname="${1-}"
+    [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
+    shift
+    _farr_alist_get_meta "$@"
+    [ $# -lt 2 ] && _farr_fatal "missing key"
+    __farr_key="$2"
+
+    __farr_idx=1
+    while [ ${__farr_idx} -le ${__farr_len} ]; do
+        eval __farr_getkey=\"\${${__farr_keyname}_${__farr_idx}+SET}\"
+        if [ -n "${__farr_getkey}" ]; then
+            eval __farr_getkey=\"\${${__farr_keyname}_${__farr_idx}}\"
+            if [ "${__farr_getkey}" = "${__farr_key}" ]; then
+                eval ${__farr_varname}=\"\${${__farr_valname}_${__farr_idx}}\"
+                return 0
+            fi
+        else
+            _farr_fatal "key unexpectedly unset (index ${__farr_idx})"
+        fi
+        __farr_idx=$((${__farr_idx} + 1))
+    done
+    return 1
+}
+
+
+#:
+#: Try to get the key that is associated with a storage index and store it in a variable.
+#:
+#: Use this for iteration over values.
+#:
+#: Args:
+#:   $1 (str): The name of the variable where to put the key into.
+#:   $2 (str): The name of an existing alist.
+#:   $3 (int): The index.
+#:
+#: Returns:
+#:   0 (truthy) on success,
+#:   1 (falsy) if the given index is out of bounds.
+#:
+#: Exit:
+#:   Other errors (missing array name, missing index value) are considered
+#:   fatal and call `_farr_fatal` (i.e. `exit`).
+#:
+falist_tryget_key_at_index() {
+    local __farr_varname __farr_name __farr_index
+
+    local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
+    local __farr_idx __farr_getikey
+
+    __farr_varname=${1-}
+    [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
+    shift
+    _farr_alist_get_meta "$@"
+    [ $# -lt 2 ] && _farr_fatal "missing index"
+    __farr_index=$(($2 + 0))
+
+    if [ \( ${__farr_index} -ge 1 \) -a \( ${__farr_index} -le ${__farr_len} \) ]; then
+        eval __farr_getikey=\"\${${__farr_keyname}_${__farr_index}+SET}\"
+        if [ -n "${__farr_getikey}" ]; then
+            eval ${__farr_varname}=\"\${${__farr_keyname}_${__farr_index}}\"
+            return 0
+        else
+            _farr_fatal "key unexpectedly unset (index ${__farr_index})"
+        fi
+    else
+        return 1
+    fi
+}
+
+
+#:
+#: Try to get the value that is associated with a storage index and store it in a variable.
+#:
+#: Use this for iteration over keys.
+#:
+#: Args:
+#:   $1 (str): The name of the variable where to put the value into.
+#:   $2 (str): The name of an existing alist.
+#:   $3 (int): The index.
+#:
+#: Returns:
+#:   0 (truthy) on success,
+#:   1 (falsy) if the given index is out of bounds.
+#:
+#: Exit:
+#:   Other errors (missing array name, missing index value) are considered
+#:   fatal and call `_farr_fatal` (i.e. `exit`).
+#:
+falist_tryget_value_at_index() {
+    local __farr_varname __farr_name __farr_index
+
+    local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
+    local __farr_idx __farr_getival
+
+    __farr_varname=${1-}
+    [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
+    shift
+    _farr_alist_get_meta "$@"
+    [ $# -lt 2 ] && _farr_fatal "missing index"
+    __farr_index=$(($2 + 0))
+
+    if [ \( ${__farr_index} -ge 1 \) -a \( ${__farr_index} -le ${__farr_len} \) ]; then
+        eval __farr_getival=\"\${${__farr_valname}_${__farr_index}+SET}\"
+        if [ -n "${__farr_getival}" ]; then
+            eval ${__farr_varname}=\"\${${__farr_valname}_${__farr_index}}\"
+            return 0
+        else
+            _farr_fatal "value unexpectedly unset (index ${__farr_index})"
+        fi
+    else
+        return 1
+    fi
+}
+
+
+#:
+#: Determine whether a key is found within the alist.
+#:
+#: Args:
+#:   $1 (str): The name of an existing alist.
+#:   $2: The key.
+#:
+#: Returns:
+#:   0 (truthy) on success if the key is found,
+#:   1 (falsy) if the given key is not found.
+#:
+falist_contains() {
+    local __farr_name __farr_key
+
+    local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
+    local __farr_idx __farr_cokey
+
+    _farr_alist_get_meta "$@"
+    [ $# -lt 2 ] && _farr_fatal "missing key"
+    __farr_key="$2"
+
+    __farr_idx=1
+    while [ ${__farr_idx} -le ${__farr_len} ]; do
+        eval __farr_cokey=\"\${${__farr_keyname}_${__farr_idx}+SET}\"
+        if [ -n "${__farr_cokey}" ]; then
+            eval __farr_cokey=\"\${${__farr_keyname}_${__farr_idx}}\"
+            if [ "${__farr_cokey}" = "${__farr_key}" ]; then
+                return 0
+            fi
+        else
+            _farr_fatal "key unexpectedly unset (index ${__farr_idx})" 1>&2
+        fi
+        __farr_idx=$((${__farr_idx} + 1))
+    done
+    return 1
+}
+
+
+#:
+#: Call a function for every key-value pair in an alist starting in index order.
+#:
+#: The function to be called must accept three or four arguments:
+#: - the alist name
+#: - the element key at the current index
+#: - the element value at the current index
+#: - the current index
+#:
+#: The iteration stops if the called function returns a falsy value.
+#:
+#: Args:
+#:   $1 (str): The name of an existing array.
+#:   $2 (str): The name of a function to be called with four arguments.
+#:
+#: Warning:
+#:   If the number of elements changes while being in `falist_for_each` then
+#:   the behaviour is undefined.
+#:   The current implementation determines the length of the alist once
+#:   at the start of execution.
+#:
+falist_for_each() {
+    local __farr_name __farr_callback
+
+    local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
+    local __farr_feidx __farr_fekey __farr_feval __farr_rv
+
+    _farr_alist_get_meta "$@"
+    __farr_callback="${2-}"
+    [ -z "${__farr_callback}" ] && _farr_fatal "missing callback function name"
+
+    __farr_feidx=1
+    while [ ${__farr_feidx} -le ${__farr_len} ]; do
+        eval __farr_fekey=\"\${${__farr_keyname}_${__farr_feidx}+SET}\"
+        if [ -n "${__farr_fekey}" ]; then
+            eval __farr_fekey=\"\${${__farr_keyname}_${__farr_feidx}}\"
+            eval __farr_feval=\"\${${__farr_valname}_${__farr_feidx}+SET}\"
+            if [ -n "${__farr_feval}" ]; then
+                eval __farr_feval=\"\${${__farr_valname}_${__farr_feidx}}\"
+                eval "${__farr_callback} ${__farr_name} \"\${__farr_fekey}\" \"\${__farr_feval}\" ${__farr_feidx}"
+                __farr_rv=$?
+                [ ${__farr_rv} -ne 0 ] && return ${__farr_rv}
+            else
+                _farr_fatal "alist \`${__farr_name}': missing value index"
+            fi
+        else
+            _farr_fatal "alist \`${__farr_name}': missing value index"
+        fi
+        __farr_feidx=$((${__farr_feidx} + 1))
+    done
+    return 0
+}
+
+
+#:
+#: Print the contents of an alist to stderr.
+#:
+#: Args:
+#:   $1 (str): The name of an alist. The array may exist or not.
+#:
+#: Returns:
+#:   0
+#:
+falist_debug() {
+    local __farr_name
+
+    local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
+    local __farr_idx __farr_el_key __farr_el_val
+
+    if ! _farr_alist_tryget_meta "$@"; then
+        echo "DEBUG: no (meta-)data for falist \`${1-}'" 1>&2
+        return 0
+    fi
+
+    echo "DEBUG: alist \`${__farr_name}' has length ${__farr_len}" 1>&2
+    __farr_idx=1
+    while [ ${__farr_idx} -le ${__farr_len} ]; do
+        eval __farr_el_key=\"\${${__farr_keyname}_${__farr_idx}+SET}\"
+        if [ -z "${__farr_el_key}" ]; then
+            echo "DEBUG: key unexpectedly unset (index ${__farr_idx})" 1>&2
+        fi
+        eval __farr_el_val=\"\${${__farr_valname}_${__farr_idx}+SET}\"
+        if [ -z "${__farr_el_val}" ]; then
+            echo "DEBUG: value unexpectedly unset (index ${__farr_idx})" 1>&2
+        fi
+        if [ \( -n "${__farr_el_key}" \) -a \( -n "${__farr_el_val}" \) ]; then
+            eval __farr_el_key=\"\${${__farr_keyname}_${__farr_idx}}\"
+            eval __farr_el_val=\"\${${__farr_valname}_${__farr_idx}}\"
+            printf "DEBUG:     \`%s' -> \`%s'\\n" "${__farr_el_key}" "${__farr_el_val}" 1>&2
+        fi
+        __farr_idx=$((${__farr_idx} + 1))
+    done
+    return 0
+}
+
+
+#:
+#: Some basic tests.
+#:
+_farray_test() {
+    local CMD
+    local _i _var _k _v
+
+    set -
+    set -eu
+
+    farray_create TEST 0 1 2 '3  4   5' $'" 678" \\\'90 '    # '
+    farray_debug TEST
+    farray_destroy TEST
+    farray_destroy TEST || { echo "(this is ok.)"; true; }
+
+    farray_create TEST 1 2 3 '4  5   6' $'" 123" \\\'45 '    # '
+    farray_debug TEST
+    if farray_contains TEST 7; then
+        echo "CONTAINS (ERROR)"
+    fi
+    if ! farray_contains TEST '4  5   6'; then
+        echo "NO CONTAINS (ERROR)"
+    fi
+    if farray_contains TEST '4 5 6'; then
+        echo "CONTAINS (ERROR)"
+    fi
+    if ! farray_contains TEST 1; then
+        echo "NOT CONTAINS (ERROR)"
+    fi
+    if ! farray_contains TEST 2; then
+        echo "NOT CONTAINS (ERROR)"
+    fi
+
+    if ! farray_contains TEST $'" 123" \\\'45 ' ; then      # '
+       echo "NOT CONTAINS (ERROR)"
+    fi
+    if ! farray_find _i TEST $'" 123" \\\'45 ' ; then       # '
+       echo "NOT CONTAINS (ERROR)"
+    fi
+
+    farray_get _var TEST 1
+    printf "VAR 1: %s\n" "$_var"
+    farray_get _var TEST 5
+    printf "VAR 2: %s\n" "$_var"
+    [ "$_var" = $'" 123" \\\'45 ' ] || echo "COMPARE ERROR"   # '
+
+    farray_destroy TEST
+
+    farray_create TEST 11 22 33 '44  55   66' $'" 112233" \\\'4455 '    # '
+    farray_debug TEST
+
+    farray_get _i TEST 1
+    echo $_i
+    farray_get _i TEST 2
+    farray_del TEST 4
+    farray_get _i TEST 4
+    echo $_i
+    farray_tryget _i TEST 1 || echo "NOT FOUND (ERROR)"
+    farray_tryget _i TEST 4 || echo "NOT FOUND (ERROR)"
+    ! farray_tryget _i TEST 5 || echo "FOUND (ERROR)"
+    farray_get _var TEST 4
+    [ "$_var" = $'" 112233" \\\'4455 ' ] || echo "COMPARE ERROR"  # '
+
+    farray_clear TEST
+    farray_length _var TEST
+    [ ${_var} -eq 0 ] || echo "LENGTH != 0 (ERROR)"
+
+    if ! farray_destroy TEST; then
+        echo "DESTROY FAILED (ERROR)"
+    fi
+    if farray_destroy TEST; then
+        echo "DESTROY succeeded (ERROR)"
+    fi
+    farray_destroy TEST || true
+
+    farray_create CMD zfs list "-H" "-o" "name,canmount,mounted,mountpoint,origin" "zpool/ROOT/test- YYY"
+    farray_join _var CMD
+    echo "CMD: join with ' ': $_var"
+    farray_join _var CMD ' --- '
+    echo "CMD: join with ' --- ': $_var"
+    farray_clear CMD
+    farray_join _var CMD ' --- '
+    echo "CMD: join with ' --- ': $_var   (empty: ok)"
+
+    farray_destroy CMD
+    farray_create CMD zfs list "-H" "-o" "name,canmount,mounted,mountpoint,origin" "zpool/ROOT/test- YYY"
+    farray_join_for_eval _var CMD
+    echo "CMD-EVAL: $_var"
+    farray_destroy CMD || true
+
+    farray_create TEST
+    farray_set TEST 1 "VAL-1"     # appends here
+    farray_set TEST 2 "VAL-2"     # appends here
+    farray_set TEST 1 "VAL-1-1"   # replaces at index 1
+    farray_length _var TEST
+    [ ${_var} -eq 2 ] || echo "LENGTH != 2 (ERROR)"
+    farray_get _var TEST 1
+    [ "${_var}" = "VAL-1-1" ] || echo "unexpected value (ERROR)"
+    farray_get _var TEST 2
+    [ "${_var}" = "VAL-2" ] || echo "unexpected value (ERROR)"
+    farray_destroy TEST
+
+    falist_create LIST
+    falist_length _i LIST
+    [ "$_i" -eq 0 ] || echo "alist length != 0 (ERROR)"
+    falist_print_length LIST && echo
+    falist_debug LIST
+    falist_destroy LIST
+
+    falist_create LIST
+    falist_set LIST K1 V1
+    falist_set LIST K2 V2
+    falist_debug LIST
+    falist_set LIST K2 V2-2
+    falist_set LIST K3 $'" 111222333" \\\'444555 '    # '
+    falist_debug LIST
+    if ! falist_contains LIST K1; then
+        echo "NOT CONTAINS (ERROR)"
+    fi
+    if falist_contains LIST K; then
+        echo "CONTAINS (ERROR)"
+    fi
+    falist_debug LIST
+    falist_get _var LIST K2
+    [ "$_var" = "V2-2" ] || echo "alist element not found (ERROR)"
+    if ! falist_tryget _var LIST K1; then
+        echo "NOT FOUND (ERROR)"
+    fi
+    if falist_tryget _i LIST K; then
+        echo "FOUND (ERROR)"
+    fi
+    falist_length _i LIST
+    echo "LENGTH: $_i"
+    printf "%s" "PRINT LENGTH: "
+    falist_print_length LIST
+    echo
+    _var="$(falist_print_length NON_EXISTING_LIST)"
+    if [ "${_var}" != "-1" ]; then
+        echo "VALID LENGTH (ERROR)"
+    fi
+
+    # Iteration by indexing
+    echo "ITERATE (manual indexing):"
+    _i=1
+    while falist_tryget_key_at_index _k LIST ${_i}; do
+        # cannot fail under "normal" circumstances
+        falist_tryget_value_at_index _v LIST ${_i}
+        printf "  KEY: \`%s', VAL: \`%s'\\n" "${_k}" "${_v}"
+        _i=$((${_i} + 1))
+    done
+
+    # Iteration with for each
+    echo "ITERATE (for each):"
+    falist_for_each LIST $'printf "EACH: %s key \\`%s\\\', value \\`%s\\\' at idx %d\\n"'   # `
+
+    falist_clear LIST
+    if ! falist_destroy LIST ; then
+        echo "DESTROY FAILED (ERROR)"
+    fi
+    if falist_destroy LIST ; then
+        echo "DESTROY SUCCEEDED (ERROR)"
+    fi
+    # set
+    echo "============================================================"
+    echo "OK."
+    echo "============================================================"
+}
+
+
+# _farray_test