Comment savoir si une chaîne n'est pas définie dans un script shell Bash

151

Si je veux vérifier la chaîne nulle, je le ferais

[ -z $mystr ]

mais que faire si je veux vérifier si la variable a été définie? Ou n'y a-t-il pas de distinction dans les scripts Bash?

Setjmp
la source
27
veuillez prendre l'habitude d'utiliser [-z "$ mystr"] par opposition à [-z $ mystr]
Charles Duffy
comment faire la chose inverse? Je veux dire quand la chaîne n'est pas nulle
Ouvrez le chemin
1
@flow: What about[ -n "${VAR+x}"] && echo not null
Lekensteyn
1
@CharlesDuffy J'ai déjà lu ceci dans de nombreuses ressources en ligne. Pourquoi est-ce préféré?
ffledgling
6
@Ayos car si vous n'avez pas de guillemets, le contenu de la variable est fractionné et globalisé; si c'est une chaîne vide, elle devient [ -z ]au lieu de [ -z "" ]; s'il a des espaces, il devient [ -z "my" "test" ]au lieu de [ -z my test ]; et si c'est le cas [ -z * ], le *est remplacé par les noms des fichiers de votre répertoire.
Charles Duffy

Réponses:

138

Je pense que la réponse que vous recherchez est implicite (sinon énoncée) par la réponse de Vinko , bien qu'elle ne soit pas énoncée simplement. Pour distinguer si VAR est défini mais vide ou non défini, vous pouvez utiliser:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "$VAR" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Vous pouvez probablement combiner les deux tests de la deuxième ligne en un seul avec:

if [ -z "$VAR" -a "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Cependant, si vous lisez la documentation d'Autoconf, vous constaterez qu'ils ne recommandent pas de combiner les termes avec « -a» et recommandent d'utiliser des tests simples séparés combinés avec &&. Je n'ai pas rencontré de système présentant un problème; cela ne veut pas dire qu'ils n'existaient pas (mais ils sont probablement extrêmement rares de nos jours, même s'ils n'étaient pas aussi rares dans un passé lointain).

Vous pouvez trouver les détails de ceux-ci, ainsi que d'autres extensions de paramètres de shell connexes , la commande testou[ et les expressions conditionnelles dans le manuel Bash.


J'ai récemment été interrogé par e-mail sur cette réponse avec la question:

Vous utilisez deux tests, et je comprends bien le second, mais pas le premier. Plus précisément, je ne comprends pas la nécessité d'une expansion variable

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi

Cela ne ferait-il pas la même chose?

if [ -z "${VAR}" ]; then echo VAR is not set at all; fi

Bonne question - la réponse est «Non, votre alternative plus simple ne fait pas la même chose».

Supposons que j'écris ceci avant votre test:

VAR=

Votre test dira "VAR n'est pas du tout défini", mais le mien dira (par implication car il ne fait écho à rien) "VAR est défini mais sa valeur peut être vide". Essayez ce script:

(
unset VAR
if [ -z "${VAR+xxx}" ]; then echo JL:1 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:1 VAR is not set at all; fi
VAR=
if [ -z "${VAR+xxx}" ]; then echo JL:2 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:2 VAR is not set at all; fi
)

La sortie est:

JL:1 VAR is not set at all
MP:1 VAR is not set at all
MP:2 VAR is not set at all

Dans la deuxième paire de tests, la variable est définie, mais elle est définie sur la valeur vide. C'est la distinction que font les notations ${VAR=value}et ${VAR:=value}. Idem pour ${VAR-value}et ${VAR:-value}, et ${VAR+value}et ${VAR:+value}, et ainsi de suite.


Comme Gili le souligne dans sa réponse , si vous utilisez bashl' set -o nounsetoption, la réponse de base ci-dessus échoue unbound variable. On y remédie facilement:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "${VAR-}" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Ou vous pouvez annuler l' set -o nounsetoption avec set +u( set -uéquivalent à set -o nounset).

Jonathan Leffler
la source
7
Le seul problème que j'ai avec cette réponse est qu'elle accomplit sa tâche d'une manière plutôt indirecte et peu claire.
Suisse
3
Pour ceux qui souhaitent rechercher la description de ce que cela signifie dans la page de manuel bash, recherchez la section "Expansion des paramètres" puis ce texte: "Lorsque vous n'effectuez pas de développement de sous-chaînes, en utilisant les formulaires documentés ci-dessous, bash teste pour paramètre non défini ou nul [«null» signifiant la chaîne vide]. L'omission des deux-points entraîne un test uniquement pour un paramètre non défini (...) $ {paramètre: + mot}: utilisez une autre valeur. Si paramètre est nul ou non défini, rien n'est substitué, sinon le développement du mot est remplacé. "
David Tonhofer
2
@Swiss Ce n'est ni flou ni indirect, mais idiomatique. Peut-être pour un programmeur peu familier avec ${+}et ${-}ce n'est pas clair, mais la familiarité avec ces constructions est essentielle si l'on veut être un utilisateur compétent du shell.
William Pursell
Consultez stackoverflow.com/a/20003892/14731 si vous souhaitez l'implémenter avec set -o nounsetenabled.
Gili
J'ai fait beaucoup de tests à ce sujet; car les résultats dans mes scripts étaient incohérents. Je suggère aux gens de se pencher sur l' [ -v VAR ]approche " " avec bash -s v4.2 et au - delà .
sera le
38
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO=""
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO="a"
~> if [ -z $FOO ]; then echo "EMPTY"; fi
~> 

-z fonctionne également pour les variables non définies. Pour faire la distinction entre un indéfini et un défini, vous utiliseriez les éléments listés ici ou, avec des explications plus claires, ici .

La manière la plus propre est d'utiliser l'expansion comme dans ces exemples. Pour obtenir toutes vos options, consultez la section Extension des paramètres du manuel.

Autre mot:

~$ unset FOO
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
~$ FOO=""
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
DEFINED

Valeur par défaut:

~$ FOO=""
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
~$ unset FOO
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
UNDEFINED

Bien sûr, vous utiliseriez l'un de ceux-ci différemment, en mettant la valeur souhaitée au lieu de «valeur par défaut» et en utilisant l'expansion directement, le cas échéant.

Vinko Vrsalovic
la source
1
Mais je veux distinguer si la chaîne est "" ou n'a jamais été définie. Est-ce possible?
Setjmp
1
cette réponse ne dit comment faire la distinction entre ces deux cas; suivez le lien faq bash qu'il fournit pour plus de discussion.
Charles Duffy
1
J'ai ajouté qu'après son commentaire, Charles
Vinko Vrsalovic
2
Recherchez «Expansion des paramètres» dans la page de manuel de bash pour toutes ces «astuces». Exemple: $ {foo: -default} pour utiliser une valeur par défaut, $ {foo: = default} pour attribuer la valeur par défaut, $ {foo:? Error message} pour afficher un message d'erreur si foo n'est pas défini, etc.
Jouni K . Seppänen
18

Guide de création de scripts bash avancé, 10.2. Substitution de paramètres:

  • $ {var + blahblah}: si var est défini, 'blahblah' est remplacé par l'expression, sinon nul est remplacé
  • $ {var-blahblah}: si var est défini, il est lui-même substitué, sinon 'blahblah' est remplacé
  • $ {var? blahblah}: si var est défini, il est remplacé, sinon la fonction existe avec 'blahblah' comme message d'erreur.


pour baser la logique de votre programme sur la définition ou non de la variable $ mystr, vous pouvez faire ce qui suit:

isdefined=0
${mystr+ export isdefined=1}

maintenant, si isdefined = 0 alors la variable n'était pas définie, si isdefined = 1 la variable a été définie

Cette façon de vérifier les variables est meilleure que la réponse ci-dessus car elle est plus élégante, plus lisible, et si votre shell bash a été configuré en erreur lors de l'utilisation de variables non définies (set -u), le script se terminera prématurément.


Autres trucs utiles:

pour avoir une valeur par défaut de 7 assignée à $ mystr si elle n'était pas définie, et la laisser intacte sinon:

mystr=${mystr- 7}

pour imprimer un message d'erreur et quitter la fonction si la variable n'est pas définie:

: ${mystr? not defined}

Attention ici que j'ai utilisé ':' pour ne pas faire exécuter le contenu de $ mystr en tant que commande au cas où il serait défini.


la source
Je ne savais pas pour le? syntaxe pour les variables BASH. C'est une belle prise.
suisse
Cela fonctionne également avec des références de variables indirectes. Ce passe: var= ; varname=var ; : ${!varname?not defined}ce qui se termine: varname=var ; : ${!varname?not defined}. Mais c'est une bonne habitude à utiliser set -uqui fait de même beaucoup plus facilement.
ceving le
11

Un résumé des tests.

[ -n "$var" ] && echo "var is set and not empty"
[ -z "$var" ] && echo "var is unset or empty"
[ "${var+x}" = "x" ] && echo "var is set"  # may or may not be empty
[ -n "${var+x}" ] && echo "var is set"  # may or may not be empty
[ -z "${var+x}" ] && echo "var is unset"
[ -z "${var-x}" ] && echo "var is set and empty"
k107
la source
Bonne pratique en bash. Parfois, les chemins de fichiers ont des espaces, ou pour se protéger contre l'injection de commande
k107
1
Je pense que ${var+x}ce n'est pas nécessaire, sauf si les noms de variables sont autorisés à contenir des espaces.
Anne van Rossum
Pas seulement de bonnes pratiques; il est nécessaire avec [pour obtenir des résultats corrects. Si varest non défini ou vide, [ -n $var ]réduit à [ -n ]qui a l'état de sortie 0, tandis que [ -n "$var" ]a l'état de sortie attendu (et correct) de 1.
chepner
5

https://stackoverflow.com/a/9824943/14731 contient une meilleure réponse (une qui est plus lisible et fonctionne avec set -o nounsetactivé). Cela fonctionne à peu près comme ceci:

if [ -n "${VAR-}" ]; then
    echo "VAR is set and is not empty"
elif [ "${VAR+DEFINED_BUT_EMPTY}" = "DEFINED_BUT_EMPTY" ]; then
    echo "VAR is set, but empty"
else
    echo "VAR is not set"
fi
Gili
la source
4

La manière explicite de vérifier une variable en cours de définition serait:

[ -v mystr ]
Felix Leipold
la source
1
Je ne trouve pas cela dans aucune des pages de manuel (pour bash ou test) mais cela fonctionne pour moi .. Une idée des versions ajoutées?
Ash Berlin-Taylor
1
Il est documenté dans les expressions conditionnelles , référencées à partir de l' testopérateur à la fin de la section Bourne Shell Builtins . Je ne sais pas quand il a été ajouté, mais ce n'est pas dans la version Apple de bash(basée sur bash3.2.51), donc c'est probablement une fonctionnalité 4.x.
Jonathan Leffler
1
Cela ne fonctionne pas pour moi avec GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu). L'erreur est -bash: [: -v: unary operator expected. Par un commentaire ici , il faut au minimum bash 4.2 pour fonctionner.
Acumenus
Merci d'avoir vérifié quand il est devenu disponible. Je l'avais déjà utilisé sur divers systèmes, mais soudainement cela n'a pas fonctionné. Je suis venu ici par curiosité et oui, bien sûr, la bash sur ce système est une bash 3.x.
clacke
1

une autre option: l'extension "list array index" :

$ unset foo
$ foo=
$ echo ${!foo[*]}
0
$ foo=bar
$ echo ${!foo[*]}
0
$ foo=(bar baz)
$ echo ${!foo[*]}
0 1

le seul moment où cela se développe en chaîne vide est quand foon'est pas défini, vous pouvez donc le vérifier avec la chaîne conditionnelle:

$ unset foo
$ [[ ${!foo[*]} ]]; echo $?
1
$ foo=
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=bar
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=(bar baz)
$ [[ ${!foo[*]} ]]; echo $?
0

devrait être disponible dans n'importe quelle version de bash> = 3.0

Aaron Davies
la source
1

pour ne pas jeter ce vélo encore plus loin, mais je voulais ajouter

shopt -s -o nounset

est quelque chose que vous pouvez ajouter en haut d'un script, ce qui entraînera une erreur si les variables ne sont déclarées nulle part dans le script. Le message que vous voyez est unbound variable, mais comme d'autres le mentionnent, il n'attrapera pas une chaîne vide ou une valeur nulle. Pour nous assurer qu'aucune valeur individuelle n'est vide, nous pouvons tester une variable au fur et à mesure qu'elle est développée avec ${mystr:?}, également appelée expansion du signe dollar, ce qui entraînerait une erreur avec parameter null or not set.

Sacrilicieux
la source
1

Le manuel de référence de Bash est une source d'informations faisant autorité sur bash.

Voici un exemple de test d'une variable pour voir si elle existe:

if [ -z "$PS1" ]; then
        echo This shell is not interactive
else
        echo This shell is interactive
fi

(De la section 6.3.2 .)

Notez que l'espace après l'ouverture [et avant le ]n'est pas facultatif .


Conseils pour les utilisateurs de Vim

J'avais un script qui avait plusieurs déclarations comme suit:

export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"

Mais je voulais qu'ils s'en remettent à toutes les valeurs existantes. Alors je les ai réécrits pour ressembler à ceci:

if [ -z "$VARIABLE_NAME" ]; then
        export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"
fi

J'ai pu automatiser cela dans vim en utilisant une expression régulière rapide:

s/\vexport ([A-Z_]+)\=("[^"]+")\n/if [ -z "$\1" ]; then\r  export \1=\2\rfi\r/gc

Cela peut être appliqué en sélectionnant visuellement les lignes pertinentes, puis en les tapant :. La barre de commandes pré-remplit avec :'<,'>. Collez la commande ci-dessus et appuyez sur Entrée.


Testé sur cette version de Vim:

VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Aug 22 2015 15:38:58)
Compiled by root@apple.com

