Kontrol değişkeni Bourne'de kabuk gibi bir dizidir?


14

Bourne gibi dizi değişkenini destekleyen kabuk gibi, değişkenin bir dizi olup olmadığını kontrol etmek için biraz ayrıştırma kullanabiliriz.

Aşağıdaki tüm komutlar çalıştırıldıktan sonra çalıştırıldı 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 ve türevi:

$ 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

Bir örnek bash:

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

Bu yaklaşım çok fazla iştir ve bir alt kabuk ortaya çıkarması gerekir. Gibi diğer kabuk yerleşiğini kullanarak =~içinde [[ ... ]]bir altkabuk gerekmez, ama yine de karmaşıktır.

Bu görevi yerine getirmenin daha kolay bir yolu var mı?


Hangi koşullar altında değişkenlerinizin dizi olup olmadığını kontrol etmeniz gerekir?
Kusalananda

Yanıtlar:


10

Yapabileceğini sanmıyorum ve bunun aslında bir fark yarattığını düşünmüyorum.

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

x

Yani biriyle aynı şeyi yapar ksh93ve bash. Muhtemelen tüm değişkenler bu kabuklardaki diziler veya en azından özel nitelikler atanmamış herhangi bir normal değişken gibi görünüyor , ancak bunların çoğunu kontrol etmedim.

bashBir dize değişkeni karşı bir dizi için farklı davranışları hakkında manuel görüşmeler kullanırken +=atamaları, ancak sonradan çitlerin ve devletler dizisi sadece farklı davrandığını bileşik atama bağlamında.

Ayrıca, herhangi bir alt simge bir değer atandıysa bir değişkenin dizi olarak kabul edildiğini ve açıkça bir boş dize olasılığını içerdiğini belirtir. Yukarıda, normal bir atamanın kesinlikle bir alt simge atanmasına neden olduğunu görebilirsiniz - ve sanırım her şey bir dizi.

Pratik olarak, muhtemelen şunları kullanabilirsiniz:

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

... yalnızca 0 değerine sahip tek bir alt simge atanmış olan değişkenleri açıkça belirlemek için.


Sanırım ${a[1]-not array}görevi yapıp yapamayacağını kontrol ediyorum , değil mi?
cuonglm

@cuonglm - bashEl kitabına göre değil : Bir alt dizeye bir değer atanmışsa bir dizi değişkeni ayarlanmış kabul edilir. Boş dize geçerli bir değerdir. Herhangi bir alt simge atanmışsa, her özellik için bir dizidir. Pratikte de hayır, çünkü yapabilirsiniz a[5]=x. Sanırım [ 1 -eq "${#a[@]}" ] && [ -n "${a[0]+1}" ]işe yarayabilir.
mikeserv

6

Yani declare -petraftaki önemsiz olmadan sadece orta kısmını mı istiyorsunuz?

Aşağıdaki gibi bir makro yazabilirsiniz:

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

böylece şunları yapabilirsiniz:

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

(Bunu işlev-yerel değişkenlerde kullanmak isteyecekseniz yalnızca bir işlev çalışmaz).


Takma adlarla

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

vartype a #scalar
vartype b #array
vartype c #hash

@mikeserv İyi bir nokta. Takma adlar daha güzel görünmesini sağlar. +1
PSkocik

Demek istediğim - alias vartype="$VARTYPE"... ya da hiç tanımlamıyor $VARTYPE- çalışmalı, değil mi? sadece bu shoptşeye ihtiyacınız var bashçünkü aliaskomut dosyalarındaki genişleme ile ilgili spesifikasyonu bozuyor .
mikeserv

1
@mikeserv Eminim cuonglm, bu yaklaşımı ihtiyaçlarına ve tercihlerine göre ayarlayabiliyor. ;-)
PSkocik

... ve güvenlikle ilgili konular.
PSkocik

Yukarıdaki kod hiçbir noktada kullanıcıya sağlanan metni değerlendirmez. Bir fonksiyondan daha az güvenli değildir. Fonksiyonları salt okunur yapma konusunda huzursuz olduğunuzu hiç görmedim, ama tamam, değişkeni salt okunur olarak işaretleyebilirim.
PSkocik

6

Zsh cinsinden

zsh% a=(1 2 3) s=1
zsh% [[ ${(t)a} == *array* ]] && echo array
array
zsh% [[ ${(t)s} == *array* ]] && echo array
zsh%

Belki echo ${(t)var}daha basittir. Bunun için teşekkürler.

4

Değişken var'ı test etmek için

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

Birden fazla dizi indeksi olup olmadığını test etmek mümkündür:

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

İlk dizin değeri sıfır değilse:

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

Tek zor karışıklık sadece bir indeks değeri olduğunda ve bu değerin sıfır (veya bir) olması.

Bu koşul için, dizi olmayan bir değişkenden bir dizi öğesini kaldırmaya çalışmanın yan etkisini kullanmak mümkündür:

**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

Bu bash için doğru çalışır:

# 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."

Zsh için dizinin 1 olması gerekebilir (uyumlu bir mod etkin değilse).

Alt kabuk, var. İndeksinin 0 silinmesinin yan etkisini önlemek için gereklidir.

Ksh'ta çalışmasını sağlamanın bir yolunu bulamadım.

Düzenle 1

Bu işlev yalnızca bash4.2 + 'da çalışır

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

Düzenle 2

Bu sadece bash4.2 + için de geçerlidir

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

Not: var test edilen dizeleri içeriyorsa, yanlış pozitifler verir.


Sıfır elemanlı dizi nasıl olur?
cuonglm

1
Dat düzenleme, tho. Çok orijinal görünüyor. : D
PSkocik

@cuonglm ( unset "var[0]" 2>/dev/null; ) && echo "var is an array."var doğru denetimi var değişkenleri sıfır öğeli bir diziye ayarlandığında var=()bir dizidir. Beyanla tamamen aynı şekilde hareket eder.

Skaler dışa aktarılırsa veya tamsayı / küçük harf / salt okunur olarak işaretlenirse skaler testi çalışmaz ... Muhtemelen boş olmayan herhangi bir çıktının skaler değişken anlamına gelmesini güvenle yapabilirsiniz. Bana kalırsa doğru grep -Eyerine grep -PGNU grep bağımlılığını önlemek için.
Stéphane Chazelas

Tamsayı ve / veya küçük harf ile skalar için @ StéphaneChazelas (bash) testi ve / veya salt okunur zaman ile başlar -a, bu gibi: declare -airl var='()'. Bu nedenle grep testi işe yarayacaktır .

3

İçin Bash , bu (belgelenmiş olsa) bir hack biraz var: kullanım girişimi typeset"dizi" ayrıntısını kaldırmak için:

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

(Bunu yapamazsınız zsh, bir diziyi skalere dönüştürmenize izin verir, bashaçıkça yasaktır.)

Yani:

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

Veya bir işlevde, sonunda uyarıları not edin:

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
}

Kullanımına dikkat typeset -g(bash-4.2 veya üstü), bu şekilde bir işlev içinde gereklidir typeset(syn. declare) Gibi işi değil localve incelemek için çalışıyoruz değeri yenmek. Bu ayrıca işlev "değişken" türlerini işlemez, gerekirse başka bir dal testi ekleyebilirsiniz typeset -f.


Başka bir (neredeyse tamamlanmış) seçenek bunu kullanmaktır:

    ${!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.

Yine de küçük bir sorun var, tek bir 0 aboneliğe sahip bir dizi yukarıdaki koşullardan ikisiyle eşleşiyor. Bu da mikeserv'in referans aldığı bir şeydir, bash gerçekten zor bir ayrımı yoktur ve bunların bir kısmı (Changelog'u kontrol ederseniz) ksh'a suçlanabilir ve bir dizide olmayan ${name[*]}veya nasıl ${name[@]}davranılacağı ile uyumlu olabilir .

Bir Yani kısmi bir çözümdür:

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

Geçmişte bu konuda bir varyasyon kullandım:

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

bu da bir alt kabuk gerekir.

Daha olası bir teknik daha compgen:

compgen -A arrayvar

Bu, tüm dizinlenmiş dizileri listeler, ancak ilişkilendirilebilir diziler özel olarak ele alınmaz (bash-4.4'e kadar) ve normal değişkenler olarak görünür ( compgen -A variable)


typeset +aAyrıca ksh bir hata bildirir. Yine de zsh değil.

1

Kısa cevap:

Bu gösterimi ( bashve ksh93) tanıtan iki kabuk için, bir skaler değişken sadece tek bir elemanı olan bir dizidir .

Her ikisinin de bir dizi oluşturmak için özel bir bildirime ihtiyacı yoktur . Sadece ödev yeterlidir ve basit bir ödev var=valueaynıdır var[0]=value.


Deneyin: bash -c 'unset var; var=foo; typeset -p var'. Bash yanıt bir diziyi rapor ediyor mu (-a gerekir)? Şimdi ile karşılaştır: bash -c 'unset var; var[12]=foo; typeset -p var'. Neden bir farklılık var?. C: Kabuk, varyasyonları skaler veya diziler olan (iyi ya da kötü için) bir nosyonu korur. Kabuk ksh her iki kavramı da bir araya getirir.

1

yash's arraybuiltin sadece dizi değişkenleri ile çalışan bazı seçeneklere sahiptir. Örnek: -dseçenek, dizi olmayan değişken üzerinde bir hata bildirir:

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

Böylece böyle bir şey yapabiliriz:

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

Dizi değişkeni salt okunursa bu yaklaşım çalışmaz . Hataya neden olan salt okunur bir değişkeni değiştirmeye çalışıyorum :

$ a=()
$ readonly a
$ array -d a
array: $a is read-only

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.