Periksa variabel apakah array di Bourne like shell?

14

Di Bourne like shell yang mendukung variabel array, kita bisa menggunakan beberapa parsing untuk memeriksa apakah variabel adalah array.

Semua perintah di bawah ini dijalankan setelah dijalankan a=(1 2 3).

zsh:

$ declare -p a
typeset -a a
a=( 1 2 3 )

bash:

$ declare -p a
declare -a a='([0]="1" [1]="2" [2]="3")'

ksh93:

$ typeset -p a
typeset -a a=(1 2 3)

pdksh dan turunannya:

$ typeset -p a
set -A a
typeset a[0]=1
typeset a[1]=2
typeset a[2]=3

yash:

$ typeset -p a
a=('1' '2' '3')
typeset a

Contoh dalam bash:

if declare -p var 2>/dev/null | grep -q 'declare -a'; then
  echo array variable
fi

Pendekatan ini terlalu banyak bekerja dan perlu menelurkan subkulit. Menggunakan shell builtin lain seperti =~dalam [[ ... ]]tidak perlu subkulit, tetapi masih terlalu rumit.

Apakah ada cara yang lebih mudah untuk menyelesaikan tugas ini?

cuonglm
sumber
Dalam keadaan apa Anda perlu memeriksa apakah variabel Anda adalah array atau tidak?
Kusalananda

Jawaban:

10

Saya tidak berpikir Anda bisa, dan saya tidak berpikir itu benar-benar membuat perbedaan.

unset a
a=x
echo "${a[0]-not array}"

x

Itu melakukan hal yang sama di salah satu ksh93dan bash. Sepertinya semua variabel adalah array di shell tersebut, atau setidaknya variabel biasa yang belum diberi atribut khusus, tetapi saya tidak memeriksa banyak hal.

The bashpembicaraan pengguna tentang perilaku yang berbeda untuk berbagai versus variabel string ketika menggunakan +=tugas, tapi setelah itu pagar dan menyatakan bahwa array hanya berperilaku berbeda dalam senyawa konteks tugas.

Ini juga menyatakan bahwa variabel dianggap sebagai array jika ada subskrip yang diberi nilai - dan secara eksplisit menyertakan kemungkinan null-string. Di atas Anda dapat melihat bahwa penugasan reguler pasti menghasilkan subskrip yang ditugaskan - dan saya rasa semuanya adalah array.

Secara praktis, mungkin Anda dapat menggunakan:

[ 1 = "${a[0]+${#a[@]}}" ] && echo not array

... untuk menunjukkan dengan jelas variabel set yang hanya ditugaskan satu subskrip nilai 0.

mikeserv
sumber
Jadi saya kira memeriksa apakah ${a[1]-not array}dapat melakukan tugas, bukan?
cuonglm
@cuonglm - Ya, tidak sesuai dengan bashmanual: Sebuah variabel array dianggap disetel jika sebuah subskrip telah diberi nilai. String nol adalah nilai yang valid. Jika ada subscript yang diberikan array per spec. Dalam latihan, juga tidak, karena Anda bisa melakukannya a[5]=x. Saya kira [ 1 -eq "${#a[@]}" ] && [ -n "${a[0]+1}" ]bisa bekerja.
mikeserv
6

Jadi Anda ingin hanya bagian tengah declare -ptanpa sampah di sekitarnya?

Anda dapat menulis makro seperti:

readonly VARTYPE='{ read __; 
       case "`declare -p "$__"`" in
            "declare -a"*) echo array;; 
            "declare -A"*) echo hash;; 
            "declare -- "*) echo scalar;; 
       esac; 
         } <<<'

sehingga Anda dapat melakukan:

a=scalar
b=( array ) 
declare -A c; c[hashKey]=hashValue;
######################################
eval "$VARTYPE" a #scalar
eval "$VARTYPE" b #array
eval "$VARTYPE" c #hash

(Fungsi belaka tidak akan berfungsi jika Anda ingin menggunakannya pada variabel fungsi-lokal).


Dengan alias

shopt -s expand_aliases
alias vartype='eval "$VARTYPE"'

