Variável de verificação é uma matriz em Bourne como shell?

13

No shell parecido com Bourne, que suporta a variável array, podemos usar algumas análises para verificar se a variável é um array.

Todos os comandos abaixo foram executados depois de executar 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 e sua derivada:

$ 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

Um exemplo em bash :

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

Essa abordagem é muito trabalhosa e precisa gerar um subshell. Usar outro shell embutido como =~ in [[ ... ]] não precisa de um subshell, mas ainda é muito complicado.

Existe uma maneira mais fácil de realizar essa tarefa?

    
por cuonglm 28.11.2015 / 05:04

8 respostas

9

Eu não acho que você pode, e eu não acho que isso realmente faça alguma diferença.

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

Isso faz a mesma coisa em ksh93 e bash . Parece que possivelmente todas variáveis são matrizes nesses shells, ou pelo menos qualquer variável regular que não tenha sido atribuída atributos especiais, mas eu não verifiquei muito disso.

O manual bash fala sobre os diferentes comportamentos de uma matriz versus uma variável de string ao usar += atribuições, mas depois se protege e afirma que a matriz só se comporta de maneira diferente em uma atribuição composto contexto.

Ele também afirma que uma variável é considerada uma matriz se qualquer índice tiver sido atribuído a um valor - e incluir explicitamente a possibilidade de uma sequência nula. Acima você pode ver que uma atribuição regular definitivamente resulta em um subscrito sendo atribuído - e então eu acho que tudo é um array.

Praticamente, possivelmente você pode usar:

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

... para identificar claramente as variáveis configuradas que receberam apenas um único subscrito do valor 0.

    
por 28.11.2015 / 05:52
5

Então, você quer efetivamente apenas a parte intermediária de declare -p sem o lixo em volta dele?

Você pode escrever uma macro como:

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

para que você possa fazer:

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

(Uma mera função não funcionará se você quiser usar isso em variáveis locais de função).

com aliases

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

vartype a #scalar
vartype b #array
vartype c #hash
    
por 28.11.2015 / 12:47
5

Em zsh

zsh% a=(1 2 3) s=1
zsh% [[ ${(t)a} == *array* ]] && echo array
array
zsh% [[ ${(t)s} == *array* ]] && echo array
zsh%
    
por 28.11.2015 / 21:16
4

Para testar a variável var, com

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

É possível testar se há mais de um índice de matriz:

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

Se o primeiro valor de índice não for zero:

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

A única confusão difícil é quando existe apenas um valor de índice e esse valor é zero (ou um).

Para essa condição, é possível usar um efeito colateral de tentar remover um elemento da matriz de uma variável que não seja uma matriz:

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

Isso funciona corretamente para o 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."

Para zsh, o índice pode precisar ser 1 (a menos que um modo compatível esteja ativo).

O sub-shell é necessário para evitar o efeito colateral de apagar o índice 0 de var.

Não encontrei uma maneira de fazer isso funcionar no ksh.

Editar 1

Esta função só funciona no 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

Editar 2

Isso também funciona apenas para bash4.2 +

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

Nota: Isso dará falsos positivos se var contiver as strings testadas.

    
por 28.11.2015 / 09:30
3

Para o bash , é um pouco um hack (embora documentado): tente usar typeset para remover o atributo "array":

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

(Você não pode fazer isso em zsh , ele permite converter uma matriz em escalar, em bash é explicitamente proibido.)

Então:

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

Ou em uma função, observando as advertências no final:

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
}

Observe o uso de typeset -g (bash-4.2 ou posterior), isso é necessário dentro de uma função para que typeset (syn. declare ) não funcione como local e desconsidere o valor que você está tentando inspecionar. Isso também não lida com tipos de "variável" de função, você pode adicionar outro teste de ramificação usando typeset -f , se necessário.

Outra opção (quase completa) é usar isto:

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

Há um pequeno problema, porém, uma matriz com um único subscrito de 0 corresponde a duas das condições acima. Isso é algo que o mikeserv também faz referência, bash realmente não tem uma distinção strong, e algumas delas (se você verificar o Changelog) podem ser responsabilizadas pelo ksh e compatibilidade com como ${name[*]} ou ${name[@]} se comportam em um não array.

Portanto, uma solução parcial é:

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

Eu usei no passado uma variação sobre isso:

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

isso também precisa de um subshell.

Uma técnica possivelmente mais útil é compgen :

compgen -A arrayvar

Isto irá listar todos os arrays indexados, no entanto arrays associativos não são tratados especialmente (até bash-4.4) e aparecem como variáveis regulares ( compgen -A variable )

    
por 28.11.2015 / 17:39
1

Resposta curta:

Para os dois shells que introduziram essa notação ( bash e ksh93 ), uma variável escalar é apenas uma matriz com um único elemento .

Nem precisa de uma declaração especial para criar uma matriz. Apenas a atribuição é suficiente e uma atribuição simples var=value é idêntica a var[0]=value .

    
por 29.11.2015 / 23:41
1

o array builtin do yash tem algumas opções que funcionam apenas com variáveis de matriz. Exemplo: a opção -d reportará um erro na variável não matriz:

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

Para que possamos fazer algo assim:

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

Esta abordagem não funcionará se a variável array for readonly . Tentando modificar uma variável readonly levando a um erro:

$ a=()
$ readonly a
$ array -d a
array: $a is read-only
    
por 30.11.2015 / 08:19
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
    
por 20.12.2018 / 10:56