web-dev-qa-db-fra.com

Quelle est une manière concise de vérifier que les variables d’environnement sont définies dans un script Unix Shell?

J'ai quelques scripts Unix Shell pour lesquels je dois vérifier que certaines variables d'environnement sont définies avant de commencer à faire des choses, alors je fais ce genre de chose:

if [ -z "$STATE" ]; then
    echo "Need to set STATE"
    exit 1
fi  

if [ -z "$DEST" ]; then
    echo "Need to set DEST"
    exit 1
fi

ce qui fait beaucoup de dactylographie. Existe-t-il un langage plus élégant pour vérifier qu’un ensemble de variables d’environnement est défini?

EDIT: Je devrais mentionner que ces variables n’ont pas de valeur implicite significative - le script doit afficher une erreur si certaines ne sont pas définies.

478
AndrewR

Paramètre Expansion

La réponse évidente consiste à utiliser l’une des formes spéciales d’expansion des paramètres:

: ${STATE?"Need to set STATE"}
: ${DEST:?"Need to set DEST non-empty"}

Ou mieux (voir la section "Position des guillemets doubles" ci-dessous):

: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"

La première variante (utilisant uniquement ?) nécessite de définir STATE, mais STATE = "" (une chaîne vide) est OK. Ce n'est pas exactement ce que vous voulez, mais la notation alternative et plus ancienne.

La deuxième variante (en utilisant :?) nécessite que DEST soit défini et non vide.

Si vous ne fournissez aucun message, le shell fournit un message par défaut.

La construction ${var?} est portable vers la version 7 UNIX et le Bourne Shell (1978 ou environ). La construction ${var:?} est légèrement plus récente: je pense que c'était dans System III UNIX vers 1981, mais peut-être dans PWB UNIX auparavant. C'est donc dans le shell Korn, et dans les shell POSIX, notamment Bash.

Il est généralement décrit dans la page de manuel du shell dans une section intitulée Expansion des paramètres . Par exemple, le manuel bash dit:

${parameter:?word}

Erreur d'affichage si Null ou Unset. Si le paramètre est null ou non défini, le développement de Word (ou d'un message à cet effet si Word n'est pas présent) est écrit dans l'erreur standard et le shell, s'il n'est pas interactif, se ferme. Sinon, la valeur du paramètre est substituée.

Le commandement du colon

