Przetestuj obsługę macierzy według powłoki

12

Czy istnieje zwięzły sposób testowania obsługi tablicy przez lokalną powłokę podobną do Bourne'a w wierszu poleceń?

Jest to zawsze możliwe:

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

lub testowanie $SHELLi wersja powłoki:

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

a następnie czytanie strony podręcznika, zakładając, że mam do niej dostęp. (Nawet tam, pisząc z /bin/bash, zakładam, że wszystkie powłoki podobne do Bourne'a dopuszczają długą opcję --version, kiedy na przykład psuje się to dla ksh .)

Szukam prostego testu, który mógłby być bezobsługowy i włączony do sekcji Użycie na początku skryptu, a nawet przed jego wywołaniem.

Cbhihe
źródło
Zakładam, że chcesz ograniczyć się do muszelek podobnych do Bourne'a?
Stéphane Chazelas
@ StéphaneChazelas: Tak, jeśli masz na myśli (nie wyczerpująco) podstawową grupę złożoną z: sh, csh, ksh, tcsh, bash, zsh i bliskich przyjaciół. Nie wiem, gdzie yash ustawia się w tej konstelacji.
Cbhihe,
2
cshnie jest skorupą Bourne'a. tcshteż nie jest jeden (jest cshz kilkoma naprawionymi błędami)
cas
1
Zauważ, że $SHELLjest to preferowana powłoka użytkownika, podobnie jak $EDITORjego preferowany edytor tekstu. Ma niewiele wspólnego z aktualnie działającą powłoką.
Stéphane Chazelas
1
evalużycie wyjścia $SHELL --versionjako kodu powłoki nie ma sensu.
Stéphane Chazelas

Odpowiedzi:

12

Zakładając, że chcesz ograniczyć do powłokach typu bourne (wiele innych muszli jak csh, tcsh, rc, eslub fishtablic wsparcia ale pisząc scenariusz zgodny jednocześnie do Bourne-jak muszle i tych jest trudne i na ogół pozbawione sensu, ponieważ są one tłumaczy na zupełnie inny i niekompatybilne języki), zauważ, że istnieją znaczące różnice między implementacjami.

Powłoki typu Bourne'a obsługujące tablice to:

  • ksh88(to pierwsza implementująca tablice, ksh88 jest nadal spotykany jak kshw większości tradycyjnych komercyjnych Unices, gdzie jest również podstawą sh)

    • tablice są jednowymiarowe
    • Tablice są zdefiniowane jako set -A array foo barlub set -A array -- "$var" ...jeśli nie możesz zagwarantować, że $varnie rozpocznie się od znaku -lub +.
    • Indeksy tablic zaczynają się od 0.
    • Poszczególne elementy tablicy są przypisywane jako a[1]=value.
    • tablice są rzadkie. To a[5]=foozadziała, nawet jeśli a[0,1,2,3,4]nie są ustawione i pozostawi je rozbrojone.
    • ${a[5]}aby uzyskać dostęp do elementu indeksu 5 (niekoniecznie szóstego elementu, jeśli tablica jest rzadka). 5Nie może być dowolne wyrażenie arytmetyczne.
    • Rozmiar tablicy i indeks dolny są ograniczone (do 4096).
    • ${#a[@]} to liczba przypisanych elementów w tablicy (nie największy przypisany indeks).
    • nie ma możliwości poznania listy przypisanych indeksów dolnych (oprócz testowania 4096 elementów indywidualnie [[ -n "${a[i]+set}" ]]).
    • $ajest taki sam jak ${a[0]}. To znaczy, że tablice w jakiś sposób rozszerzają zmienne skalarne, nadając im dodatkowe wartości.
  • pdkshi pochodne (to podstawa, ksha czasem shkilka BSD i była jedyną implementacją ksh typu open source przed uwolnieniem źródła ksh93):

    Najczęściej jak, ksh88ale uwaga:

    • Niektóre stare implementacje nie obsługiwały set -A array -- foo bar( --nie były tam potrzebne).
    • ${#a[@]}to jeden plus wskaźnik największej przypisanej wartości. ( a[1000]=1; echo "${#a[@]}"wyprowadza 1001, mimo że tablica ma tylko jeden element.
    • w nowszych wersjach rozmiar tablicy nie jest już ograniczony (inny niż rozmiar liczb całkowitych).
    • Nowsze wersje mkshmają kilka dodatkowych operatorów inspirowane z bash, ksh93lub zshpodobne zadania la a=(x y), a+=(z), ${!a[@]}aby uzyskać listę przypisanych indeksów.
  • zsh. zshtablice są na ogół lepiej zaprojektowane i podjąć najlepsze kshi cshtablice. Są podobne, kshale z istotnymi różnicami:

    • indeksy zaczynają się od 1, a nie od 0 (z wyjątkiem kshemulacji), co jest zgodne z tablicą Bourne'a (parametry pozycji $ @, która zshrównież wyświetla się jako tablica $ argv) i cshtablice.
    • są one odrębnym typem od zmiennych normalnych / skalarnych. Operatorzy stosują się do nich inaczej i tak, jak zwykle można się spodziewać. $anie jest tym samym co, ${a[0]}ale rozwija się do niepustych elementów tablicy ( "${a[@]}"dla wszystkich elementów jak w ksh).
    • są to normalne tablice, a nie rzadkie tablice. a[5]=1działa, ale przypisuje wszystkie elementy od 1 do 4 pusty ciąg, jeśli nie zostały one przypisane. Tak więc ${#a[@]}(to samo ${#a}co w ksh jest wielkością elementu indeksu 0) jest liczba elementów w tablicy i największy przypisany indeks.
    • obsługiwane są tablice asocjacyjne.
    • obsługiwana jest duża liczba operatorów do pracy z tablicami, zbyt duża, aby ją tutaj wymienić.
    • tablice zdefiniowane jako a=(x y). set -A a x ydziała również, ale set -A a -- x ynie jest obsługiwane, chyba że w emulacji ksh ( --nie jest potrzebne w emulacji zsh).
  • ksh93. (tutaj opisujące najnowsze wersje). ksh93, od dawna uważany za eksperymentalny można teraz znaleźć w coraz większej liczbie systemów, odkąd został wydany jako FOSS. Na przykład, jest to /bin/sh(gdzie zastąpiła Bourne Shell /usr/xpg4/bin/sh, POSIX powłoki nadal opiera się na ksh88) i kshod Solaris 11. Jego tablice rozszerzają i poprawiają ksh88.

    • a=(x y)może być użyty do zdefiniowania tablicy, ale ponieważ a=(...)jest również używany do zdefiniowania zmiennych złożonych ( a=(foo=bar bar=baz)), a=()jest niejednoznaczny i deklaruje zmienną złożoną, a nie tablicę.
    • tablice są wielowymiarowe ( a=((0 1) (0 2))), a elementy tablicy mogą być również zmiennymi złożonymi ( a=((a b) (c=d d=f)); echo "${a[1].c}").
    • Za pomocą a=([2]=foo [5]=bar)składni można jednocześnie zdefiniować rzadkie tablice.
    • Zniesiono ograniczenia wielkości.
    • Nie w stopniu zsh, ale ogromna liczba operatorów wsparła również manipulowanie tablicami.
    • "${!a[@]}" aby pobrać listę indeksów tablicowych.
    • tablice asocjacyjne obsługiwane również jako osobny typ.
  • bash. bashjest powłoką projektu GNU. Jest używany jak shw najnowszych wersjach OS / X i niektórych dystrybucjach GNU / Linux. bashtablice najczęściej emulują ksh88te z niektórymi cechami ksh93i zsh.

    • a=(x y)utrzymany. set -A a x y nie obsługiwane a=()tworzy pustą tablicę (bez zmiennych złożonych bash).
    • "${!a[@]}" dla listy indeksów.
    • a=([foo]=bar)Składnia obsługiwane, a także kilka innych z ksh93i zsh.
    • najnowsze bashwersje obsługują również tablice asocjacyjne jako osobny typ.
  • yash. Jest to stosunkowo nowa, czysta, wielobajtowa implementacja sh POSIX. Nie w powszechnym użyciu. Jego tablice są kolejnym czystym API podobnym dozsh

    • tablice nie są rzadkie
    • Indeksy tablic zaczynają się od 1
    • zdefiniowane (i zadeklarowane) za pomocą a=(var value)
    • Elementy wkładane, usuwane lub modyfikowane przy arraypolecenie wbudowane
    • array -s a 5 valuemodyfikacja piątego elementu nie powiodłaby się, gdyby ten element nie został wcześniej przypisany.
    • liczba elementów w matrycy ${a[#]}, ${#a[@]}jest wielkość elementów w formie listy.
    • tablice są osobnym typem. a=("$a")Przed dodaniem lub modyfikacją elementów należy ponownie zdefiniować zmienną skalarną jako tablicę.
    • tablice nie są obsługiwane po wywołaniu jako sh.

Z tego wynika, że ​​wykrywanie obsługi macierzy jest możliwe dzięki:

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

nie wystarczy, aby móc korzystać z tych tablic. Musisz zdefiniować polecenia otoki, aby przypisać tablice jako całość i poszczególne elementy, i upewnij się, że nie próbujesz tworzyć rzadkich tablic.

Lubić

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 następnie uzyskać dostęp do elementów tablicy z "${a[$first_indice+n]}"cała lista ze "${a[@]}"i korzystać z funkcji otoki ( array_elements, set_array, set_array_element), aby uzyskać liczbę elementów tablicy (w $REPLY), ustawić tablicę jako całość lub przypisać poszczególnych elementów.

Prawdopodobnie nie warte wysiłku. Użyję perllub ograniczenie do tablicy powłoki Bourne'a / POSIX: "$@".

Jeśli intencją jest, aby jakiś plik był pozyskiwany przez interaktywną powłokę użytkownika w celu zdefiniowania funkcji, które wewnętrznie korzystają z tablic, oto kilka dodatkowych uwag, które mogą być przydatne.

Można skonfigurować zshtablice, aby były bardziej podobne do kshtablic w lokalnych zasięgach (w funkcjach lub funkcjach anonimowych).

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

Możesz także emulować ksh(poprawić zgodność z kshtablicami i kilkoma innymi obszarami) z:

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

Mając to na uwadze i jesteś gotów do spadku poparcia dla yasha ksh88i starsze wersje pdkshpochodne, i tak długo, jak nie próbować tworzyć macierze rzadkie, należy być w stanie konsekwentnie używać:

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

w tych funkcjach, które mają emulate -L ksh, podczas gdy zshużytkownik nadal używa swoich tablic normalnie w sposób zsh.

Stéphane Chazelas
źródło
7

Możesz użyć evaldo wypróbowania składni tablicy:

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

if is_array_support; then
  echo support
else
  echo not
fi
Cuonglm
źródło
2
ksh88obsługuje tablice, ale nie obsługuje a=(). W ksh93, a=()deklaruje zmienną związek, a nie tablicy, chyba że zmienna została zadeklarowana jako tablica wcześniej.
Stéphane Chazelas
2
Należy również zauważyć, że istnieją znaczące różnice między implementacjami tablicowymi. Na przykład niektóre mają indeksy tablicowe zaczynające się od 0 (bash, ksh, zsh w emulacji ksh), niektóre zaczynają się od jednego (zsh, yash). Niektóre są normalnymi tablicami / listami, niektóre są rzadkimi tablicami (tablice asocjacyjne z kluczami ograniczonymi do dodatnich liczb całkowitych, takich jak ksh lub bash).
Stéphane Chazelas
W yashnie robisz, a[5]=1alearray -s a 5 1
Stéphane Chazelas
@ StéphaneChazelas: dzięki za szczegóły. W moim przypadku wszystko sprowadza się do tego, czy tablice (asocjacyjne, czy nie) są w ogóle obsługiwane. Szczegóły dotyczące indeksu można łatwo opracować nawet w skrypcie przeznaczonym do uruchamiania bez nadzoru.
Cbhihe
@ StéphaneChazelas: Zmienna ksh93złożona mnie zaskoczyła, czy mógłbyś przekazać mi część dokumentacji na jej temat. Dodaję 1do tablicy, aby działała.
cuonglm