Les utilisateurs Windows peuvent souhaiter des fins de ligne différentes.

funroll
la source
0

Voici ce que je pense être un moyen beaucoup plus clair de vérifier si une variable est définie:

var_defined() {
    local var_name=$1
    set | grep "^${var_name}=" 1>/dev/null
    return $?
}

Utilisez-le comme suit:

if var_defined foo; then
    echo "foo is defined"
else
    echo "foo is not defined"
fi
Suisse
la source
1
Ecrire une fonction n'est pas une mauvaise idée; invoquer grepest horrible. Notez que bashprend ${!var_name}en charge comme moyen d'obtenir la valeur d'une variable dont le nom est spécifié dans $var_name, donc name=value; value=1; echo ${name} ${!name}renvoie value 1.
Jonathan Leffler
0

Une version plus courte pour tester une variable non définie peut simplement être:

test -z ${mystr} && echo "mystr is not defined"
nfleury
la source
-3

call set sans aucun argument .. il affiche tous les vars définis ..
les derniers sur la liste seraient ceux définis dans votre script ..
ainsi vous pourriez diriger sa sortie vers quelque chose qui pourrait comprendre ce qui est défini et ce qui ne l'est pas

Aditya Mukherji
la source
2
Je n'ai pas voté contre cela, mais c'est un peu un marteau pour casser une petite noix.
Jonathan Leffler
En fait, j'aime mieux cette réponse que la réponse acceptée. Ce qui est fait dans cette réponse est clair. La réponse acceptée est bancale comme l'enfer.
suisse
Il semble que cette réponse soit plus un effet secondaire de l'implémentation de "set" que quelque chose de souhaitable.
Jonathan Morgan