Je devrais probablement ajouter que la commande deux points a simplement ses arguments évalués puis réussit. Il s'agit de la notation de commentaire d'origine du shell (avant '#' jusqu'à la fin de la ligne). Pendant longtemps, les scripts Bourne Shell avaient un caractère deux-points comme premier caractère. Le shell C lit un script et utilise le premier caractère pour déterminer s’il s’agit du shell C (un hachage '#') ou du Bourne Shell (un ':'). Ensuite, le noyau est entré en jeu et a ajouté le support pour '#!/path/to/program' et le Bourne Shell a reçu les commentaires '#' ', et la convention des deux points est passée outre. Mais si vous rencontrez un script qui commence par un deux-points, vous saurez maintenant pourquoi.


Position des guillemets

blong demandé dans un commentaire :

Des réflexions sur cette discussion? https://github.com/koalaman/shellcheck/issues/380#issuecomment-145872749

Le résumé de la discussion est:

… Cependant, quand je shellcheck le (avec la version 0.4.1), je reçois ce message:

In script.sh line 13:
: ${FOO:?"The environment variable 'FOO' must be set and non-empty"}
  ^-- SC2086: Double quote to prevent globbing and Word splitting.

Des conseils sur ce que je devrais faire dans ce cas?

La réponse courte est "faites comme shellcheck suggère":

: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"

Pour illustrer pourquoi, étudiez ce qui suit. Notez que la commande : ne fait pas écho à ses arguments (mais le shell évalue les arguments). Nous voulons voir les arguments, donc le code ci-dessous utilise printf "%s\n" à la place de :.

$ mkdir junk
$ cd junk
$ > abc
$ > def
$ > ghi
$ 
$ x="*"
$ printf "%s\n" ${x:?You must set x}    # Careless; not recommended
abc
def
ghi
$ unset x
$ printf "%s\n" ${x:?You must set x}    # Careless; not recommended
bash: x: You must set x
$ printf "%s\n" "${x:?You must set x}"  # Careful: should be used
bash: x: You must set x
$ x="*"
$ printf "%s\n" "${x:?You must set x}"  # Careful: should be used
*
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
abc
def
ghi
$ x=
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ unset x
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ 

Notez que la valeur dans $x est étendue à * d'abord, puis à une liste de noms de fichiers lorsque l'expression globale n'est pas entre guillemets. C’est ce que shellcheck recommande de corriger. Je n'ai pas vérifié qu'il ne s'oppose pas à la forme où l'expression est placée entre guillemets, mais il s'agit d'une hypothèse raisonnable que ce serait OK.

563
Jonathan Leffler

Essaye ça:

[ -z "$STATE" ] && echo "Need to set STATE" && exit 1;
127
David Schlosnagle

Votre question dépend du shell que vous utilisez.

Bourne Shell laisse très peu de choses sur ce que vous recherchez.

MAIS...

Cela fonctionne, presque partout.

Essayez simplement de rester à l'écart de csh. C'était bien pour les cloches et les sifflets, a-t-il ajouté, comparé au Bourne Shell, mais ça craque vraiment maintenant. Si vous ne me croyez pas, essayez simplement de séparer STDERR en csh! (-:

Il y a deux possibilités ici. L'exemple ci-dessus, à savoir utiliser:

${MyVariable:=SomeDefault}

pour la première fois, vous devez vous référer à $ MyVariable. Cela prend env. var MyVariable et, s'il n'est pas défini, attribue la valeur de SomeDefault à la variable pour une utilisation ultérieure.

Vous avez également la possibilité de:

${MyVariable:-SomeDefault}

qui substitue simplement SomeDefault à la variable où vous utilisez cette construction. Il n'affecte pas la valeur SomeDefault à la variable et la valeur de MyVariable sera toujours nulle après la rencontre de cette instruction.

55
Rob Wells

La méthode la plus simple consiste sûrement à ajouter le commutateur -u au Shebang (la ligne en haut de votre script), en supposant que vous utilisez bash:

#!/bin/sh -u

Cela entraînera la fermeture du script si des variables non liées s'y cachent.

47
Paul Makkar
${MyVariable:=SomeDefault}

Si MyVariable est défini et non nul, la valeur de la variable sera réinitialisée (= rien ne se passe).
Sinon, MyVariable est réglé sur SomeDefault.

Ce qui précède va tenter d'exécuter ${MyVariable}, alors si vous voulez juste définir la variable, faites:

MyVariable=${MyVariable:=SomeDefault}
37

A mon avis, le test le plus simple et le plus compatible pour #!/Bin/sh est:

if [ "$MYVAR" = "" ]
then
   echo "Does not exist"
else
   echo "Exists"
fi

Là encore, cela concerne/bin/sh et est également compatible sur les anciens systèmes Solaris.

20
Adriano

bash 4.2 a introduit l'opérateur -v qui teste si un nom est défini sur any valeur, même la chaîne vide.

$ unset a
$ b=
$ c=
$ [[ -v a ]] && echo "a is set"
$ [[ -v b ]] && echo "b is set"
b is set
$ [[ -v c ]] && echo "c is set"
c is set
16
chepner

J'ai toujours utilisé:

if [ "x$STATE" == "x" ]; then echo "Need to set State"; exit 1; fi

Pas trop concis, j'en ai bien peur.

Sous CSH, vous avez $? STATE.

14
Mr.Ree

Pour les personnes futures comme moi, je voulais faire un pas en avant et paramétrer le nom de la variable afin de pouvoir parcourir une liste de noms de variables de taille variable:

#!/bin/bash
declare -a vars=(NAME GITLAB_URL GITLAB_TOKEN)

for var_name in "${vars[@]}"
do
  if [ -z "$(eval "echo \$$var_name")" ]; then
    echo "Missing environment variable $var_name"
    exit 1
  fi
done
4
nicooga

Nous pouvons écrire une assertion de Nice pour vérifier plusieurs variables en même temps:

#
# assert if variables are set (to a non-empty string)
# if any variable is not set, exit 1 (when -f option is set) or return 1 otherwise
#
# Usage: assert_var_not_null [-f] variable ...
#
function assert_var_not_null() {
  local fatal var num_null=0
  [[ "$1" = "-f" ]] && { shift; fatal=1; }
  for var in "$@"; do
    [[ -z "${!var}" ]] &&
      printf '%s\n' "Variable '$var' not set" >&2 &&
      ((num_null++))
  done

  if ((num_null > 0)); then
    [[ "$fatal" ]] && exit 1
    return 1
  fi
  return 0
}

Exemple d'invocation:

one=1 two=2
assert_var_not_null one two
echo test 1: return_code=$?
assert_var_not_null one two three
echo test 2: return_code=$?
assert_var_not_null -f one two three
echo test 3: return_code=$? # this code shouldn't execute

Sortie:

test 1: return_code=0
Variable 'three' not set
test 2: return_code=1
Variable 'three' not set
2
codeforester

Cela peut aussi être un moyen:

if (set -u; : $HOME) 2> /dev/null
...
...

http://unstableme.blogspot.com/2007/02/checks-whether-envvar-is-set-or-not.html

2
Jadu Saikia

Aucune des solutions ci-dessus n'a fonctionné à mes fins, en partie parce que je vérifiais dans l'environnement une liste non exhaustive de variables à définir avant de démarrer un long processus. J'ai fini avec ceci:

mapfile -t arr < variables.txt

EXITCODE=0

for i in "${arr[@]}"
do
   ISSET=$(env | grep ^${i}= | wc -l)
   if [ "${ISSET}" = "0" ];
   then
      EXITCODE=-1
      echo "ENV variable $i is required."
   fi
done

exit ${EXITCODE}
1