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


500

J'ai quelques scripts shell Unix où je dois 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 devrais mentionner que ces variables n'ont pas de valeur par défaut significative - le script devrait sortir en erreur s'il n'y en a pas.


2
Beaucoup de réponses à cette question ressemblent à quelque chose que vous verriez sur Code Golf Stack Exchange .
Daniel Schilling,

Réponses:


587

Expansion des paramètres

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 simplement ?) nécessite que STATE soit 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.

La ${var?}construction est portable vers la version 7 UNIX et le Bourne Shell (1978 ou environ). La ${var:?}construction est un peu plus récente: je pense que c'était dans System III UNIX vers 1981, mais peut-être dans PWB UNIX avant cela. Il se trouve donc dans le Korn Shell, et dans les shells POSIX, notamment spécifiquement Bash.

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

${parameter:?word}

Erreur d'affichage si Null ou Unset. Si le paramètre est nul ou non défini, l'expansion de word (ou d'un message à cet effet si ce mot n'est pas présent) est écrite 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 Colon Command

Je devrais probablement ajouter que la commande colon a simplement ses arguments évalués puis réussit. C'est la notation originale du commentaire du shell (avant ' #' jusqu'à la fin de la ligne). Pendant longtemps, les scripts shell Bourne ont eu deux points comme premier caractère. Le C Shell lirait un script et utiliserait le premier caractère pour déterminer s'il s'agissait du C Shell (un ' #' hachage) ou du Bourne shell (un ' :' deux-points). Ensuite, le noyau est intervenu et a ajouté un support pour les commentaires ' #!/path/to/program' et le shell Bourne ' #', et la convention du côlon a été abandonnée. Mais si vous tombez sur un script qui commence par deux points, vous saurez maintenant pourquoi.


Position des guillemets doubles

Blong a demandé dans un commentaire :

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

L'essentiel de la discussion est:

