La variable de vérification est-elle un tableau dans Bourne comme shell?

14

Dans Bourne comme shell qui supporte les variables de tableau, nous pouvons utiliser une analyse pour vérifier si la variable est un tableau.

Toutes les commandes ci-dessous ont été exécutées après exécution 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 et sa dérivée:

$ 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 exemple dans bash:

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

Cette approche représente trop de travail et doit générer un sous-shell. L'utilisation d'un autre shell intégré comme =~dans [[ ... ]]ne nécessite pas de sous-shell, mais est toujours trop compliqué.

Existe-t-il un moyen plus facile d'accomplir cette tâche?

cuonglm
la source
Dans quelles circonstances auriez-vous besoin de vérifier si vos variables sont des tableaux ou non?
Kusalananda

Réponses:

10

Je ne pense pas que vous puissiez le faire, et je ne pense pas que cela fasse vraiment de différence.

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

x

Cela fait la même chose dans ksh93etbash . Il ressemble peut-être à tous variables soient des tableaux dans ces shells, ou au moins toute variable régulière à laquelle aucun attribut spécial n'a été attribué, mais je n'ai pas vérifié beaucoup de cela.

Le bashmanuel parle de comportements différents pour un tableau par rapport à une variable de chaîne lors de l'utilisation des +=affectations, mais il couvre ensuite et déclare que le tableau se comporte uniquement différemment dans un contexte d'affectation composé .

Il indique également qu'une variable est considérée comme un tableau si un indice a été affecté à une valeur - et inclut explicitement la possibilité d'une chaîne nulle. Ci-dessus, vous pouvez voir qu'une affectation régulière entraîne définitivement l'affectation d'un indice - et donc je suppose que tout est un tableau.

En pratique, vous pouvez éventuellement utiliser:

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

... pour localiser clairement les variables d'ensemble auxquelles un seul indice de valeur 0 a été affecté.

mikeserv
la source
Donc je suppose que vérifier si ${a[1]-not array}peut faire la tâche, n'est-ce pas?
cuonglm
@cuonglm - Eh bien, pas selon le bashmanuel: une variable de tableau est considérée comme définie si un indice a reçu une valeur. La chaîne nulle est une valeur valide. Si un indice est affecté, c'est un tableau par spécification. En pratique, non, car vous pouvez le faire a[5]=x. Je suppose que ça [ 1 -eq "${#a[@]}" ] && [ -n "${a[0]+1}" ]pourrait marcher.
mikeserv
6

Donc, vous voulez effectivement juste la partie médiane declare -psans les déchets autour d'elle?

Vous pouvez écrire une macro telle que:

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

afin que vous puissiez faire:

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

(Une simple fonction ne fera pas l'affaire si vous voulez l'utiliser sur des variables fonctionnelles locales).


Avec des alias

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

vartype a #scalar
vartype b #array
vartype c #hash
PSkocik
la source
@mikeserv Bon point. Les alias le rendent plus joli. +1
PSkocik
je voulais dire - alias vartype="$VARTYPE"... ou tout simplement pas définir du $VARTYPEtout - cela devrait fonctionner, non? vous devriez seulement avoir besoin de cette shoptchose bashcar elle rompt avec les spécifications concernant l' aliasexpansion dans les scripts.
mikeserv
1
@mikeserv Je suis sûr que cuonglm est bien capable de modifier cette approche en fonction de ses besoins et de ses préférences. ;-)
PSkocik
... et des considérations de sécurité.
PSkocik
À aucun moment, le code ci-dessus n'a validé le texte fourni par l'utilisateur. Ce n'est pas moins sûr qu'une fonction. Je ne vous ai jamais vu vous soucier de rendre les fonctions en lecture seule, mais OK, je peux marquer la variable en lecture seule.
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
la source
C'est peut echo ${(t)var}- être plus simple. Merci pour cela.
4

Pour tester la variable var, avec

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

Il est possible de tester s'il existe plusieurs index de tableau:

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

Si la première valeur d'index n'est pas nulle:

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

La seule confusion difficile est quand il n'y a qu'une seule valeur d'index et que cette valeur est zéro (ou un).

Pour cette condition, il est possible d'utiliser un effet secondaire en essayant de supprimer un élément de tableau d'une variable qui n'est pas un tableau:

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

Cela fonctionne correctement pour 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."

Pour zsh, l'index peut avoir besoin d'être 1 (sauf si un mode compatible est actif).

