Shell-Script

通過 shell 測試對數組的支持

  • November 17, 2021

是否有一種簡潔的方法可以在命令行中通過本地 Bourne-like shell 測試陣列支持?

這總是可能的:

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

或測試$SHELL和外殼版本:

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

然後閱讀手冊頁,假設我可以訪問它。(即使在那裡,從 寫作/bin/bash,我假設所有類似 Bourne 的 shell 都承認 long 選項,例如--version,當 ksh 中斷時。)

我正在尋找一個簡單的測試,它可以在腳本開始時甚至在呼叫它之前無人看管並合併到使用部分中。

假設您想限制為類 Bourne 的 shell(許多其他 shell,如cshtcsh、或支持數組rc,但同時編寫一個與類 Bourne 的 shell 兼容的腳本,這些是棘手且通常毫無意義的,因為它們是完全不同的解釋器和不兼容的語言),請注意實現之間存在顯著差異。es``fish

支持數組的 Bourne like shell(按添加支持的時間順序)是:

  • ksh88(原始 ksh 的最後一次演變,第一個實現數組,ksh88 仍然存在ksh於大多數傳統的商業 Unices 上,它也是 的基礎sh

    • 數組是一維的
    • 數組被定義為set -A array foo barorset -A array -- "$var" ...如果你不能保證它$var不會以 a -or開頭+
    • 數組索引從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類似賦值 a la a=(x y), a+=(z)${!a[@]}以獲取已分配索引的列表。
  • zsh. zsh數組通常設計得更好,並充分利用了kshcsh數組。從1991 年的 zsh 2.0 公告中可以看出,該設計的靈感來自 tcsh 而不是 ksh。它們與ksh數組有一些相似之處,但有顯著差異:

    • 索引從 1 開始,而不是 0(在ksh仿真中除外),這與 Bourne 數組(位置參數 $@,它zsh也作為其 $argv 數組公開)和csh數組一致。
    • 它們是與普通/標量變數不同的類型。運算符對它們的應用不同,就像您通常期望的那樣。$a與數組的非空元素不同,${a[0]}但擴展到數組的非空元素("${a[@]}"對於 in 等所有元素ksh)。
    • 它們是普通數組,而不是稀疏數組。a[5]=1有效,但如果未分配從 1 到 4 的所有元素,則將其分配為空字元串。因此(與ksh 中的${#a[@]}相同是索引 0 的元素的大小)是數組中的元素數分配的最大索引。${#a}
    • 支持關聯數組。
    • 支持大量使用數組的運算符,此處無法列出。
    • 數組定義為a=(x y). set -A a x y也適用於兼容ksh,但set -A a -- x y不支持,除非在 ksh 仿真中(--在 zsh 仿真中不需要)。
  • ksh93. (此處描述最新版本)。ksh93ksh由原作者重寫,長期以來被認為是實驗性的,現在可以在越來越多的系統中找到它,因為它已作為 FOSS 發布。例如,它是/bin/sh(它取代了 Bourne shell,/usr/xpg4/bin/shPOSIX shell 仍然基於ksh88)和kshof Solaris 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[@]}"檢索數組索引列表。
    • 關聯數組也支持作為單獨的類型。
  • bash. bash是 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")將標量變數重新定義為數組,然後才能添加或修改元素。
    • "$array"按原樣擴展到數組的所有元素,這使得它們比在其他 shell 中更容易使用(與在 ksh/bash/zsh 中相比,使用數組的元素作為參數進行cmd "$array"呼叫; ‘s接近但剝離空元素)。cmd``cmd "${array[@]}"``zsh``cmd $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(提高與kshfor 數組和其他幾個領域的兼容性):

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 方式使用他/她的數組的函式中。

引用自:https://unix.stackexchange.com/questions/238080