Question Quelle est la manière concise de vérifier que les variables d'environnement sont définies dans un script shell Unix?


J'ai quelques scripts shell Unix où j'ai besoin de vérifier que certaines variables d'environnement sont définies avant de commencer à faire des choses, donc 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 est beaucoup de frappe. Existe-t-il un idiome plus élégant pour vérifier qu'un ensemble de variables d'environnement est défini?

EDIT: Je dois mentionner que ces variables n'ont pas de valeur par défaut significative - le script devrait faire une erreur s'il y en a.


431
2017-11-21 01:11


origine


Réponses:


Expansion de paramètre

La réponse évidente est d'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 (en utilisant juste ?) nécessite STATE pour être défini, mais STATE = "" (une chaîne vide) est OK - 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.

le ${var?} Construire est portable vers la version 7 UNIX et le Bourne Shell (1978 ou à peu près). le ${var:?} La construction est un peu plus récente: je pense que c'était dans le système III UNIX vers 1981, mais cela pouvait être dans PWB UNIX avant cela. Il est donc dans le shell Korn, et dans les coquilles POSIX, y compris spécifiquement Bash.

Il est généralement documenté dans la page de manuel du shell dans une section appelée Expansion de paramètre. Par exemple, le bash Le manuel dit:

${parameter:?word}

Afficher l'erreur si Null ou Unset. Si le paramètre est null ou unset, l'extension de mot (ou un message à cet effet si le mot 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.

La commande Colon

Je devrais probablement ajouter que la commande deux-points a simplement ses arguments évalués et réussit ensuite. C'est la notation de commentaire du shell d'origine (avant '#'à la fin de la ligne). Pendant longtemps, les scripts de shell Bourne ont eu un deux-points comme premier caractère. Le C Shell lirait un script et utiliserait le premier caractère pour déterminer si c'était pour le C Shell (un '#'hash) ou le shell Bourne (a':'deux points). Ensuite, le noyau est entré en scène et a ajouté du soutien pour '#!/path/to/program'et le shell Bourne a'#'commentaires, et la convention du côlon est allé au bord du chemin. Mais si vous rencontrez un script qui commence par deux points, vous saurez maintenant pourquoi.


Position des guillemets

blong demandé dans un commentaire:

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

L'essentiel de la discussion est:

... Cependant, quand je shellcheck (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 "faire 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 le :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" au lieu 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 comment la valeur dans $x est étendu à la première * puis une liste de noms de fichiers lorsque l'expression globale n'est pas entre guillemets. C'est quoi shellcheck recommande devrait être corrigé. Je n'ai pas vérifié qu'il ne s'oppose pas à la forme où l'expression est entre guillemets, mais c'est une hypothèse raisonnable que ce serait correct.


518
2017-11-21 03:25



Essaye ça:

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

107
2017-11-21 01:32



Votre question dépend du shell que vous utilisez.

Bourne shell laisse très peu de ce que vous recherchez.

MAIS...

Cela fonctionne, à peu près partout.

Essaie juste de rester loin de csh. C'était bien pour les cloches et les sifflets ajoutés, comparé à la carapace de Bourne, mais ça craque vraiment maintenant. Si vous ne me croyez pas, essayez de séparer STDERR dans csh! (-:

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

${MyVariable:=SomeDefault}

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

Vous avez également la possibilité de:

${MyVariable:-SomeDefault}

qui remplace simplement SomeDefault pour la variable où vous utilisez cette construction. Il n'attribue pas la valeur SomeDefault à la variable, et la valeur de MyVariable sera toujours nulle une fois cette instruction rencontrée.


52
2017-11-21 01:45



L'approche la plus simple consiste certainement à ajouter -u passer au shebang (la ligne en haut de votre script), en supposant que vous utilisez bash:

#!/bin/sh -u

Cela provoquera la sortie du script si des variables non liées se trouvent à l'intérieur.


44
2017-10-26 15:21



${MyVariable:=SomeDefault}

Si MyVariable est défini et non nul, il réinitialisera la valeur de la variable (= rien ne se passe).
Autre, MyVariable est réglé sur SomeDefault.

Ce qui précède tentera d'exécuter ${MyVariable}, donc si vous voulez simplement définir la variable, faites:

MyVariable=${MyVariable:=SomeDefault}

32
2017-11-21 01:24



À mon avis, le contrôle le plus simple et le plus compatible pour #! / bin / sh est:

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

Encore une fois, ceci est pour / bin / sh et est également compatible avec les anciens systèmes Solaris.


19
2017-10-11 17:39



J'ai toujours utilisé:

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

Pas vraiment plus concis, j'en ai peur.

Sous CSH, vous avez $? STATE.


13
2017-11-21 02:24



bash 4.2 introduit le -v opérateur qui teste si un nom est défini sur tout 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

11
2017-09-21 15:56



Aucune des solutions ci-dessus n'a fonctionné pour mes besoins, en partie parce que je cherchais dans l'environnement une liste non exhaustive de variables à définir avant de commencer un long processus. Je me suis retrouvé 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
2018-03-09 14:04



Nous pouvons écrire une belle assertion pour vérifier plusieurs variables en une fois:

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

1
2018-05-16 05:05