vartype a #scalar
vartype b #array
vartype c #hash
PSkocik
sumber
@ mikeserv Poin yang bagus. Alias ​​membuatnya terlihat lebih cantik. +1
PSkocik
maksud saya - alias vartype="$VARTYPE"... atau tidak mendefinisikan $VARTYPEsama sekali - itu harus bekerja, kan? Anda hanya perlu memasukkan shopthal bashitu karena tidak sesuai dengan spesifikasi mengenai aliasekspansi skrip.
mikeserv
1
@ mikeserv Saya yakin cuonglm juga mampu menyesuaikan pendekatan ini dengan kebutuhan dan kesukaannya. ;-)
PSkocik
... dan pertimbangan keamanan.
PSkocik
Tidak ada kode apakah di atas eval pengguna menyediakan teks. Ini tidak kalah aman dari suatu fungsi. Saya belum pernah melihat Anda sibuk membuat fungsi read-only, tapi OK, saya bisa menandai variabel readonly.
PSkocik
6

Dalam zsh

zsh% a=(1 2 3) s=1
zsh% [[ ${(t)a} == *array* ]] && echo array
array
zsh% [[ ${(t)s} == *array* ]] && echo array
zsh%
llua
sumber
Mungkin echo ${(t)var}lebih sederhana. Terima kasih untuk ini.
4

Untuk menguji var variabel, dengan

b=("${!var[@]}")
c="${#b[@]}"

Adalah mungkin untuk menguji apakah ada lebih dari satu indeks array:

[[ $c > 1 ]] && echo "Var is an array"

Jika nilai indeks pertama bukan nol:

[[ ${b[0]} -eq 0 ]] && echo "Var is an array"      ## should be 1 for zsh.

Satu-satunya kebingungan adalah ketika hanya ada satu nilai indeks dan nilai itu nol (atau satu).

Untuk kondisi itu, dimungkinkan untuk menggunakan efek samping dari mencoba menghapus elemen array dari variabel yang bukan array:

**bash** reports an error with             unset var[0]
bash: unset: var: not an array variable

**zsh** also reports an error with         $ var[1]=()
attempt to assign array value to non-array

Ini berfungsi dengan benar untuk bash:

# Test if the value at index 0 could be unset.
# If it fails, the variable is not an array.
( unset "var[0]" 2>/dev/null; ) && echo "var is an array."

Untuk zsh, indeks mungkin harus 1 (kecuali mode yang kompatibel aktif).

Sub-shell diperlukan untuk menghindari efek samping dari menghapus indeks 0 dari var.

Saya tidak menemukan cara untuk membuatnya bekerja di ksh.

Edit 1

Fungsi ini hanya berfungsi di bash4.2 +

getVarType(){
    varname=$1;
    case "$(typeset -p "$varname")" in
        "declare -a"*|"typeset -a"*)    echo array; ;;
        "declare -A"*|"typeset -A"*)    echo hash; ;;
        "declare -- "*|"typeset "$varname*| $varname=*) echo scalar; ;;
    esac;
}

var=( foo bar );  getVarType var

Edit 2

Ini juga berfungsi hanya untuk bash4.2 +

{ typeset -p var | grep -qP '(declare|typeset) -a'; } && echo "var is an array"

Catatan: Ini akan memberikan positif palsu jika var berisi string yang diuji.


sumber
Bagaimana dengan array dengan elemen nol?
cuonglm
1
Dat mengedit, tho. Terlihat sangat asli. : D
PSkocik
@cuonglm Cek ( unset "var[0]" 2>/dev/null; ) && echo "var is an array."dengan benar melaporkan var adalah array ketika var telah diatur ke var=()array dengan nol elemen. Kerjanya persis sama dengan menyatakan.
Tes untuk skalar tidak akan berfungsi jika skalar diekspor atau bertanda integer / huruf kecil / hanya baca ... Anda mungkin dapat dengan aman membuatnya bahwa output non-kosong lainnya berarti variabel skalar. Saya akan menggunakan grep -Ealih-alih grep -Puntuk menghindari ketergantungan pada GNU grep.
Stéphane Chazelas
@ StéphaneChazelas Tes (di bash) untuk skalar dengan bilangan bulat dan / atau huruf kecil dan / atau dibaca selalu mulai dengan -a, seperti ini: declare -airl var='()'. Karenanya tes grep akan bekerja .
3

Untuk bash , ini sedikit hack (meskipun didokumentasikan): upaya typesetuntuk menghapus atribut "array":

$ typeset +a BASH_VERSINFO
bash: typeset: BASH_VERSINFO: cannot destroy array variables in this way
echo $?
1

(Anda tidak dapat melakukan ini di zsh, ini memungkinkan Anda untuk mengubah array menjadi skalar, di bashdalamnya secara eksplisit dilarang.)

Begitu:

 typeset +A myvariable 2>/dev/null || echo is assoc-array
 typeset +a myvariable 2>/dev/null || echo is array