… Cependant, quand je shellcheckle fais (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.

Un conseil sur ce que je dois faire dans ce cas?

La réponse courte est "faites comme shellchecksuggéré":

: "${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 comment la valeur de $xest développée en premier *, puis une liste de noms de fichiers lorsque l'expression globale n'est pas entre guillemets. C'est ce qui shellcheckrecommande devrait être corrigé. Je n'ai pas vérifié qu'il ne s'oppose pas au formulaire où l'expression est placée entre guillemets, mais c'est une supposition raisonnable que ce serait OK.


2
Voilà la chose dont j'ai besoin. J'utilise diverses versions d'Unix depuis 1987 et je n'ai jamais vu cette syntaxe - va juste pour montrer ...
AndrewR

Comment cela fonctionne-t-il et où est-il documenté? Je me demande si elle peut être modifiée pour vérifier la variable existe et est définie sur une valeur spécifique.
jhabbott

6
Il est documenté dans la page de manuel du shell, ou dans le manuel de Bash, généralement sous l'en-tête «Expansion des paramètres». La méthode standard pour vérifier si la variable existe et est définie à une valeur spécifique (dire « abc ») est tout simplement: if [ "$variable" = "abc" ]; then : OK; else : variable is not set correctly; fi.
Jonathan Leffler

Comment utiliser cette syntaxe avec une variable de chaîne contenant des espaces. J'obtiens une erreur disant "This: command not found" lorsque je règle la variable que je veux vérifier sur "This is a test". Des idées?
user2294382

1
Des réflexions sur cette discussion? github.com/koalaman/shellcheck/issues/…
blong

138

Essaye ça:

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

8
C'est assez verbeux. Le point-virgule est redondant.
Jonathan Leffler

3
Ou encore plus court [ -z "$STATE" ] && { echo "Need to set STATE"; exit 1; } voir ce billet de blog
zipizap

36
Ce qui est, est lisible. Je suis tout à fait pour celui-ci. Les scripts Bash ont besoin de toute l'aide qu'ils peuvent obtenir dans ce département!
Iain Duncan

la réponse originale est plus cohérente dans sa syntaxe.
dent rapide

4
Toutefois, cela ne fait pas de distinction entre une variable non définie et une variable définie sur la chaîne vide.
chepner

56

Votre question dépend du shell que vous utilisez.

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

MAIS...

Cela fonctionne, à peu près partout.

Essayez simplement de rester loin de csh. Il était bon pour les cloches et les sifflets qu'il a ajoutés, comparé à la coque Bourne, mais il grince vraiment maintenant. Si vous ne me croyez pas, essayez simplement 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 actuellement pas défini, affecte 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 dans laquelle vous utilisez cette construction. Il n'affecte pas la valeur SomeDefault à la variable, et la valeur de MyVariable sera toujours nulle après que cette instruction soit rencontrée.


3
CSH: (foo> foo.out)> & foo.err
Mr.Ree

Le shell Bourne fait ce qui est nécessaire.
Jonathan Leffler

51

Assurément, l'approche la plus simple consiste à ajouter le -ucommutateur 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 se cachent à l'intérieur.


40
${MyVariable:=SomeDefault}

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

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

MyVariable=${MyVariable:=SomeDefault}

Cela ne génère pas le message - et le Q indique qu'il n'y a pas de défaut (bien qu'il ne l'ait pas dit lorsque vous avez tapé votre réponse).
Jonathan Leffler

10
Versions correctes: (1): $ {MyVariable: = SomeDefault} ou (2): MyVariable = $ {MyVariable: -SomeDefault}
Jonathan Leffler

23

À mon avis, la vérification la plus simple et la plus compatible pour #! / Bin / sh est:

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

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


Cela «fonctionne», mais même les anciens systèmes Solaris en ont un /bin/shqui prend en charge la ${var:?}notation, etc.
Jonathan Leffler

La meilleure réponse jusqu'à présent.
Ole

4
Être défini sur la chaîne vide est différent de ne pas être défini du tout. Ce test afficherait "N'existe pas" après les deux unset MYVARet MYVAR=.
chepner

@chepner, j'ai voté pour votre commentaire pour le heads-up vraisemblablement précieux, mais j'ai continué à le tester (avec bash), et je ne pouvais pas réellement définir un var sur une chaîne vide sans qu'il ne soit également désactivé. Comment feriez-vous cela?
Sz.

@Sz, je ne sais pas ce que tu veux dire. "Non défini" signifie que le nom n'a aucune valeur qui lui est affectée. Si foon'est pas défini, "$foo"se développe toujours dans la chaîne vide.
chepner

17

bash4.2 a introduit l' -vopérateur qui teste si un nom est défini sur n'importe quelle 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

J'ai toujours utilisé:

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

Pas beaucoup plus concis, je le crains.

Sous CSH, vous avez $? STATE.


2
Cela vérifie si STATEest vide ou non défini, pas seulement non défini.
chepner

7

Pour de futures personnes comme moi, je voulais faire un pas en avant et paramétrer le nom var, donc je peux 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

3

Nous pouvons écrire une belle assertion pour vérifier un tas de variables à la 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'appel:

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

Production:

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

Plus d'assertions de ce type ici: https://github.com/codeforester/base/blob/master/lib/assertions.sh



1

Aucune des solutions ci-dessus n'a fonctionné pour mes besoins, en partie parce que je vérifie l'environnement pour une liste ouverte de variables qui doivent être définies avant de démarrer 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

Plutôt que d'utiliser des scripts shell externes, j'ai tendance à charger des fonctions dans mon shell de connexion. J'utilise quelque chose comme ça comme fonction d'aide pour vérifier les variables d'environnement plutôt que n'importe quelle variable définie:

is_this_an_env_variable ()
    local var="$1"
    if env |grep -q "^$var"; then
       return 0
    else
       return 1
    fi
 }

1
C'est faux. is_this_an_env_variable Pretournera le succès parce qu'il PATHexiste.
Eric

Il existe parce que c'est une variable d'environnement. Qu'il ait été défini une chaîne non nulle eqaul est une autre affaire.
nafdef

-7

La $?syntaxe est assez soignée:

if [ $?BLAH == 1 ]; then 
    echo "Exists"; 
else 
    echo "Does not exist"; 
fi

Cela fonctionne, mais est-ce que quelqu'un ici sait où trouver des documents sur cette syntaxe? $? est normalement la valeur de retour précédente.
Danny Staple

Mon mauvais - cela ne semble pas fonctionner. Essayez-le dans un script simple, avec et sans cet ensemble.
Danny Staple

11
Dans les shells Bourne, Korn, Bash et POSIX, $?l'état de sortie de la commande précédente; $?BLAHest la chaîne constituée de l'état de sortie de la commande précédente concaténée avec 'BLAH'. Cela ne teste pas du $BLAHtout la variable . (Il y a une rumeur selon laquelle cela pourrait faire plus ou moins ce qui est requis csh, mais les coquillages sont mieux laissés sur le bord de la mer.)
Jonathan Leffler

C'est une réponse complètement fausse en ce qui concerne Bash.
codeforester
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.