Verificar variável é uma matriz em Bourne como shell?

14

No Bourne como shell, que suporta variáveis ​​de matriz, podemos usar algumas análises para verificar se a variável é uma matriz.

Todos os comandos abaixo foram executados após a execução 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 seu derivado:

$ 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. O uso de outro shell embutido como =~no [[ ... ]]não precisa de um subshell, mas ainda é muito complicado.

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

cuonglm
fonte
Em que circunstâncias você precisaria verificar se suas variáveis ​​são matrizes ou não?
Kusalananda

Respostas:

10

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

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

x

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

O bashmanual fala sobre comportamentos diferentes para uma matriz versus uma variável de seqüência de caracteres ao usar +=atribuições, mas depois faz hedge e afirma que a matriz se comporta apenas de maneira diferente em um contexto de atribuição composto .

Ele também afirma que uma variável é considerada uma matriz se algum valor tiver sido atribuído a um índice - e inclui explicitamente a possibilidade de uma sequência nula. Acima, você pode ver que uma tarefa regular definitivamente resulta em um índice subscrito - e, portanto, acho que tudo é uma matriz.

Praticamente, possivelmente você pode usar:

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

... para identificar claramente as variáveis ​​de conjunto às quais foi atribuído apenas um único índice de valor 0.

mikeserv
fonte
Então eu acho que checar se ${a[1]-not array}pode fazer a tarefa, não é?
cuonglm
@cuonglm - Bem, não de acordo com o bashmanual: Uma variável de matriz é considerada definida se um valor subscrito tiver sido atribuído. A cadeia nula é um valor válido. Se qualquer subscrito é atribuído, é uma matriz por especificação. Na prática, também não, porque você pode fazer a[5]=x. Eu acho que [ 1 -eq "${#a[@]}" ] && [ -n "${a[0]+1}" ]poderia funcionar.
mikeserv
6

Então você quer efetivamente apenas a parte do meio ou declare -psem o lixo ao seu redor?

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ê desejar usá-lo em variáveis ​​de função local).


Com aliases

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

vartype a #scalar
vartype b #array
vartype c #hash
PSkocik
fonte
@mikeserv Bom ponto. Os aliases fazem com que pareça mais bonito. 1
PSkocik 28/11/2015
eu quis dizer - alias vartype="$VARTYPE"... ou simplesmente não defini $VARTYPEnada - deve funcionar, certo? você só precisa dessa shoptcoisa bashporque ela quebra com as especificações relacionadas à aliasexpansão nos scripts.
mikeserv
1
@mikeserv Tenho certeza que o cuonglm é bem capaz de ajustar essa abordagem às suas necessidades e preferências. ;-)
PSkocik 28/11
... e considerações de segurança.
PSKocik
Em nenhum momento o código acima avalia o usuário fornecido o texto. Não é menos seguro que uma função. Eu nunca vi você se preocupando em tornar as funções somente leitura, mas tudo bem, posso marcar a variável como somente leitura.
PSKocik #
6

No zsh

zsh% a=(1 2 3) s=1
zsh% [[ ${(t)a} == *array* ]] && echo array
array
zsh% [[ ${(t)s} == *array* ]] && echo array
zsh%
llua
fonte
Talvez echo ${(t)var}seja mais simples. Obrigado por isso.
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 do índice não for zero:

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

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

Para essa condição, é possível usar um efeito colateral ao tentar remover um elemento da matriz de uma variável que não é 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 nenhuma maneira de fazê-lo funcionar em ksh.

Editar 1

Esta função funciona apenas 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 o bash4.2 +

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

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


fonte
Que tal matriz com zero elementos?
cuonglm
1
Edição, tho. Parece muito original. : D
PSkocik
@cuonglm A verificação ( unset "var[0]" 2>/dev/null; ) && echo "var is an array."relata corretamente var é uma matriz quando var foi definida como var=()uma matriz com zero elementos. Ele age exatamente igual a declarar.
O teste para escalar não funcionará se o escalar for exportado ou marcado como inteiro / minúsculo / somente leitura ... Você provavelmente pode garantir que qualquer outra saída não vazia signifique variável escalar. Eu usaria em grep -Evez de grep -Pevitar a dependência do GNU grep.
Stéphane Chazelas
@ StéphaneChazelas O teste (em bash) para escalar com inteiro e / ou letras minúsculas e / ou somente de leitura sempre iniciar com -a, como este: declare -airl var='()'. Portanto, o teste grep funcionará .
3

Para o bash , é um pouco complicado (embora documentado): tente usar typesetpara 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 zsh, pois permite converter uma matriz em um escalar, bashpois é 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 em uma função para que typeset(syn. declare) Não funcione como locale derrube o valor que você está tentando inspecionar. Isso também não lida com os tipos de "variáveis" da função, você pode adicionar outro teste de ramificação usando, typeset -fse necessário.


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

    ${!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 índice subscrito de 0 corresponde a duas das condições acima. Isso é algo que o mikeserv também faz referência, o bash realmente não tem uma distinção difícil, e parte disso (se você verificar o Changelog) pode ser atribuída ao ksh e à compatibilidade com como ${name[*]}ou ${name[@]}se comportar 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

Isso listará todas as matrizes indexadas, no entanto, matrizes associativas não são tratadas especialmente (até o bash-4.4) e aparecem como variáveis ​​regulares ( compgen -A variable)

mr.spuratic
fonte
O typeset +atambém relata um erro no ksh. Não no zsh, no entanto.
1

Resposta curta:

Para os dois shells que introduziram essa notação ( bashe 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 tarefa é suficiente, e uma tarefa simples var=valueé idêntica a var[0]=value.

Henk Langeveld
fonte
Tente: bash -c 'unset var; var=foo; typeset -p var'. O resposta do bash relata uma matriz (precisa de um -a) ?. Agora compare com: bash -c 'unset var; var[12]=foo; typeset -p var'. Por que há uma diferença? R: O shell mantém (para o bem ou para o mal) uma noção de quais vars são escalares ou matrizes. O shell ksh combina ambos os conceitos em um.
1

O yash's arraybuiltin possui algumas opções que funcionam apenas com variáveis ​​de array. Exemplo: a -dopção relatará um erro na variável que não é da 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

Essa abordagem não funcionará se a variável da matriz for somente leitura . Tentando modificar uma variável somente leitura levando a um erro:

$ a=()
$ readonly a
$ array -d a
array: $a is read-only
cuonglm
fonte
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
fonte