¿Comprobar variable es una matriz en Bourne como shell?

14

En Bourne, como el shell que admite la variable de matriz, podemos usar algún análisis para verificar si la variable es una matriz.

Todos los comandos a continuación se ejecutaron después de ejecutarse 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 y su 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

Un ejemplo en bash:

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

Este enfoque es demasiado trabajo y necesita generar una subshell. Usar otro shell incorporado como =~en [[ ... ]]no necesita un subshell, pero sigue siendo demasiado complicado.

¿Hay alguna manera más fácil de realizar esta tarea?

Cuonglm
fuente
¿En qué circunstancias necesitaría verificar si sus variables son matrices o no?
Kusalananda

Respuestas:

10

No creo que puedas, y no creo que realmente haga ninguna diferencia.

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

x

Eso hace lo mismo en cualquiera de ksh93y bash. Parece que posiblemente todas las variables son matrices en esos shells, o al menos cualquier variable regular a la que no se le hayan asignado atributos especiales, pero no verifiqué mucho de eso.

El bashmanual habla sobre diferentes comportamientos para una matriz frente a una variable de cadena cuando se usan +=asignaciones, pero luego cubre y establece que la matriz solo se comporta de manera diferente en un contexto de asignación compuesto .

También establece que una variable se considera una matriz si a cualquier subíndice se le ha asignado un valor, e incluye explícitamente la posibilidad de una cadena nula. Arriba puede ver que una asignación regular definitivamente resulta en la asignación de un subíndice, por lo que supongo que todo es una matriz.

Prácticamente, posiblemente puede usar:

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

... para identificar claramente las variables establecidas a las que solo se les ha asignado un subíndice único de valor 0.

mikeserv
fuente
Así que supongo que verificar si ${a[1]-not array}puede hacer la tarea, ¿no?
Cuonglm
@cuonglm - Bueno, no de acuerdo con el bashmanual: una variable de matriz se considera establecida si a un subíndice se le ha asignado un valor. La cadena nula es un valor válido. Si se asigna un subíndice, es una matriz por especificación. En la práctica, tampoco, porque puedes hacerlo a[5]=x. Supongo que [ 1 -eq "${#a[@]}" ] && [ -n "${a[0]+1}" ]podría funcionar.
mikeserv
6

Entonces, ¿quieres efectivamente solo la parte media declare -psin la basura a su alrededor?

Podrías escribir una macro como:

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

para que puedas hacer:

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

(Una simple función no funcionará si desea utilizar esto en variables locales de función).


Con alias

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

vartype a #scalar
vartype b #array
vartype c #hash
PSkocik
fuente
@mikeserv Buen punto. Los alias hacen que se vea más bonita. +1
PSkocik
quise decir alias vartype="$VARTYPE"... o simplemente no definía $VARTYPEnada, debería funcionar, ¿verdad? solo debería necesitar esa shoptcosa bashporque rompe con la especificación con respecto a la aliasexpansión en los scripts.
mikeserv
1
@mikeserv Estoy seguro de que cuonglm es capaz de ajustar este enfoque a sus necesidades y preferencias. ;-)
PSkocik
... y consideraciones de seguridad.
PSkocik
En ningún momento el código anterior evalúa el texto proporcionado por el usuario. No es menos seguro que una función. Nunca te he visto preocupado por hacer que las funciones sean de solo lectura, pero OK, puedo marcar la variable solo lectura.
PSkocik
6

En zsh

zsh% a=(1 2 3) s=1
zsh% [[ ${(t)a} == *array* ]] && echo array
array
zsh% [[ ${(t)s} == *array* ]] && echo array
zsh%
llua
fuente
Quizás echo ${(t)var}es más simple. Gracias por esto.
4

Para probar la variable var, con

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

Es posible probar si hay más de un índice de matriz:

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

Si el primer valor del índice no es cero:

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

La única confusión difícil es cuando solo hay un valor de índice y ese valor es cero (o uno).

Para esa condición, es posible usar un efecto secundario al tratar de eliminar un elemento de matriz de una variable que no es una 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

