通过外壳测试对阵列的支持


12

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

这始终是可能的:

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

或测试$SHELL和shell版本:

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

然后,假设我可以访问它,则阅读手册页。(即使从那里开始/bin/bash,我也假设所有类似Bourne的shell都接受long选项--version,例如,对于ksh 来说,这样做会导致中断。)

我正在寻找一个简单的测试,可以在脚本开始时甚至在调用它之前将其无人值守并纳入“ 用法”部分。


我假设您想限制使用像伯恩一样的贝壳?
斯特凡Chazelas

@StéphaneChazelas:是的,如果您(不是穷尽地)是指由sh,csh,ksh,tcsh,bash,zsh和密友组成的核心小组。我不知道鱼在这个星座中的位置。
Cbhihe 2015年

2
csh不是本恩壳。 tcsh也不是一个(已csh修复了一些错误)
cas

1
请注意,这$SHELL是用户的首选外壳程序,就像$EDITOR他的首选文本编辑器一样。它与当前正在运行的shell无关。
斯特凡Chazelas

1
eval确定$SHELL --versionas shell代码的输出是没有意义的。
斯特凡Chazelas

Answers:


12

假设你要限制对类似Bourne外壳(许多其他炮弹一样cshtcshrcesfish支持的阵列,但在同一时间编写脚本兼容的Bourne像壳以及那些是棘手的,通常毫无意义的,因为它们是口译完全不同的,不兼容的语言),请注意,实现之间存在重大差异。

支持数组的Bourne类shell是:

  • ksh88(这是第一个实现数组,ksh88仍然是ksh大多数传统的商业Unices上的应用,也是它的基础sh

    • 数组是一维的
    • 数组定义为,set -A array foo bar或者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 和衍生产品(这是 kshsh是多个BSD,有时是多个BSD并且是在释放ksh93源之前唯一的开源ksh实现):

    通常喜欢,ksh88但请注意:

    • 一些旧的实现不支持set -A array -- foo bar,(--那里并不需要)。
    • ${#a[@]}是最大分配的索引中的一个。(a[1000]=1; echo "${#a[@]}"即使阵列只有一个元素,也会输出1001。
    • 在较新的版本中,数组大小不再受限制(除了整数大小)。
    • 最近的版本mksh具有启发了一些额外的运营商bashksh93zsh像分配一拉a=(x y)a+=(z)${!a[@]}获得分配的索引列表。
  • zshzsh数组通常设计得更好,并会充分利用kshand csh数组。它们类似于ksh但有显着差异:

    • 索引从1开始,而不是0(在ksh仿真中除外),这与Bourne数组(位置参数$ @,zsh也作为其$ argv数组公开)和csh数组一致。
    • 它们是与普通/标量变量不同的类型。运营商对他们的应用方式有所不同,就像您通常期望的那样。与数组$a不同,${a[0]}但扩展为数组的非空元素("${a[@]}"对于in中的所有元素ksh)。
    • 它们是普通数组,而不是稀疏数组。a[5]=1可以,但是如果未分配,则将1到4的所有元素分配为空字符串。因此${#a[@]}(与${#a}ksh中indice 0元素的大小相同)是数组中元素的数量分配的最大indice。
    • 支持关联数组。
    • 支持使用数组的大量运算符,太大了,无法在此处列出。
    • 定义为的数组a=(x y)set -A a x y也可以使用,但是set -A a -- x y不支持,除非在ksh仿真中(--在zsh仿真中不需要)。
  • ksh93。(此处介绍最新版本)。ksh93,由于它已经作为FOSS发布,因此在越来越多的系统中可以找到长期以来被认为是实验性的项目。例如,它的/bin/sh(它取代Bourne shell的/usr/xpg4/bin/sh,POSIX的外壳仍然是以ksh88)和kshSolaris 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)语法可用于一次定义稀疏数组。
    • 尺寸限制解除。
    • 并非达到的程度zsh,但也支持大量的运算符来操纵数组。
    • "${!a[@]}" 检索数组索引列表。
    • 关联数组也支持作为单独的类型。
  • bashbash是GNU项目的外壳。它sh在最新版本的OS / X和某些GNU / Linux发行版中使用。bash数组大多模拟ksh88具有ksh93和特性的数组zsh

    • 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")先将标量变量重新定义为数组,然后才能添加或修改元素。
    • 以调用时不支持数组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_elementsset_arrayset_array_element),以获得(在一个数组的元素的数量$REPLY),设置数组作为一个整体或指定单个元素。

可能不值得付出努力。我会使用perl或限制Bourne / POSIX 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方式使用其数组。


7

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

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

if is_array_support; then
  echo support
else
  echo not
fi

2
ksh88支持数组,但不支持a=()。在中ksh93a=()声明一个复合变量,而不是一个数组,除非事先将该变量声明为数组。
斯特凡Chazelas

2
还要注意,数组实现之间存在显着差异。例如,有些数组索引从0开始(在ksh仿真中为bash,ksh,zsh),有些数组索引从1开始(zsh,yash)。有些是普通数组/列表,有些是稀疏数组(键限制为正整数的关联数组,如ksh或bash)。
斯特凡Chazelas

yash,你不这样做a[5]=1,但array -s a 5 1
斯特凡Chazelas

@StéphaneChazelas:感谢您的精确度。就我而言,一切归结为是否完全支持数组(关联的或不关联的)。即使在旨在无人值守的脚本中,也可以轻松得出有关基于索引的详细信息。
Cbhihe

@StéphaneChazelas:复合变量ksh93让我感到惊讶,您介意为我提供有关它的部分文档。我添加1到数组以使其工作。
cuonglm
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.