Tester si plusieurs variables sont définies


19

Je voudrais m'assurer qu'à un certain point d'un script, après avoir sourcecréé un fichier de configuration, plusieurs variables sont définies et, si ce n'est pas le cas, arrêter l'exécution en informant l'utilisateur de la variable manquante. j'ai essayé

for var in $one $two $three ; do
    ...

mais si par exemple $twon'est pas défini, la boucle n'est jamais exécutée pour $two. La prochaine chose que j'ai essayée était

for var in one two three ; do
    if [ -n ${!var} ] ; then
        echo "$var is set to ${!var}"
    else
        echo "$var is not set"
    fi
done

Mais si deux n'est pas défini, je reçois toujours "deux est défini sur" au lieu de "deux n'est pas défini".

Comment puis-je m'assurer que toutes les variables requises sont définies?

Mise à jour / Solution: je sais qu'il y a une différence entre "set" et "set, but empty". J'utilise maintenant (grâce à /programming//a/16753536/3456281 et les réponses à cette question) les éléments suivants:

if [ -n "${!var:-}" ] ; then

ainsi, s'il varest défini mais vide, il est toujours considéré comme invalide.


1
Vous pouvez également ajouter set -uau début de votre script pour le terminer immédiatement lorsqu'une variable non définie est utilisée.
n.st

Réponses:


8

Erreur de citation.

if [ -n "${!var}" ] ; then

Pour l'avenir: le cadre

set -x

avant d'exécuter le code vous aurait montré le problème. Au lieu d'ajouter cela au code, vous pouvez appeler votre script avec

bash -vx ./my/script.sh

Cela fonctionne, mais que se passe-t-il avec / sans guillemets? Mon approche générale est-elle correcte en premier lieu?
Jasper

Oui, c'est précognitif: j'ai répondu à la question avant qu'elle ne soit posée ... 8-)
Hauke ​​Laging

4
@Jasper Vous devez toujours citer des variables. Cela ne coûte pas plus de temps que de se demander si cela est nécessaire à chaque fois. Mais cela évite les erreurs.
Hauke ​​Laging du

Au contraire, la citation ne protège que contre l'expansion. Si l'expansion vous intéresse, la citation n'est certainement pas la voie à suivre. Par exemple: cs = 673,290,765,; set - $ (IFS =,; echo $ cs); echo $ #; sortie: 3
mikeserv

2
@mikeserv Seules les guillemets simples protègent contre l'expansion, ce que je ne suggère évidemment pas. Les guillemets doubles protègent contre le fractionnement des mots. Je n'ai pas nié qu'il puisse y avoir des cas où la citation doit être omise. Mais ce n'est pas un argument contre ma règle générale. Quel genre de personnes va utiliser quelque chose comme ça set -- $(IFS=, ; echo $cs)? Le genre de personnes qui doivent demander ici pourquoi if [ -n ${!var} ] ; thenne fonctionne pas? Probablement pas.
Hauke ​​Laging

5

La seule chose dont vous avez besoin sont des citations dans votre test:

for var in one two three ; do
    if [ -n "${!var}" ] ; then
        echo "$var is set to ${!var}"
    else
        echo "$var is not set"
    fi
done

Travaille pour moi.


4

Si vous voulez que le programme soit arrêté:

N= 
${one?var 1 is unset} 
${two:?var 2 is unset or null}
${three:+${N:?var 3 is set and not null}}

Ça fera l'affaire. Chacun des messages suivant le point d'interrogation est imprimé stderret le shell parent meurt. Eh bien, OK, donc pas chaque message - un seul - juste le premier qui échoue imprime un message car le shell meurt. J'aime utiliser ces tests comme ceci:

( for v in "$one" "$two" "$three" ; do
    i=$((i+1)) ; : ${v:?var $i is unset or null...} 
done ) || _handle_it

J'avais beaucoup plus à dire à ce sujet ici .


2

Vous pouvez ajouter

set -u

au début de votre script pour qu'il se termine lorsqu'il essaie d'utiliser une variable non définie.

Un script comme

#!/bin/sh
set -u
echo $foo

aura pour résultat

script.sh: 3: script.sh: foo: paramètre non défini

Si vous utilisez à la bashplace, l'erreur ressemblera à ceci:

script.sh: ligne 3: foo: variable non liée


Et à votre avis, cela a-t-il un sens pour les contrôles de durée d'exécution?
Hauke ​​Laging

2
@HaukeLaging Je ne vous suis pas tout à fait - set -uempêche exactement le type d'erreur que l'OP essaie d'éviter et (contrairement à toutes les autres solutions) ne se limite pas à un ensemble spécifique de variables. C'est en fait une précaution utile pour presque tous les scripts shell de les faire échouer en toute sécurité au lieu de faire des choses inattendues lorsqu'une variable n'est pas définie. Cet article est une référence pratique sur le sujet.
2014

@ n.st Je ne suis pas d'accord - null peut être une valeur tout aussi utile que non null si vous le prévoyez.
mikeserv

1
@ n.st Cela n'a aucun sens pour l'OP car il ne veut pas de protection contre l'accès aux variables non définies (BTW: une variable définie mais vide provoquerait la même erreur mais ne réagirait pas à votre "protection"). Il souhaite une vérification de l'exécution si une variable n'est pas définie / vide. Votre suggestion peut être utile comme aide au développement générale mais ne résout pas le problème du PO.
Hauke ​​Laging

set -upourrait être utilisé aussi je pense, mais ce n'est pas aussi flexible que l'expansion des paramètres appropriée (voir ma question mise à jour). Et avec set -uje devrais faire un faux accès à toutes les variables cibles si je veux vérifier leur présence en un seul endroit.
Jasper

2

Une solution qui est au maximum conviviale teste toutes les exigences et les signale ensemble, plutôt que d'échouer à la première et d'exiger des allers-retours pour bien faire les choses:

#!/bin/bash

required_vars=(one two three)

missing_vars=()
for i in "${required_vars[@]}"
do
    test -n "${!i:+y}" || missing_vars+=("$i")
done
if [ ${#missing_vars[@]} -ne 0 ]
then
    echo "The following variables are not set, but should be:" >&2
    printf ' %q\n' "${missing_vars[@]}" >&2
    exit 1
fi

J'utilise une variable de tableau pour suivre les variables qui n'ont pas été définies et j'utilise le résultat pour composer un message utilisateur.

Remarques:

  • J'ai cité ${required_vars[@]}dans la forboucle principalement par habitude - je ne conseillerais pas d'inclure des métacaractères shell dans vos noms de variables!
  • Je n'ai pas cité ${#missing_vars[@]}, car c'est toujours un entier, même si vous êtes assez pervers pour ignorer les conseils précédents.
  • J'ai utilisé %qlors de l'impression; %sserait normalement suffisant.
  • La sortie d'erreur va toujours au flux d'erreurs avec >&2, donc elle n'est pas redirigée vers les commandes en aval
  • Le silence est d'or - n'imprimez pas les informations de progression ou les informations de débogage, sauf demande spécifique. Cela rend les erreurs plus évidentes.

1

bash4.2 vous permet de tester si une variable est définie avec l' -vopérateur; une variable non définie et une variable définie sur la chaîne vide sont deux conditions différentes:

$ unset foo
$ [[ -v foo ]] && echo foo is set
$ [[ -z "$foo" ]] && echo foo is empty
foo is empty
$ foo=
$ [[ -v foo ]] && echo foo is set
foo is set
$ [[ -z "$foo" ]] && echo foo is empty
foo is empty

Je posais des questions sur les "multiples" variables, donc je cherchais quelque chose comme l'indirection ...
Jasper

Vous n'avez pas besoin indirection, depuis -vprend le nom d'une variable, donc for var in one two three; [[ -v $var ]] && ...; donevérifierait si chacun one, twoet threesont situés dans la séquence.
chepner

1

Je pense que si vous voulez dire not set, la variable ne doit donc jamais être initialisée. Si vous utilisez [ -n "${!var}" ], la variable vide comme two=""échouera alors qu'elle est définie . Vous pouvez essayer ceci:

one=1
three=3

for var in one two three; do
  declare -p $var > /dev/null 2>&1 \
  && printf '%s is set to %s\n' "$var" "${!var}" \
  || printf '%s is not set\n' "$var"
done

0

Une solution concise (quoique légèrement hacky), pour gérer le cas où le sourcescript d peut définir les variables sur une valeur nulle, consiste à initialiser les variables à une valeur spéciale avant de rechercher le script, puis à vérifier cette valeur par la suite , par exemple

one=#UNSET#
two=#UNSET#
three=#UNSET#

. set_vars_script

if [[ $one$two$three == *#UNSET#* ]] ; then
  echo "One or more essential variables are unset" >&2
  exit 1
fi

1
une autre bonne solution, mais je voudrais donner un message d'erreur plus spécifique puis "une ou plusieurs variables non définies" ...
Jasper

0

J'ai étendu la réponse de @ mikeserv .

Cette version prend une liste de variables pour vérifier la présence de, imprimant le nom de la variable manquante et déclenchant un appel à usage () en cas d'erreur.

REQD_VALUES=("VARIABLE" "FOO" "BAR" "OTHER_VARIABLE")
( i=0; for var_name in ${REQD_VALUES[@]}; do
    VALUE=${!var_name} ;
    i=$((i+1)) ; : ${VALUE:?$var_name is missing}
done ) || usage

Exemple de sortie lorsqu'une valeur est manquante:

./myscript.sh: line 42: VALUE: OTHER_VARIABLE is missing

Notez que vous pouvez changer la variable appelée VALUE en un autre nom qui convient mieux à la sortie souhaitée.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.