Pourquoi vérifier si un dossier existe en utilisant -d sur une chaîne vide renvoie true?

8

J'écrivais des scripts et écrivais quelque chose comme

ARTIFACTS="/SOME/PATH"
[ -d $ARTIFCATS ] && rm -rf $ARTIFACTS/*

Ce qui s'est passé, c'est que par stupidité j'ai exécuté la deuxième ligne sans exécuter la première. Il s'est avéré que [-d ""] renvoie vrai et l'expression est devenue

rm -rf /*

Heureusement, ce n'était qu'une machine de test et je n'étais pas un sudo, mais même si j'ai perdu des données

Ma question est, pourquoi [-d ""] retourne vrai ?? la documentation indique clairement qu'il vérifie si un chemin existe et s'il s'agit d'un dossier

J'ai résolu le problème en utilisant

[ -e $ARTIFACTS ]
qui semble fonctionner

À votre santé

Moataz Elmasry
la source
5
Ou peut-être que vous avez exécuté les deux lignes. Dans l'exemple de code ci-dessus, vous ne définissez jamais ARTIFCATS.
Buhb
2
Je voudrais simplement écrire cela comme rm -rf $ARTIFACTSsans /*. Cela supprimerait également le $ARTIFACTSrépertoire, ce qui est bien, car si je veux être sûr qu'il existe avant d'y mettre quelque chose, je l'exécuterai mkdir -p $ARTIFACTSquand même. Il supprimera également les fichiers cachés à l'intérieur $ARTIFACTS, ce qui est également bien, car je n'écrirais pas rm -rf $ARTIFACTS/*s'il $ARTIFACTScontenait quelque chose que je voulais enregistrer.
Christoffer Hammarström
@ ChristofferHammarström très vrai
Moataz Elmasry

Réponses:

9

1. Ces deux tests retournent vrai :

# [ -d ] && echo true || echo false
true
# [ -d $SOME_UNSET_VAR ] && echo true || echo false
true

selon POSIX (comme expliqué par @Tim).

2. Mais cela retourne faux ( pas vrai comme indiqué dans la question)

# [ -d "" ] && echo true || echo false
false

car testest appelé avec deux arguments (bien que le second soit une chaîne vide).

3. C'est pourquoi il est recommandé d'utiliser à la [[ … ]]place de test( [ … ]), que la plupart (tous?) Des shells actuels fournissent. Cette construction vérifie si vous fournissez suffisamment d'arguments (sinon lance une erreur et abandonne)

# [[ -d ]] && echo true || echo false
bash: unexpected argument `]]' to conditional unary operator
bash: syntax error near `]]'

ou se comporte simplement comme on pourrait s'y attendre:

# [[ -d $SOME_UNSET_VAR ]] && echo true || echo false
false

4. Et, comme l'a souligné @Gilles, il est encore plus important de doubler les substitutions de guillemets. Ainsi , se -d "$SOME_UNSET_VAR"développe pour -d ""et retourne faux même test(égal au cas 2). C'est donc également compatible avec le shell Bourne sh:

# [ -d "$SOME_UNSET_VAR" ] && echo true || echo false
false

testé avec bash 3.00.16 (1) et 4.1.5 (1)

mpy
la source
1
Bash, ksh et zsh fournissent [[ … ]]mais pas simple sh. La pratique vraiment important est de mettre des guillemets doubles autour de substitutions de commandes : [ -d "$ARTIFACTS" ].
Gilles 'SO- arrête d'être méchant'
6

Cette question a déjà été répondue sur StackOverflow. Il indique que selon la norme POSIX, testdevrait toujours retourner avec succès s'il est appelé avec exactement un argument non vide (et aucun autre argument).

Cela devrait également être le cas avec test -e(et en fait c'est sur mon système), alors soyez prudent.

Utilisez plutôt:

[ -d "$ARTIFACTS" ]

test sera alors appelé avec deux arguments même si la variable est vide et retourne false dans ce cas.

Tim
la source
"exactement un argument non vide." - ceci est un résumé trompeur - la page que vous avez liée mentionne être appelée avec exactement un argument et cet argument étant non vide; rien sur le cas d'un argument non vide suivi d'un argument vide. La bonne solution devrait être d'entourer le nom de la variable entre guillemets.
Random832
@ Random832 Merci! J'ai implémenté les deux changements suggérés.
Tim
[ ! -z $ARTIFACTS ] && [ -d $ARTIFACTS ]n'est pas mieux: il ne s'adresse qu'au cas particulier lorsqu'il $ARTIFACTSest vide, mais échoue lorsqu'il $ARTIFACTSpeut contenir des espaces ou \[?*. [ -d "$ARTIFACTS" ]est la bonne façon de le faire (ou [[ -d $ARTIFACTS ]]dans des coquilles qui ont [[ … ]]).
Gilles 'SO- arrête d'être méchant'
@Gilles Vous avez raison, je l'ai supprimé. Merci!
Tim
4

J'ai résolu le problème en utilisant [-e $ ARTIFACTS] qui semble fonctionner

Vous vous trompez . Cela fonctionne parce que$ARTIFACTS est maintenant réglé sur quelque chose.

Lorsqu'une variable n'est pas définie, dire

[ -d $SOMEVAR ]

ou

[ -e $SOMEVAR ]

serait à la fois évaluer truecar cela implique de dire

[ -d ]

et

[ -e ]

respectivement. (En disant[ foobar ] serait toujours vrai.)

En disant

set -u

est utile dans de telles situations. help setvous dirait:

  -u  Treat unset variables as an error when substituting.
devnull
la source
[-e ""] && echo "IMPRIMER QUELQUE CHOSE" n'imprime rien, alors comment cela peut-il être faux?
Moataz Elmasry
2
ahh merde [-e $ ARTIFACTS] avec des artefacts vides donne [-e] pas [-e ""], compris
Moataz Elmasry
4

Vérifiez que vous définissez la variable ARTIFACTS et que vous recherchez ARTIFCATS. Probablement une faute de frappe?

Quoi qu'il en soit, -d ainsi que -e produiraient les mêmes résultats sur les variables non définies.

Par conséquent, utilisez des guillemets doubles et cela vous aidera.

ARTIFACTS="/SOME/PATH"
[ -d "$ARTIFACTS" ] && rm -rf -- "$ARTIFACTS/"*

REMARQUE: si votre "/ SOME / PATH" contient un dossier avec de l'espace, le script que vous avez mentionné rompra avec l'erreur "opérateur binaire attendu".

Exemple:

ARTIFACTS="/home backup/"

1) [ -d $ARTIFACTS ] && rm -rf $ARTIFACTS/*
bash: [: /home: binary operator expected

2) [ -d "$ARTIFACTS" ] && rm -rf "$ARTIFACTS"/*

fera bien. N'oubliez pas de mettre également des guillemets dans l' rminvocation ( rm -rf $ARTIFACTSsupprimer joyeusement /homepuis se plaindre de backup/*ne pas exister).

En outre, y compris -Lcheck vous assurera qu'il s'agit d'un répertoire et pas seulement d'un lien symbolique vers un répertoire.

Donc en gros,

[ -d "$ARTIFACTS" && ! -L "$ARTIFACTS" ] && rm -rf -- "$ARTIFACTS"/*
thiruvenkadam
la source