Sprawdź, czy zmienna jest tablicą w powłoce Bourne'a?


14

W powłoce Bourne'a, która obsługuje zmienną tablicową, możemy użyć analizy składniowej, aby sprawdzić, czy zmienna jest tablicą.

Wszystkie poniższe polecenia zostały uruchomione po uruchomieniu 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 i jego pochodna:

$ 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

Przykład w bash:

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

To podejście jest zbyt pracochłonne i wymaga odrodzenia podpowłoki. Korzystanie z innych wbudowanych powłok, takich jak =~in [[ ... ]], nie wymaga podpowłoki, ale wciąż jest zbyt skomplikowane.

Czy istnieje łatwiejszy sposób na wykonanie tego zadania?


W jakich okolicznościach musiałbyś sprawdzić, czy twoje zmienne są tablicami, czy nie?
Kusalananda

Odpowiedzi:


10

Nie sądzę, że możesz, i nie sądzę, że to naprawdę robi jakąkolwiek różnicę.

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

x

Że robi to samo w jednej z ksh93i bash. Wygląda na to, że prawdopodobnie wszystkie zmienne są tablicami w tych powłokach, a przynajmniej jakakolwiek zwykła zmienna, której nie przypisano specjalnych atrybutów, ale nie sprawdziłem wiele z nich.

Do bashpodręcznika mówi o różnych zachowań na tablicy w funkcji zmiennej łańcuchowej podczas korzystania z +=zadania, ale potem żywopłoty i stwierdza, że matryca zachowuje się inaczej niż tylko w związek kontekście przypisania.

Wskazuje również, że zmienna jest uważana za tablicę, jeśli jakiemukolwiek indeksowi przypisano wartość - i wyraźnie obejmuje możliwość łańcucha zerowego. Powyżej widać, że zwykłe przypisanie zdecydowanie skutkuje przypisaniem indeksu dolnego - i tak myślę, że wszystko jest tablicą.

Praktycznie możesz użyć:

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

... aby wyraźnie wskazać zmienne, którym przypisano tylko jeden indeks dolny o wartości 0.


Więc chyba sprawdzam, czy ${a[1]-not array}da radę, prawda?
cuonglm

@cuonglm - Cóż, niezgodne z bashinstrukcją: Zmienna tablicowa jest uważana za ustawioną, jeśli indeksowi przypisano wartość. Ciąg zerowy jest prawidłową wartością. Jeśli jakikolwiek indeks dolny ma przypisaną tablicę według specyfikacji. W praktyce też nie, bo możesz a[5]=x. Chyba [ 1 -eq "${#a[@]}" ] && [ -n "${a[0]+1}" ]może działać.
mikeserv

6

Więc skutecznie chcesz tylko środkową część declare -pbez śmieci?

Możesz napisać makro, takie jak:

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

abyś mógł:

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

(Zwykła funkcja nie zadziała, jeśli będziesz chciał użyć jej w zmiennych lokalnych funkcji).


Z aliasami

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

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

@mikeserv Dobra uwaga. Aliasy sprawiają, że wygląda ładniej. +1
PSkocik

Miałem na myśli - alias vartype="$VARTYPE"... lub po prostu nie definiując $VARTYPEwcale - powinien działać, prawda? powinieneś potrzebować tylko tego shopt, bashponieważ jest to niezgodne ze specyfikacją dotyczącą aliasrozszerzania skryptów.
mikeserv

1
@ mikeserv Jestem pewien, że cuonglm jest w stanie dostosować to podejście do swoich potrzeb i preferencji. ;-)
PSkocik

... i względy bezpieczeństwa.
PSkocik

W żadnym momencie powyższy kod nie pokazuje tekstu użytkownika. Jest nie mniej bezpieczny niż funkcja. Nigdy nie widziałem, żebyś miał tyle problemów z ustawieniem funkcji tylko do odczytu, ale OK, mogę zaznaczyć zmienną tylko do odczytu.
PSkocik

6

W Zsh

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

Może echo ${(t)var}jest prostsze. Dzięki za to.

4

Aby przetestować zmienną var, za pomocą

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

Możliwe jest sprawdzenie, czy istnieje więcej niż jeden indeks tablicy:

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

Jeśli pierwsza wartość indeksu nie jest równa zero:

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

Jedynym trudnym zamieszaniem jest sytuacja, gdy istnieje tylko jedna wartość indeksu i ta wartość wynosi zero (lub jedna).

W tym stanie można zastosować efekt uboczny próby usunięcia elementu tablicy ze zmiennej, która nie jest tablicą:

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

Działa to poprawnie w przypadku 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."

W przypadku zsh indeks może wymagać 1 (chyba że aktywny jest tryb kompatybilny).