Atau dalam suatu fungsi, mencatat peringatan pada akhirnya:

function typeof() {
    local _myvar="$1"
    if ! typeset -p $_myvar 2>/dev/null ; then
        echo no-such
    elif ! typeset -g +A  $_myvar 2>/dev/null ; then
        echo is-assoc-array
    elif ! typeset -g +a  $_myvar 2>/dev/null; then
        echo is-array
    else
        echo scalar
    fi
}

Perhatikan penggunaan typeset -g(bash-4.2 atau yang lebih baru), ini diperlukan dalam suatu fungsi agar typeset(syn. declare) Tidak berfungsi seperti localdan merusak nilai yang Anda coba periksa. Ini juga tidak menangani fungsi "variabel" jenis, Anda dapat menambahkan tes cabang lain menggunakan typeset -fjika diperlukan.


Opsi lain (hampir selesai) adalah menggunakan ini:

    ${!name[*]}
          If name is an array variable, expands to  the  list
          of  array indices (keys) assigned in name.  If name
          is not an array, expands to 0 if name  is  set  and
          null  otherwise.   When @ is used and the expansion
          appears within double quotes, each key expands to a
          separate word.

Namun ada satu masalah, array dengan satu subskrip 0 cocok dengan dua kondisi di atas. Ini adalah sesuatu yang mikeserv juga referensi, bash benar-benar tidak memiliki perbedaan yang sulit, dan beberapa di antaranya (jika Anda memeriksa Changelog) dapat disalahkan pada ksh dan kompatibel dengan bagaimana ${name[*]}atau ${name[@]}berperilaku pada non-array.

Jadi solusi parsial adalah:

if [[ ${!BASH_VERSINFO[*]} == '' ]]; then
    echo no-such
elif [[ ${!BASH_VERSINFO[*]} == '0' ]]; then 
    echo not-array
elif [[ ${!BASH_VERSINFO[*]} != '0' ]]; 
    echo is-array    
fi

Saya telah menggunakan variasi sebelumnya:

while read _line; do
   if [[ $_line =~ ^"declare -a" ]]; then 
     ...
   fi 
done < <( declare -p )

ini juga membutuhkan subkulit.

Satu lagi teknik yang mungkin bermanfaat adalah compgen:

compgen -A arrayvar

Ini akan mencantumkan semua array yang diindeks, namun array asosiatif tidak ditangani secara khusus (hingga bash-4.4) dan muncul sebagai variabel reguler ( compgen -A variable)

mr.spuratic
sumber
The typeset +ajuga melaporkan kesalahan dalam ksh. Tapi tidak dalam zsh.
1

Jawaban singkat:

Untuk dua shell yang memperkenalkan notasi ini ( bashdan ksh93) variabel skalar hanyalah sebuah array dengan elemen tunggal .

Tidak perlu deklarasi khusus untuk membuat array. Hanya tugas sudah cukup, dan tugas polos var=valueidentik dengan var[0]=value.

Henk Langeveld
sumber
Cobalah: bash -c 'unset var; var=foo; typeset -p var'. Apakah bash answer melaporkan array (membutuhkan -a) ?. Sekarang bandingkan dengan: bash -c 'unset var; var[12]=foo; typeset -p var'. Mengapa ada perbedaan? A: Shell mempertahankan (baik atau buruk) gagasan yang vars adalah skalar atau array. Shell ksh menggabungkan kedua konsep menjadi satu.
1

yash's arraybuiltin memiliki beberapa opsi yang hanya berfungsi dengan variabel array. Contoh: -dopsi akan melaporkan kesalahan pada variabel non-array:

$ a=123
$ array -d a
array: no such array $a

Jadi kita bisa melakukan sesuatu seperti ini:

is_array() (
  array -d -- "$1"
) >/dev/null 2>&1

a=(1 2 3)
if is_array a; then
  echo array
fi

b=123
if ! is_array b; then
  echo not array
fi

Pendekatan ini tidak akan berfungsi jika variabel array dibaca hanya . Mencoba mengubah variabel hanya baca yang mengarah ke kesalahan:

$ a=()
$ readonly a
$ array -d a
array: $a is read-only
cuonglm
sumber
0
#!/bin/bash

var=BASH_SOURCE

[[ "$(declare -pa)" =~ [^[:alpha:]]$var= ]]

case "$?" in 
  0)
      echo "$var is an array variable"
      ;;
  1)
      echo "$var is not an array variable"
      ;;
  *)
      echo "Unknown exit code"
      ;;
esac
Fólkvangr
sumber