Tes untuk dukungan array oleh shell

12

Apakah ada cara ringkas pengujian untuk dukungan array oleh shell Bourne-like lokal di baris perintah?

Ini selalu mungkin:

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

atau pengujian untuk $SHELLdan versi shell:

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

dan kemudian membaca halaman manual, dengan asumsi saya memiliki akses ke sana. (Bahkan di sana, menulis dari /bin/bash, saya mengasumsikan bahwa semua shell Bourne-like mengakui opsi panjang --version, ketika itu istirahat untuk ksh misalnya .)

Saya mencari tes sederhana yang dapat dilakukan tanpa pengawasan dan dimasukkan dalam bagian Penggunaan di awal skrip atau bahkan sebelum menyebutnya.

Cbhihe
sumber
Saya menganggap Anda ingin membatasi untuk kerang seperti Bourne?
Stéphane Chazelas
@ StéphaneChazelas: Ya, jika Anda maksud (tidak lengkap) grup inti terbuat dari: sh, csh, ksh, tcsh, bash, zsh dan teman dekat. Saya tidak tahu di mana posisi yash dalam konstelasi ini.
Cbhihe
2
cshbukan shell bourne. tcshjuga bukan salah satu ( cshdengan beberapa bug diperbaiki)
cas
1
Perhatikan bahwa itu $SHELLadalah shell yang disukai pengguna, seperti $EDITOReditor teks pilihannya. Ini tidak ada hubungannya dengan shell yang sedang berjalan.
Stéphane Chazelas
1
evalmenggunakan output $SHELL --versionsebagai kode shell tidak masuk akal.
Stéphane Chazelas

Jawaban:

12

Dengan asumsi Anda ingin membatasi untuk Bourne-seperti kerang (banyak kerang lainnya seperti csh, tcsh, rc, esatau fisharray dukungan tetapi menulis naskah kompatibel pada saat yang sama untuk Bourne-seperti kerang dan mereka adalah rumit dan umumnya sia-sia karena mereka interpreter untuk benar-benar berbeda dan bahasa yang tidak kompatibel), perhatikan bahwa ada perbedaan signifikan antara implementasi.