Sub-shell jest potrzebny, aby uniknąć efektu ubocznego wymazania indeksu 0 var.

Nie znalazłem sposobu, aby działał w ksh.

Edytuj 1

Ta funkcja działa tylko w 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

Edytuj 2

Działa to również tylko dla bash4.2 +

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

Uwaga: Daje to fałszywie pozytywne wyniki, jeśli var zawiera testowane ciągi.


A może tablica z zerowymi elementami?
cuonglm

1
Edycja danych, tys. Wygląda bardzo oryginalnie. : D
PSkocik

@cuonglm Sprawdzanie ( unset "var[0]" 2>/dev/null; ) && echo "var is an array."poprawności raportów var jest tablicą, gdy var jest ustawiony na var=()tablicę z zerowymi elementami. Działa dokładnie tak samo, jak deklarować.

Test na skalar nie zadziała, jeśli skalar zostanie wyeksportowany lub oznaczony jako liczba całkowita / małe litery / tylko do odczytu ... Prawdopodobnie możesz bezpiecznie sprawić, że dowolne inne niepuste dane wyjściowe oznaczają zmienną skalarną. Użyłbym grep -Ezamiast, grep -Paby uniknąć zależności od GNU grep.
Stéphane Chazelas

@ StéphaneChazelas testu (bash) dla skalarnych z liczby całkowitej i / lub małych i / lub tylko do odczytu zawsze zaczynają się -aw następujący sposób: declare -airl var='()'. Dlatego test grep będzie działał .

3

W przypadku bash jest to trochę hack (choć udokumentowane): spróbuj użyć, typesetaby usunąć atrybut „array”:

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

(Nie możesz tego zrobić w zsh, pozwala to przekonwertować tablicę na skalar, bashjest to wyraźnie zabronione).

Więc:

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

Lub w funkcji, zwracając uwagę na ostrzeżenia na końcu:

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
}

Zwróć uwagę na użycie typeset -g(bash-4.2 lub nowszy), jest to wymagane w ramach funkcji, aby typeset(syn. declare) Nie działało podobnie jak locali blokowało wartość, którą próbujesz sprawdzić. To również nie obsługuje typów funkcji „zmiennych”, możesz dodać kolejny test gałęzi, używając w typeset -frazie potrzeby.


Inną (prawie pełną) opcją jest użycie tego:

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

Jest jednak jeden niewielki problem: tablica z pojedynczym indeksem 0 odpowiada dwóm powyższym warunkom. Jest to coś, do czego mikeserv również się odwołuje, bash tak naprawdę nie ma twardego rozróżnienia, a niektóre z nich (jeśli zaznaczysz Dziennik zmian) można winić za ksh i zgodność z tym, jak ${name[*]}lub jak się ${name[@]}zachowywać na nie-macierzy.

Więc częściowe rozwiązanie jest:

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

W przeszłości korzystałem z tej wersji:

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

to też potrzebuje podpowłoki.

Jeszcze jedną potencjalnie przydatną techniką jest compgen:

compgen -A arrayvar

Spowoduje to wyświetlenie listy wszystkich indeksowanych tablic, jednak tablice asocjacyjne nie są obsługiwane specjalnie (do wersji bash-4.4) i pojawiają się jako zmienne zwykłe ( compgen -A variable)


Zgłasza typeset +arównież błąd w ksh. Jednak nie w Zsh.

1

Krótka odpowiedź:

Dla dwóch powłok, które wprowadziły ten zapis ( bashi ksh93), zmienna skalarna jest tylko tablicą z jednym elementem .

Żadne z nich nie wymaga specjalnej deklaracji, aby utworzyć tablicę. Wystarczy przypisanie, a zwykłe przypisanie var=valuejest identyczne var[0]=value.


Spróbuj: bash -c 'unset var; var=foo; typeset -p var'. Czy odpowiedź bash zgłasza tablicę (wymaga -a) ?. Teraz porównać z: bash -c 'unset var; var[12]=foo; typeset -p var'. Dlaczego jest różnica? Odp .: Powłoka utrzymuje (na dobre lub na złe) pojęcie, które zmienne są skalarami lub tablicami. Powłoka ksh łączy obie koncepcje w jedną.

1

Wbudowane arrayyash ma kilka opcji, które działają tylko ze zmiennymi tablicowymi. Przykład: -dopcja zgłosi błąd w zmiennej innej niż tablica:

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

Możemy więc zrobić coś takiego:

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

To podejście nie zadziała, jeśli zmienna tablicowa jest tylko do odczytu . Próba zmodyfikowania zmiennej tylko do odczytu prowadzącej do błędu:

$ 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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.