Mercurial > hgrepos > FreeBSD > ports > sysutils > local-bsdtools
view share/local-bsdtools/array.sh @ 506:d1ffc844ceb4
Style
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Sat, 31 Aug 2024 14:07:28 +0200 |
| parents | ace88a4831f7 |
| children | e51440a07a9d |
line wrap: on
line source
#!/bin/sh # -*- indent-tabs-mode: nil; -*- #: #: A simple library to emulate simple array (one-dimensional) 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 inspired by #: https://unix.stackexchange.com/questions/137566/arrays-in-unix-bourne-shell #: #: Is implements one-dimensional array with one-base indexing. #: #: Hints and rules: #: #: - Every array has a NAME #: - One-based indexing is used #: - Array elements are stored in a global variable named #: ``_farr_<NAME>_<index-number>`` #: - The number of elements in the array ist stored in the global #: variable ``_farr_<NAME>__`` #: - An array name must conform to shell variable naming conventions #: - Currently the number of of elements of an array must be >= 0 #: - An unset global variable ``_farr_<NAME>__`` variable is a severe #: error normally and forces an immediate error ``exit``. #: Exceptions to this rule are documented. #: _farr_global_prefix="_farr_" #: #: Internal error for fatal errors. #: #: Args: #: $1 (str): The error message #: _array_fatal() { echo "ERROR: ${1}" 1>&2 exit 70 # EX_SOFTWARE } #: #: 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): Optional initialization values #: #: Exit: #: Iff the array already exists. #: array_create() { local _name local _gvrname _el _l [ $# -lt 1 ] && _array_fatal "missing array name" _name=$1 _gvrname=${_farr_global_prefix}$1 shift # Check whether the variable already exists eval _l=\${${_gvrname}__:-__UNSET__} [ "${_l}" != "__UNSET__" ] && _array_fatal "array \`${_name}' already exists" # Really create eval ${_gvrname}__=0 for _el in "$@"; do array_append ${_name} "${_el}" done } #: #: Create or re-create a new array. #: #: Args: #: $1 (str): The name of the array. #: Must conform to shell variable naming conventions #: $2... (optional): Optional initialization values #: #: If the array exists already it will be reinitialized completely. #: If the array does not exist already it is just like `array_create`. #: It is just a convenience function for calling `array_destroy`, ignoring #: errors eventually, and calling `array_create`. #: array_new() { array_destroy $1 || true array_create "$@" } #: #: Get the length of an array. #: #: 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. #: array_length() { local _name local _gvrname _l [ $# -lt 1 ] && _array_fatal "missing array name" _name=$1 _gvrname=${_farr_global_prefix}$1 # Check whether the variable already exists eval _l=\${${_gvrname}__:-__UNSET__} if [ "${_l}" = "__UNSET__" ]; then printf "%s" "-1" else printf "%s" "${_l}" fi } #: #: 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 #: will be appended. #: array_append() { local _name _value local _gvrname _l _l1 [ $# -lt 1 ] && _array_fatal "missing array name" _name=$1 _gvrname=${_farr_global_prefix}$1 _value="${2-}" # Check whether the variable already exists eval _l=\${${_gvrname}__:-__UNSET__} if [ "${_l}" = "__UNSET__" ]; then _array_fatal "array \`${_name}' does not exist" fi _l1=$((${_l} + 1)) # Set value eval ${_gvrname}_${_l1}="\"${_value}\"" # Set new array length eval ${_gvrname}__=${_l1} } #: #: Get an array value from a given index. #: #: Args: #: $1 (str): The name of the existing array #: $2 (int): The index #: #: Output (stdout): #: The value at index $2. #: array_get() { local _name _index local _gvrname _l _value [ $# -lt 1 ] && _array_fatal "missing array name" _name=$1 _gvrname=${_farr_global_prefix}$1 [ $# -lt 2 ] && _array_fatal "missing array index" _index=$2 # Check whether the variable already exists eval _l=\${${_gvrname}__:-__UNSET__} if [ "${_l}" = "__UNSET__" ]; then _array_fatal "array \`${_name}' does not exist" fi # check index range if [ \( "${_index}" -lt 1 \) -o \( "${_index}" -gt ${_l} \) ]; then _array_fatal "array index out of bounds" fi eval _value=\"\${${_gvrname}_${_index}}\" printf "%s" "${_value}" } #: #: Try to get an array value from a given index. #: #: Args: #: $1 (str): The name of the existing array #: $2 (int): The index #: #: Output (stdout): #: The value at index $2. #: #: 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 `_array_fatal` (i.e. `exit`). #: array_tryget() { local _name _index local _gvrname _l _value [ $# -lt 1 ] && _array_fatal "missing array name" _name=$1 _gvrname=${_farr_global_prefix}$1 [ $# -lt 2 ] && _array_fatal "missing array index" _index=$2 # Check whether the variable already exists eval _l=\${${_gvrname}__:-__UNSET__} if [ "${_l}" = "__UNSET__" ]; then _array_fatal "array \`${_name}' does not exist" fi # check index range if [ \( "${_index}" -lt 1 \) -o \( "${_index}" -gt ${_l} \) ]; then return 1 fi eval _value=\"\${${_gvrname}_${_index}}\" printf "%s" "${_value}" return 0 } #: #: Empty an existing array. #: #: Args: #: $1 (str): The name of the existing array #: array_clear() { local _name local _gvrname _l _idx [ $# -lt 1 ] && _array_fatal "missing array name" _name=$1 _gvrname=${_farr_global_prefix}$1 # Check whether the variable already exists eval _l=\${${_gvrname}__:-__UNSET__} if [ "${_l}" = "__UNSET__" ]; then _array_fatal "array \`${_name}' does not exist" fi _idx=1 while [ ${_idx} -le ${_l} ]; do eval unset ${_gvrname}_${_idx} _idx=$((${_idx} + 1)) done # Length is now zero eval ${_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 #: array_destroy() { local _name local _gvrname _l _idx [ $# -lt 1 ] && _array_fatal "missing array name" _name=$1 _gvrname=${_farr_global_prefix}$1 # Handle non-existing array names eval _l=\${${_gvrname}__:-__UNSET__} if [ "${_l}" = "__UNSET__" ]; then return 1 fi _idx=1 while [ ${_idx} -le ${_l} ]; do eval unset ${_gvrname}_${_idx} _idx=$((${_idx} + 1)) done # Remove eval unset ${_gvrname}__ } #: #: 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 `array_for_each` then #: the behaviour is undefined. #: The current implementation determines the length of the array once #: at the start of execution. #: array_for_each() { local _name _cb local _gvrname _l _idx _value _rv [ $# -lt 1 ] && _array_fatal "missing array name" _name=$1 _gvrname=${_farr_global_prefix}$1 [ $# -lt 2 ] && _array_fatal "missing callback function name" _cb="$2" # Check whether the variable already exists eval _l=\${${_gvrname}__:-__UNSET__} if [ "${_l}" = "__UNSET__" ]; then _array_fatal "array \`${_name}' does not exist" fi _idx=1 while [ ${_idx} -le ${_l} ]; do eval "${_cb} ${_name} ${_idx} \"\${${_gvrname}_${_idx}}\"" _rv=$? [ ${_rv} -ne 0 ] && return ${_rv} _idx=$((${_idx} + 1)) done return 0 } #: #: Like `array_for_each`, but the function is called in reversed order -- #: beginning with the last index. #: array_reversed_for_each() { local _name _cb local _gvrname _l _idx _value _rv [ $# -lt 1 ] && _array_fatal "missing array name" _name=$1 _gvrname=${_farr_global_prefix}$1 [ $# -lt 2 ] && _array_fatal "missing callback function name" _cb="$2" # Check whether the variable already exists eval _l=\${${_gvrname}__:-__UNSET__} if [ "${_l}" = "__UNSET__" ]; then _array_fatal "array \`${_name}' does not exist" fi _idx=${_l} while [ ${_idx} -gt 0 ]; do eval "${_cb} ${_name} ${_idx} \"\${${_gvrname}_${_idx}}\"" _rv=$? [ ${_rv} -ne 0 ] && return ${_rv} _idx=$((${_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 #: array_debug() { local _name local _gvrname _l [ $# -lt 1 ] && _array_fatal "missing array name" _name=$1 _gvrname=${_farr_global_prefix}$1 # Check whether the variable already exists eval _l=\${${_gvrname}__:-__UNSET__} if [ "${_l}" = "__UNSET__" ]; then echo "DEBUG: array \`${_name}' does not exist" 1>&2 return 0 fi echo "DEBUG: array \`${_name}' has length ${_l}" 1>&2 if [ ${_l} -gt 0 ]; then echo "DEBUG: its contents:" 1>&2 array_for_each ${_name} _array_debug_print_value fi return 0 } #: #: Debug output helper for `array_debug`. #: _array_debug_print_value() { printf "DEBUG: idx: %s, val: \`%s'\\n" "$2" "$3" 1>&2 return 0 }