Le sous-shell est nécessaire pour éviter l'effet secondaire de l'effacement de l'indice 0 de var.

Je n'ai trouvé aucun moyen de le faire fonctionner dans ksh.

Modifier 1

Cette fonction ne fonctionne que dans 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

Modifier 2

Cela ne fonctionne également que pour bash4.2 +

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

Remarque: Cela donnera des faux positifs si var contient les chaînes testées.


la source
Que diriez-vous d'un tableau avec zéro élément?
cuonglm
1
Modifier les données, tho. Semble très original. : D
PSkocik
@cuonglm La vérification ( unset "var[0]" 2>/dev/null; ) && echo "var is an array."indique correctement que var est un tableau lorsque var a été défini sur var=()un tableau avec zéro élément. Il agit exactement égal à déclarer.
Le test de scalaire ne fonctionnera pas si le scalaire est exporté ou marqué entier / minuscule / en lecture seule ... Vous pouvez probablement faire en sorte que toute autre sortie non vide signifie une variable scalaire. J'utiliserais grep -Eau lieu de grep -Ppour éviter la dépendance à GNU grep.
Stéphane Chazelas
@ StéphaneChazelas Le test (en bash) pour scalaire avec entier et / ou en minuscules et / ou readonly commencent toujours avec -a, comme ceci: declare -airl var='()'. Par conséquent, le test grep fonctionnera .
3

Pour bash , c'est un peu un hack (bien que documenté): essayez d'utiliser typesetpour supprimer l'attribut "array":

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

(Vous ne pouvez pas faire cela dans zsh, cela vous permet de convertir un tableau en scalaire, dans bashce qui est explicitement interdit.)

Donc:

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

Ou dans une fonction, en notant les mises en garde à la fin:

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
}

Notez l'utilisation de typeset -g(bash-4.2 ou version ultérieure), ceci est requis dans une fonction pour que typeset(syn. declare) Ne fonctionne pas comme localet n'encombre pas la valeur que vous essayez d'inspecter. Cela ne gère pas non plus les types de fonction "variable", vous pouvez ajouter un autre test de branche en utilisant typeset -fsi nécessaire.


Une autre option (presque complète) consiste à utiliser ceci:

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

Il y a cependant un léger problème, un tableau avec un seul indice de 0 correspond à deux des conditions ci-dessus. C'est quelque chose que mikeserv fait également référence, bash n'a vraiment pas de distinction difficile, et une partie de cela (si vous vérifiez le Changelog) peut être blâmée sur ksh et compatible avec comment ${name[*]}ou ${name[@]}se comporter sur un non-tableau.

Une solution partielle est donc:

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

J'ai utilisé dans le passé une variation à ce sujet:

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

cela aussi a besoin d'un sous-shell.

Une autre technique peut-être plus utile est compgen:

compgen -A arrayvar

Ceci listera tous les tableaux indexés, cependant les tableaux associatifs ne sont pas traités spécialement (jusqu'à bash-4.4) et apparaissent comme des variables régulières ( compgen -A variable)

Mr Spuratic
la source
Le typeset +asignale également une erreur dans ksh. Pas en zsh, cependant.
1

Réponse courte:

Pour les deux shells qui ont introduit cette notation ( bashet ksh93) une variable scalaire est juste un tableau avec un seul élément .

Aucun n'a besoin d'une déclaration spéciale pour créer un tableau. Juste l'affectation est suffisante, et une affectation simple var=valueest identique à var[0]=value.

Henk Langeveld
la source
Essayez: bash -c 'unset var; var=foo; typeset -p var'. Est-ce que bash answer rapporte un tableau (nécessite un -a)?. Maintenant , comparez avec: bash -c 'unset var; var[12]=foo; typeset -p var'. Pourquoi y a-t-il une différence?. R: Le shell maintient (pour le meilleur ou pour le pire) une notion dont les vars sont des scalaires ou des tableaux. Le shell ksh mélange les deux concepts en un seul.
1

La fonction intégrée de arrayyash possède certaines options qui ne fonctionnent qu'avec des variables de tableau. Exemple: l' -doption signalera une erreur sur une variable non-tableau:

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

Nous pouvons donc faire quelque chose comme ceci:

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

Cette approche ne fonctionnera pas si la variable tableau est en lecture seule . Essayer de modifier une variable en lecture seule conduisant à une erreur:

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