Comment tester si une variable est définie dans Bash avant la version 4.2 avec l'option shell nounset?

16

Pour les versions de Bash antérieures à "GNU bash, version 4.2", existe-t-il des alternatives équivalentes pour l' -voption de la testcommande? Par exemple:

shopt -os nounset
test -v foobar && echo foo || echo bar
# Output: bar
foobar=
test -v foobar && echo foo || echo bar
# Output: foo
Tim Friske
la source
-vn'est pas une option pour test, mais un opérateur pour les expressions conditionnelles.
StackExchange for All
@Tim C'est trois choses, en plus d'être un jeton, une chaîne et une partie d'une ligne: An optionà une commande test -v, un operatorà un conditional expressionet un unary test primarypour [ ]. Ne mélangez pas la langue anglaise avec les définitions de shell.

Réponses:

36

Portable sur tous les shells POSIX:

if [ -n "${foobar+1}" ]; then
  echo "foobar is defined"
else
  echo "foobar is not defined"
fi

Faites cela ${foobar:+1}si vous voulez traiter de foobarla même manière, qu'il soit vide ou non défini. Vous pouvez également utiliser ${foobar-}pour obtenir une chaîne vide lorsque foobarest indéfini et la valeur de foobarsinon (ou mettre toute autre valeur par défaut après le -).

Dans ksh, if foobarest déclaré mais non défini, comme dans typeset -a foobar, puis se ${foobar+1}développe dans la chaîne vide.

Zsh n'a pas de variables déclarées mais non définies: typeset -a foobarcrée un tableau vide.

En bash, les tableaux se comportent de manière différente et surprenante. ${a+1}ne se développe que 1si aest un tableau non vide, par exemple

typeset -a a; echo ${a+1}    # prints nothing
e=(); echo ${e+1}            # prints nothing!
f=(''); echo ${f+1}          # prints 1

Le même principe s'applique aux tableaux associatifs: les variables de tableau sont traitées comme définies si elles ont un ensemble d'indices non vide.

Une manière différente, spécifique à bash, de tester si une variable de n'importe quel type a été définie consiste à vérifier si elle est répertoriée dans . Cela signale les tableaux vides tels que définis, contrairement à , mais signale les variables déclarées mais non affectées ( ) comme non définies.${!PREFIX*}${foobar+1}unset foobar; typeset -a foobar

case " ${!foobar*} " in
  *" foobar "*) echo "foobar is defined";;
  *) echo "foobar is not defined";;
esac

Cela revient à tester la valeur de retour de typeset -p foobaroudeclare -p foobar , sauf qu'il typeset -p foobaréchoue sur les variables déclarées mais non affectées.

Dans bash, comme dans ksh, set -o nounset; typeset -a foobar; echo $foobardéclenche une erreur dans la tentative de développement de la variable non définie foobar. Contrairement à ksh, set -o nounset; foobar=(); echo $foobar(ou echo "${foobar[@]}") déclenche également une erreur.

Notez que dans toutes les situations décrites ici, se ${foobar+1}développe dans la chaîne vide si et seulement si $foobarprovoquerait une erreur sous set -o nounset.

Gilles 'SO- arrête d'être méchant'
la source
1
Et les tableaux? Dans la version Bash, "GNU bash, version 4.1.10 (4) -release (i686-pc-cygwin)" echo "${foobar:+1}"ne s'imprime pas 1si a declare -a foobarété précédemment émis et foobarest donc un tableau indexé. declare -p foobarrapporte correctement declare -a foobar='()'. Ne "${foobar:+1}"fonctionne que pour les variables non matricielles?
Tim Friske
@TimFriske ${foobar+1}(sans le :, j'ai inversé deux exemples dans ma réponse d'origine) est correct pour les tableaux en bash si votre définition de «défini» est « $foobarfonctionnerait sous set -o nounset». Si votre définition est différente, bash est un peu bizarre. Voir ma réponse mise à jour.
Gilles 'SO- arrête d'être méchant'
1
Concernant le sujet "En bash, les tableaux se comportent de manière différente et surprenante." le comportement peut être expliqué à partir des pages de manuel bash (1), section "Tableaux". Il indique que «référencer une variable de tableau sans indice équivaut à référencer le tableau avec un indice de 0». Ainsi, si ni un 0index ni une clé n'est défini comme il est vrai pour a=(), ${a+1}ne renvoie correctement rien.
Tim Friske
1
@TimFriske Je sais que l'implémentation bash est conforme à sa documentation. Mais traiter un tableau vide comme une variable non définie est une conception vraiment étrange.
Gilles 'SO- arrête d'être méchant'
Je préfère le résultat de: Comment vérifier si une variable est définie dans Bash? -> La bonne façon ... Je touche à la façon dont je pense que cela devrait fonctionner. Oserais-je dire que Windows a un definedopérateur. Test pourrait le faire ; ça ne peut pas être difficile ( euh ...)
le
5

Pour résumer la réponse de Gilles, j'ai établi mes règles suivantes:

  1. Utiliser [[ -v foobar ]]pour les variables dans la version Bash> = 4.2.
  2. Utiliser declare -p foobar &>/dev/nullpour les variables de tableau dans la version Bash <4.2.
  3. Utilisez (( ${foo[0]+1} ))ou (( ${bar[foo]+1} ))pour les indices des tableaux indexés ( -a) et keyed ( -A) ( declare), respectivement. Les options 1 et 2 ne fonctionnent pas ici.
Tim Friske
la source
3

J'utilise la même technique pour toutes les variables en bash, et cela fonctionne, par exemple:

[ ${foobar} ] && echo "foobar is set" || echo "foobar is unset"

les sorties:

foobar is unset

tandis que

foobar=( "val" "val2" )
[ ${foobar} ] && echo "foobar is set" || echo "foobar is unset"

les sorties:

foobar is set
Stein Inge Morisbak
la source
J'ai dû supprimer [@] si le tableau a plusieurs valeurs.
Stein Inge Morisbak
1
Cela fonctionne très bien, tant que vous voulez tester si elle a une valeur, pas si elle a été définie. C'est-à-dire que foobar=""je ferai ensuite rapport de cela foobar is unset. Non attends, je reprends ça. Vraiment ne teste que si le premier élément est vide ou non, donc cela ne semble être une bonne idée que si vous savez que la variable n'est PAS un tableau, et que vous ne vous souciez que du vide, pas de la définition.
Ron Burk
ne fonctionne que si vos scripts s'exécutent avec des variables non définies autorisées (pas de set -u)
Florian Heigl