Shell Bourne like yang mendukung array adalah:

  • ksh88(itulah array implementasi pertama, ksh88 masih ditemukan kshpada kebanyakan Unite komersial tradisional yang juga menjadi dasar untuk sh)

    • array adalah satu dimensi
    • Array didefinisikan sebagai set -A array foo baratau set -A array -- "$var" ...jika Anda tidak dapat menjamin bahwa $vartidak akan mulai dengan -atau +.
    • Indeks array mulai dari 0.
    • Elemen array individual ditetapkan sebagai a[1]=value.
    • array jarang. Itu a[5]=fooakan bekerja bahkan jika a[0,1,2,3,4]tidak disetel dan akan membiarkannya tidak disetel.
    • ${a[5]}untuk mengakses elemen indice 5 (belum tentu elemen ke-6 jika array jarang). The 5bisa ada ekspresi aritmatika.
    • ukuran dan subskrip array terbatas (hingga 4096).
    • ${#a[@]} adalah jumlah elemen yang ditugaskan dalam array (bukan indice yang ditugaskan terbesar).
    • tidak ada cara untuk mengetahui daftar langganan yang ditugaskan (selain menguji 4096 elemen secara individual dengan [[ -n "${a[i]+set}" ]]).
    • $asama dengan ${a[0]}. Itu adalah array yang entah bagaimana memperluas variabel skalar dengan memberi mereka nilai tambahan.
  • pdkshdan turunannya (itulah dasar untuk kshdan terkadang shbeberapa BSD dan merupakan satu-satunya implementasi ksh opensource sebelum sumber ksh93 dibebaskan):

    Paling suka ksh88tapi perhatikan:

    • Beberapa implementasi lama tidak mendukung set -A array -- foo bar, ( --tidak diperlukan di sana).
    • ${#a[@]}adalah salah satu plus indice dari indice ditugaskan terbesar. ( a[1000]=1; echo "${#a[@]}"output 1001 meskipun array hanya memiliki satu elemen.
    • dalam versi yang lebih baru, ukuran array tidak lagi terbatas (selain oleh ukuran bilangan bulat).
    • versi terbaru dari mkshmemiliki operator beberapa tambahan terinspirasi dari bash, ksh93atau zshseperti tugas a la a=(x y), a+=(z), ${!a[@]}untuk mendapatkan daftar indeks ditugaskan.
  • zsh. zsharray umumnya dirancang lebih baik dan mengambil yang terbaik dari kshdan csharray. Mereka mirip kshtetapi dengan perbedaan signifikan:

    • indeks mulai dari 1, bukan 0 (kecuali dalam kshemulasi), itu konsisten dengan array Bourne (parameter posisi $ @, yang zshjuga mengekspos sebagai array $ argv) dan csharray.
    • mereka adalah tipe yang terpisah dari variabel normal / skalar. Operator berlaku berbeda untuk mereka dan seperti yang biasanya Anda harapkan. $atidak sama dengan ${a[0]}tetapi memperluas ke elemen array yang tidak kosong ( "${a[@]}"untuk semua elemen seperti di ksh).
    • mereka adalah array normal, bukan array jarang. a[5]=1berfungsi tetapi menetapkan semua elemen dari 1 hingga 4 string kosong jika tidak ditetapkan. Jadi ${#a[@]}(sama seperti ${#a}yang di ksh adalah ukuran elemen indice 0) adalah jumlah elemen dalam array dan indice ditugaskan terbesar.
    • array asosiatif didukung.
    • sejumlah besar operator untuk bekerja dengan array didukung, terlalu besar untuk disebutkan di sini.
    • array didefinisikan sebagai a=(x y). set -A a x yjuga berfungsi, tetapi set -A a -- x ytidak didukung kecuali dalam emulasi ksh ( --tidak diperlukan dalam emulasi zsh).
  • ksh93. (di sini menjelaskan versi terbaru). ksh93, eksperimental lama dianggap sekarang dapat ditemukan di semakin banyak sistem sekarang yang telah dirilis sebagai FOSS. Sebagai contoh, itu adalah /bin/sh(di mana ia menggantikan Bourne shell, /usr/xpg4/bin/sh, POSIX shell masih didasarkan pada ksh88) dan kshdari Solaris 11. Susunannya memperpanjang dan meningkatkan ksh88.

    • a=(x y)dapat digunakan untuk mendefinisikan array, tetapi karena a=(...)juga digunakan untuk mendefinisikan variabel gabungan ( a=(foo=bar bar=baz)), a=()bersifat mendua dan mendeklarasikan variabel gabungan, bukan array.
    • array multi-dimensional ( a=((0 1) (0 2))) dan elemen array juga bisa menjadi variabel majemuk ( a=((a b) (c=d d=f)); echo "${a[1].c}").
    • Sebuah a=([2]=foo [5]=bar)sintaks dapat digunakan untuk mendefinisikan array jarang sekaligus.
    • Keterbatasan ukuran diangkat.
    • Tidak sampai batas tertentu zsh, tetapi sejumlah besar operator didukung juga untuk memanipulasi array.
    • "${!a[@]}" untuk mengambil daftar indeks array.
    • array asosiatif juga didukung sebagai tipe terpisah.
  • bash. bashadalah shell dari proyek GNU. Ini digunakan seperti shpada versi OS / X terbaru dan beberapa distribusi GNU / Linux. basharray sebagian besar meniru ksh88yang dengan beberapa fitur ksh93dan zsh.

    • a=(x y)didukung. set -A a x y tidak didukung. a=()membuat array kosong (tidak ada variabel majemuk di bash).
    • "${!a[@]}" untuk daftar indeks.
    • a=([foo]=bar)sintaks yang didukung serta beberapa lainnya dari ksh93dan zsh.
    • bashversi terbaru juga mendukung array asosiatif sebagai tipe terpisah.
  • yash. Ini adalah implementasi POSIX sh yang relatif baru, bersih, multi-byte yang disadari. Tidak digunakan secara luas. Arraynya adalah API bersih lain yang mirip denganzsh

    • array tidak jarang
    • Indeks array mulai dari 1
    • didefinisikan (dan dideklarasikan) dengan a=(var value)
    • elemen dimasukkan, dihapus atau dimodifikasi dengan arraybuiltin
    • array -s a 5 valueuntuk memodifikasi elemen ke- 5 akan gagal jika elemen itu tidak ditetapkan sebelumnya.
    • jumlah elemen dalam array adalah ${a[#]}, ${#a[@]}menjadi ukuran elemen sebagai daftar.
    • array adalah tipe yang terpisah. Anda perlu a=("$a")mendefinisikan ulang variabel skalar sebagai array sebelum Anda dapat menambah atau memodifikasi elemen.
    • array tidak didukung ketika dipanggil sebagai sh.

Jadi, dari situ Anda dapat melihat bahwa mendeteksi dukungan array, yang dapat Anda lakukan dengan:

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

tidak cukup untuk dapat menggunakan array itu. Anda perlu mendefinisikan perintah wrapper untuk menetapkan array sebagai elemen keseluruhan dan individual, dan pastikan Anda tidak mencoba membuat array jarang.

Suka

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

Dan kemudian Anda mengakses elemen array dengan "${a[$first_indice+n]}", seluruh daftar dengan "${a[@]}"dan menggunakan fungsi wrapper ( array_elements, set_array, set_array_element) untuk mendapatkan jumlah elemen array (dalam $REPLY), mengatur array sebagai keseluruhan atau menetapkan unsur-unsur individu.

Mungkin tidak sepadan dengan usaha. Saya akan menggunakan perlatau batasan untuk Bourne / POSIX shell array yang: "$@".

Jika maksudnya untuk memiliki beberapa file yang akan dipasok oleh shell interaktif pengguna untuk mendefinisikan fungsi yang secara internal menggunakan array, berikut adalah beberapa catatan yang mungkin berguna.

Anda dapat mengonfigurasi zsharray menjadi lebih seperti ksharray di lingkup lokal (dalam fungsi atau fungsi anonim).

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

Anda juga dapat meniru ksh(meningkatkan kompatibilitas dengan kshuntuk array dan beberapa area lainnya) dengan:

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

Dengan mengingat hal itu dan Anda bersedia melepaskan dukungan untuk yashdan ksh88dan versi pdkshturunan yang lebih lama, dan selama Anda tidak mencoba membuat array jarang, Anda harus dapat menggunakan:

  • a[0]=foo
  • a=(foo bar)(tapi tidak a=())
  • "${a[#]}", "${a[@]}","${a[0]}"

dalam fungsi-fungsi yang memiliki emulate -L ksh, sementara zshpengguna masih menggunakan array nya biasanya dengan cara zsh.

Stéphane Chazelas
sumber
7

Anda dapat menggunakan evaluntuk mencoba sintaks array:

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

if is_array_support; then
  echo support
else
  echo not
fi
cuonglm
sumber
2
ksh88mendukung array tetapi tidak a=(). Di ksh93, a=()mendeklarasikan variabel gabungan, bukan array kecuali variabel tersebut telah dideklarasikan sebagai array sebelumnya.
Stéphane Chazelas
2
Perhatikan juga bahwa ada perbedaan yang signifikan antara implementasi array. Sebagai contoh, beberapa memiliki indeks array mulai dari 0 (bash, ksh, zsh di emulasi ksh), beberapa dimulai pada satu (zsh, yash). Beberapa array / daftar normal, beberapa array jarang (array asosiatif dengan kunci terbatas pada bilangan bulat positif seperti di ksh atau bash).
Stéphane Chazelas
Dalam yash, Anda tidak melakukan a[5]=1tetapiarray -s a 5 1
Stéphane Chazelas
@ StéphaneChazelas: terima kasih atas langkah-langkahnya. Dalam kasus saya semuanya bermuara pada apakah array (asosiatif atau tidak) didukung sama sekali. Detail tentang basis-indeks dapat dengan mudah dikerjakan bahkan dalam skrip yang dimaksudkan untuk dijalankan tanpa pengawasan.
Cbhihe
@ StéphaneChazelas: Variabel majemuk di ksh93membuat saya terkejut, maukah Anda memberi saya bagian dari dokumentasi tentang hal itu. Saya menambahkan 1ke array untuk membuatnya berfungsi.
cuonglm