changeset 483:1aa14a7d96dc

Provide a simple implementation of arrays for the FreeBSD POSIX/Bourne shell /bin/sh
author Franz Glasner <fzglas.hg@dom66.de>
date Thu, 29 Aug 2024 14:01:38 +0200
parents 26a97b2351e0
children 5d43c68bd1e9
files Makefile pkg-plist share/local-bsdtools/array.sh
diffstat 3 files changed, 275 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Wed Aug 28 09:51:05 2024 +0200
+++ b/Makefile	Thu Aug 29 14:01:38 2024 +0200
@@ -80,7 +80,7 @@
 	${SED} -i "" -e "s|@@SIMPLEVERSIONTAG@@|${SIMPLEVERSIONTAG}|" ${WRKSRC}/${_ef}
 .endfor
 	${MKDIR} ${WRKSRC}/share/${PORTNAME}
-.for _df in share/local-bsdtools/common.subr
+.for _df in share/local-bsdtools/array.sh share/local-bsdtools/common.subr
 	${CP} -v ${SRC}/${_df} ${WRKSRC}/${_df}
 	${SED} -i "" -e "s|@@SIMPLEVERSIONTAG@@|${SIMPLEVERSIONTAG}|" ${WRKSRC}/${_df}
 .endfor
@@ -116,7 +116,7 @@
 	${INSTALL_SCRIPT} ${WRKSRC}/etc/periodic/daily/${_ps} ${STAGEDIR}${PREFIX}/etc/periodic/daily
 .endfor
 	${MKDIR} ${STAGEDIR}${DATADIR}
-.for _df in common.subr
+.for _df in array.sh common.subr
 	${INSTALL_DATA} ${WRKSRC}/share/${PORTNAME}/${_df} ${STAGEDIR}${DATADIR}
 .endfor
 	${MKDIR} ${STAGEDIR}${EXAMPLESDIR}
--- a/pkg-plist	Wed Aug 28 09:51:05 2024 +0200
+++ b/pkg-plist	Thu Aug 29 14:01:38 2024 +0200
@@ -9,6 +9,7 @@
 sbin/fpkg
 sbin/fzfs
 share/local-bsdtools/common.subr
+share/local-bsdtools/array.sh
 share/examples/local-bsdtools/freebsd-update-ftjail-template.sh
 share/examples/local-bsdtools/freebsd-update-ftjail.sh
 %%DOCS%%share/man/man5/bsmtp2dma.conf.5.gz
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/local-bsdtools/array.sh	Thu Aug 29 14:01:38 2024 +0200
@@ -0,0 +1,272 @@
+#!/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.
+#:
+#: Implementation hints:
+#:
+#: - Every array has a NAME
+#: - One-based indexing is used
+#: - Array elements are stored in a variable named <NAME>_<index-number>
+#: - The number of elements in the array ist stored in variable <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 <NAME>__ variable is a severe error and forces an immediate
+#:   error ``exit``
+#:
+
+
+#:
+#: 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
+#:
+#: Args:
+#:  $1 (str): The name of the array.
+#:            Must conform to shell variable naming conventions
+#:  $2... (optional): Optional initialization values
+#:
+#: It is assumed that the array does not exist already.
+#:
+array_new() {
+    local _name
+
+    local _el _l
+
+    [ $# -lt 1 ] && _array_fatal "missing array name"
+    _name=$1
+    shift
+
+    # Check whether the variable already exists
+    eval _l=\${${_name}__:-__UNSET__}
+
+    [ "${_l}" != "__UNSET__" ] && _array_fatal "array \`${_name}' already exists"
+    # Really create
+    eval ${_name}__=0
+
+    for _el in "$@"; do
+	array_append ${_name} "${_el}"
+    done
+}
+
+
+#:
+#: 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 _l
+
+    [ $# -lt 1 ] && _array_fatal "missing array name"
+    _name=$1
+
+    # Check whether the variable already exists
+    eval _l=\${${_name}__:-__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 _l _l1
+
+    [ $# -lt 1 ] && _array_fatal "missing array name"
+    _name=$1
+    _value="${2-}"
+
+    # Check whether the variable already exists
+    eval _l=\${${_name}__:-__UNSET__}
+    if [ "${_l}" = "__UNSET__" ]; then
+	_array_fatal "array \`${_name}' does not exist"
+    fi
+
+    _l1=$((${_l} + 1))
+    # Set value
+    eval ${_name}_${_l1}="\"${_value}\""
+    # Set new array length
+    eval ${_name}__=${_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 _l _value
+
+    [ $# -lt 1 ] && _array_fatal "missing array name"
+    _name=$1
+    [ $# -lt 2 ] && _array_fatal "missing array index"
+    _index=$2
+
+    # Check whether the variable already exists
+    eval _l=\${${_name}__:-__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=\"\${${_name}_${_index}}\"
+    printf "%s" "${_value}"
+}
+
+
+#:
+#: Destroy and unset an array and all its elements
+#:
+#: Args:
+#:   $1 (str): The name of an array. The array may exist or not.
+#:
+array_destroy() {
+    local _name
+
+    local _l _idx
+
+    [ $# -lt 1 ] && _array_fatal "missing array name"
+    _name=$1
+
+    # Handle non-existing array names
+    eval _l=\${${_name}__:-__UNSET__}
+    if [ "${_l}" = "__UNSET__" ]; then
+	return 0
+    fi
+    _idx=1
+    while [ ${_idx} -le ${_l} ]; do
+	eval unset ${_name}_${_idx}
+	_idx=$((${_idx} + 1))
+    done
+
+    # Remove
+    eval unset ${_name}__
+}
+
+
+#:
+#: 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.
+#:
+array_for_each() {
+    local _name _cb
+
+    local _l _idx _value _rv
+
+    [ $# -lt 1 ] && _array_fatal "missing array name"
+    _name=$1
+    [ $# -lt 2 ] && _array_fatal "missing callback function name"
+    _cb="$2"
+
+    # Check whether the variable already exists
+    eval _l=\${${_name}__:-__UNSET__}
+    if [ "${_l}" = "__UNSET__" ]; then
+	_array_fatal "array \`${_name}' does not exist"
+    fi
+
+    _idx=1
+    while [ ${_idx} -le ${_l} ]; do
+	eval "${_cb} ${_name} ${_idx} \"\${${_name}_${_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 _l _idx _value _rv
+
+    [ $# -lt 1 ] && _array_fatal "missing array name"
+    _name=$1
+    [ $# -lt 2 ] && _array_fatal "missing callback function name"
+    _cb="$2"
+
+    # Check whether the variable already exists
+    eval _l=\${${_name}__:-__UNSET__}
+    if [ "${_l}" = "__UNSET__" ]; then
+	_array_fatal "array \`${_name}' does not exist"
+    fi
+
+    _idx=${_l}
+    while [ ${_idx} -gt 0 ]; do
+	eval "${_cb} ${_name} ${_idx} \"\${${_name}_${_idx}}\""
+	_rv=$?
+	[ ${_rv} -ne 0 ] && return ${_rv}
+	_idx=$((${_idx} - 1))
+    done
+    return 0
+}