测试 shell 对数组的支持

测试 shell 对数组的支持

是否有一种简洁的方法可以在命令行上通过本地类似 Bourne 的 shell 测试数组支持?

这总是可能的:

$ arr=(0 1 2 3);if [ "${arr[2]}" != 2 ];then echo "No array support";fi

或测试$SHELLshell 版本:

$ eval $(echo "$SHELL --version") | grep version

然后阅读手册页,假设我有权访问它。 (即使在那里,写自/bin/bash,我假设所有类似 Bourne 的 shell 都承认长选项--version,当 ksh 中断时例如.)

我正在寻找一个简单的测试,可以无人值守并合并到用法脚本开头甚至调用之前的部分。

答案1

假设您想要限制为类似 Bourne 的 shell(许多其他 shell,例如、 、 或csh支持tcsh数组rc,但编写同时兼容类似 Bourne 的 shell 的脚本,这些很棘手并且通常毫无意义,因为它们是完全不同的解释器不兼容的语言),请注意实现之间存在显着差异。esfish

支持数组的类似 Bourne 的 shell(按添加支持的时间顺序排列)是:

  • ksh88(原始 ksh 的最后一次演变,第一个实现数组的 ksh88 仍然存在于ksh大多数传统商业 Unices 上,它也是 的基础sh

    • 数组是一维的
    • 如果您不能保证数组不会以or开头,则将数组定义为set -A array foo baror 。set -A array -- "$var" ...$var-+
    • 数组索引从 开始0
    • 各个数组元素被指定为a[1]=value
    • 数组是稀疏的。a[5]=foo即使a[0,1,2,3,4]没有设置,这也会起作用,并且会让它们保持未设置状态。
    • ${a[5]}访问索引 5 的元素(如果数组稀疏,则不一定是第 6 个元素)。可以5有任何算术表达式。
    • 数组大小和下标受到限制(最多 4096)。
    • ${#a[@]}是数组中指定元素的数量(不是最大的指定索引)。
    • 没有办法知道分配的下标列表(除了使用 单独测试 4096 个元素之外[[ -n "${a[i]+set}" ]])。
    • $a是相同的${a[0]}。也就是说,数组通过给标量变量提供额外的值来以某种方式扩展它们。
  • pdksh和衍生品(这是一些 BSD 的基础,ksh有时也是sh几个 BSD 的基础,并且是 ksh93 源代码被释放之前唯一的开源 ksh 实现):

    主要喜欢ksh88但请注意:

    • 一些旧的实现不支持set -A array -- foo bar, (--那里不需要)。
    • ${#a[@]}是最大分配索引的索引加一。 (a[1000]=1; echo "${#a[@]}"即使数组只有一个元素,也会输出 1001。
    • 在较新的版本中,数组大小不再受到限制(除了整数的大小)。
    • 的最新版本mksh有一些额外的运算符,其灵感来自于bashksh93zsh类似赋值 la a=(x y), a+=(z)${!a[@]}以获取分配的索引列表。
  • zsh。数组通常设计得更好,并且充分利用了数组zsh的优点。正如你可以看到的kshcsh1991 年发布 zsh 2.0,设计灵感来自于tcsh而不是ksh。它们与ksh数组有一些相似之处,但也有显着差异:

    • 索引从 1 开始,而不是从 0 开始(模拟除外ksh),这与 Bourne 数组(位置参数 $@,也zsh公开为其 $argv 数组)和csh数组一致。
    • 它们是与普通/标量变量不同的类型。运算符对它们的应用不同,就像您通常期望的那样。$a与数组不同,${a[0]}但扩展到数组的非空元素("${a[@]}"对于像 in 那样的所有元素ksh)。
    • 它们是普通数组,而不是稀疏数组。a[5]=1有效,但如果未分配,则将 1 到 4 的所有元素分配为空字符串。所以${#a[@]}(与ksh中索引0的元素的大小相同${#a})是数组中的元素数量最大的指定索引。
    • 支持关联数组。
    • 支持大量用于处理数组的运算符,数量太多,无法在此列出。
    • 数组定义为a=(x y).set -A a x y也适用于与 的兼容性ksh,但除非在 ksh 仿真中( zsh 仿真中不需要),set -A a -- x y否则不受支持。--
  • ksh93。 (此处描述最新版本)。 ,由原作者ksh93重写,长期以来一直被认为ksh实验性的由于它已作为 FOSS 发布,现在可以在越来越多的系统中找到。例如,它是/bin/sh(它取代了 Bourne shell,/usr/xpg4/bin/shPOSIX shell 仍然基于ksh88kshSolaris 11。它的阵列扩展并增强了 ksh88 的阵列。

    • a=(x y)可用于定义数组,但由于a=(...)也可用于定义复合变量 ( a=(foo=bar bar=baz)),因此a=()是不明确的,并且声明的是复合变量,而不是数组。
    • 数组是多维的 ( a=((0 1) (0 2))),数组元素也可以是复合变量 ( a=((a b) (c=d d=f)); echo "${a[1].c}")。
    • 可以使用语法a=([2]=foo [5]=bar)一次定义稀疏数组。
    • 最大数组索引提高到 4,194,303。
    • 虽然没有达到 的程度zsh,但也支持大量的运算符来操作数组。
    • "${!a[@]}"检索数组索引列表。
    • 关联数组也支持作为单独的类型。
  • bashbash是GNU 项目的外壳。它sh在最新版本的 OS/X 和一些 GNU/Linux 发行版上使用。bash数组主要模拟具有和ksh88的某些功能的数组。ksh93zsh

    • a=(x y)支持的。set -A a x y 不是支持的。a=()创建一个空数组( 中没有复合变量bash)。
    • "${!a[@]}"获取索引列表。
    • a=([foo]=bar)支持语法以及来自ksh93和 的其他一些语法zsh
    • 最新bash版本还支持关联数组作为单独的类型。
  • yash。它是一个相对较新的、干净的、多字节感知的 POSIX sh 实现。没有广泛使用。它的数组是另一个干净的 API,类似于zsh

    • 数组并不稀疏
    • 数组索引从 1 开始
    • 定义(和声明)为a=(var value)
    • array使用内置插入、删除或修改的元素
    • array -s a 5 value如果未事先分配第 5 个元素,则修改该元素将失败
    • 数组中元素的数量是${a[#]}${#a[@]}是列表中元素的大小。
    • 数组是一种单独的类型。您需要a=("$a")将标量变量重新定义为数组,然后才能添加或修改元素。
    • "$array"按原样扩展到数组的所有元素,这使得它们比在其他 shell 中更容易使用(与 ksh/bash/zsh相比,以数组的元素作为参数cmd "$array"进行调用;是关闭的,但剥离了空元素)。cmdcmd "${array[@]}"zshcmd $array
    • 作为 调用时不支持数组sh

因此,从中您可以看到检测数组支持,您可以这样做:

if (unset a; set -A a a; eval "a=(a b)"; eval '[ -n "${a[1]}" ]'
   ) > /dev/null 2>&1
then
  array_supported=true
else
  array_supported=false
fi

还不足以使用这些数组。您需要定义包装器命令来将数组分配为整体和单个元素,并确保您不会尝试创建稀疏数组。

喜欢

unset a
array_elements() { eval "REPLY=\"\${#$1[@]}\""; }
if (set -A a -- a) 2> /dev/null; then
  set -A a -- a b
  case ${a[0]}${a[1]} in
    --) set_array() { eval "shift; set -A $1"' "$@"'; }
        set_array_element() { eval "$1[1+(\$2)]=\$3"; }
        first_indice=0;;
     a) set_array() { eval "shift; set -A $1"' -- "$@"'; }
        set_array_element() { eval "$1[1+(\$2)]=\$3"; }
        first_indice=1;;
   --a) set_array() { eval "shift; set -A $1"' "$@"'; }
        set_array_element() { eval "$1[\$2]=\$3"; }
        first_indice=0;;
    ab) set_array() { eval "shift; set -A $1"' -- "$@"'; }
        set_array_element() { eval "$1[\$2]=\$3"; }
        first_indice=0;;
  esac
elif (eval 'a[5]=x') 2> /dev/null; then
  set_array() { eval "shift; $1=("'"$@")'; }
  set_array_element() { eval "$1[\$2]=\$3"; }
  first_indice=0
elif (eval 'a=(x) && array -s a 1 y && [ "${a[1]}" = y ]') 2> /dev/null; then
  set_array() { eval "shift; $1=("'"$@")'; }
  set_array_element() {
    eval "
      $1=(\${$1+\"\${$1[@]}"'"})
      while [ "$(($2))" -ge  "${'"$1"'[#]}" ]; do
        array -i "$1" "$2" ""
      done'
    array -s -- "$1" "$((1+$2))" "$3"
   }
  array_elements() { eval "REPLY=\${$1[#]}"; }
  first_indice=1
else
  echo >&2 "Array not supported"
fi

然后,您可以使用 访问数组元素"${a[$first_indice+n]}",使用整个列表,"${a[@]}"并使用包装函数 ( array_elements, set_array, set_array_element) 来获取数组的元素数量 (in $REPLY),将数组设置为一个整体或分配单个元素。

可能不值得付出努力。我会使用perl或限制 Bourne/POSIX shell 数组:"$@"

如果目的是让用户的交互式 shell 获取一些文件来定义内部使用数组的函数,那么这里还有一些可能有用的注释。

您可以将zsh数组配置为更像ksh本地范围内的数组(在函数或匿名函数中)。

myfunction() {
  [ -z "$ZSH_VERSION" ] || setopt localoption ksharrays
  # use arrays of indice 0 in this function
}

您还可以通过以下方式进行模拟ksh(提高与ksh数组和其他几个区域的兼容性):

myfunction() {
  [ -z "$ZSH_VERSION" ] || emulate -L ksh
  # ksh code more likely to work here
}

考虑到这一点,您愿意放弃对 和 和旧版本衍生品的支持yashksh88并且pdksh只要您不尝试创建稀疏数组,您应该能够始终如一地使用:

  • a[0]=foo
  • a=(foo bar)(但不是a=()
  • "${a[#]}", "${a[@]}","${a[0]}"

在那些具有 的函数中emulate -L ksh,而zsh用户仍然通常以 zsh 方式使用他/她的数组。

答案2

您可以使用eval尝试数组语法:

is_array_support() (
  eval 'a=(1)'
) >/dev/null 2>&1

if is_array_support; then
  echo support
else
  echo not
fi

相关内容