Esto funciona correctamente para 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, el índice puede ser 1 (a menos que haya un modo compatible activo).

El subconjunto es necesario para evitar el efecto secundario de borrar el índice 0 de la var.

No he encontrado la manera de hacerlo funcionar en ksh.

Editar 1

Esta función solo funciona en 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

Esto también funciona solo para bash4.2 +

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

Nota: Esto dará falsos positivos si var contiene las cadenas probadas.


fuente
¿Qué tal una matriz con cero elementos?
Cuonglm
1
Dat edit, aunque. Se ve muy original. : D
PSkocik
@cuonglm La verificación ( unset "var[0]" 2>/dev/null; ) && echo "var is an array."informa correctamente que var es una matriz cuando var se ha establecido en var=()una matriz con cero elementos. Actúa exactamente igual a declarar.
La prueba para escalar no funcionará si el escalar se exporta o se marca como entero / minúscula / solo lectura ... Probablemente pueda asegurarse de que cualquier otra salida no vacía signifique una variable escalar. Lo usaría en grep -Elugar de grep -Pevitar la dependencia de GNU grep.
Stéphane Chazelas
@ StéphaneChazelas la prueba (en bash) para escalar con el número entero y / o minúsculas y / o de sólo lectura siempre comienzan con -a, como esto: declare -airl var='()'. Por lo tanto, la prueba grep funcionará .
3

Para bash , es un truco (aunque documentado): intente usar typesetpara eliminar el atributo "array":

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

(No puede hacer esto zsh, le permite convertir una matriz a un escalar, ya bashque está explícitamente prohibido).

Entonces:

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

O en una función, observando las advertencias al 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
}

Tenga en cuenta el uso de typeset -g(bash-4.2 o posterior), esto es necesario dentro de una función para que typeset(syn. declare) No funcione como localy bloquee el valor que está tratando de inspeccionar. Esto tampoco maneja los tipos de función "variable", puede agregar otra prueba de ramificación utilizando typeset -fsi es necesario.


Otra opción (casi completa) es usar esto:

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

Sin embargo, hay un pequeño problema: una matriz con un solo subíndice de 0 coincide con dos de las condiciones anteriores. Esto es algo a lo que mikeserv también hace referencia, bash realmente no tiene una distinción difícil, y algo de esto (si marca el registro de cambios) puede atribuirse a ksh y a la compatibilidad con cómo ${name[*]}o ${name[@]}comportarse en una no matriz.

Entonces, una solución parcial es:

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

He usado en el pasado una variación de esto:

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

Sin embargo, esto también necesita un subshell.

Una técnica posiblemente más útil es compgen:

compgen -A arrayvar

Esto enumerará todas las matrices indexadas, sin embargo, las matrices asociativas no se manejan especialmente (hasta bash-4.4) y aparecen como variables regulares ( compgen -A variable)

Sr. púrpura
fuente
El typeset +atambién informa un error en ksh. Sin embargo, no en zsh.
1

Respuesta corta:

Para las dos capas que introdujeron esta notación ( bashy ksh93) una variable escalar es solo una matriz con un solo elemento .

Tampoco necesita una declaración especial para crear una matriz. Solo la asignación es suficiente, y una asignación simple var=valuees idéntica a var[0]=value.

Henk Langeveld
fuente
Proveedores: bash -c 'unset var; var=foo; typeset -p var'. ¿Bash answer informa una matriz (necesita un -a) ?. Ahora compare con: bash -c 'unset var; var[12]=foo; typeset -p var'. ¿Por qué hay una diferencia?. R: El shell mantiene (para bien o para mal) una noción de qué vars son escalares o matrices. El shell ksh mezcla ambos conceptos en uno.
1

Yash arrayorden interna tiene algunas opciones que sólo funcionan con variables de matriz. Ejemplo: la -dopción informará un error en la variable sin matriz:

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

Entonces podemos hacer algo como esto:

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

Este enfoque no funcionará si la variable de matriz es de solo lectura . Intentando modificar una variable de solo lectura que conduce a un error:

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