Pourquoi vérifier l'existence d'un fichier avant de le rechercher?

13

Lors de la tentative de source d'un fichier, ne voudriez-vous pas une erreur indiquant que le fichier n'existe pas afin que vous sachiez quoi corriger?

Par exemple, nvm recommande d'ajouter ceci à votre profil / rc:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

Avec ci-dessus, s'il nvm.shn'existe pas, vous obtiendrez une "erreur silencieuse". Mais si vous essayez . "$NVM_DIR/nvm.sh", la sortie sera FILE_PATH: No such file or directory.

JBallin
la source
3
Tu ne devrais pas. C'est racé. Essayez la source puis gérez l'erreur, le cas échéant
Mikel
2
@Mikel à droite! alors pourquoi est-ce que je le vois partout?
JBallin
3
@FaheemMitha, eh bien, si ce que vous faites est sensible à la sécurité (c'est-à-dire que votre programme fonctionne pour le compte de quelqu'un d'autre), alors vous devez vraiment vous soucier des conditions de course ( TOCTOU ). Ce n'est probablement pas le cas ici, car vous avez de plus gros problèmes si quelqu'un arrive à modifier des fichiers HOME.
ilkkachu
8
@FaheemMitha Il n'est jamais préférable de vérifier d'abord l'existence. La situation peut changer entre le test et l'utilisation, produisant à la fois des faux positifs et des faux négatifs: et vous devez quand même gérer l'échec lors de l'utilisation. Et comme vous l'avez mentionné, les tests avant utilisation sont deux fois plus lents que de ne pas le faire et de laisser le système le faire, ce qu'il fera de toute façon. Ça ne peut pas ne pas l'être.
user207421
1
@SimonRichter, dans ce cas, nvm ne fonctionnera pas. L'utilisateur devrait comprendre que le fichier est lui-même manquant.
JBallin

Réponses:

25

Dans les shells POSIX, .est une fonction intégrée spéciale, donc son échec provoque la fermeture du shell (dans certains shells comme bash, cela n'est fait qu'en mode POSIX).

Ce qui constitue une erreur dépend du shell. Tous ne se terminent pas sur une erreur de syntaxe lors de l'analyse du fichier, mais la plupart se fermeraient lorsque le fichier source ne peut pas être trouvé ou ouvert. Je n'en connais aucun qui se terminerait si la dernière commande du fichier source renvoyait avec un état de sortie différent de zéro (sauf si l' errexitoption est bien sûr activée).

Voici faire:

[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

Est un cas où vous souhaitez source le fichier s'il est là, et pas si ce n'est pas le cas (ou est vide ici avec -s).

Autrement dit, il ne doit pas être considéré comme une erreur (erreur fatale dans les shells POSIX) si le fichier n'est pas là, ce fichier est considéré comme un fichier facultatif.

Ce serait toujours une erreur (fatale) si le fichier n'était pas lisible ou était un répertoire ou (dans certains shells) s'il y avait une erreur de syntaxe lors de son analyse, ce qui serait de vraies conditions d'erreur qui devraient être signalées.

Certains diront qu'il y a une condition de concurrence. Mais la seule chose que cela signifie serait que le shell se termine avec une erreur si le fichier est supprimé entre le [et ., mais je dirais qu'il est valide de considérer comme une erreur que ce fichier de chemin fixe disparaîtrait soudainement pendant que le script est fonctionnement.

D'autre part,

command . "$NVM_DIR/nvm.sh" 2> /dev/null

command¹ supprime l' attribut spécial de la .commande (pour ne pas quitter le shell en cas d'erreur) ne fonctionnerait pas comme:

  • il cacherait .les erreurs mais aussi les erreurs des commandes exécutées dans le fichier source
  • cela masquerait également de vraies conditions d'erreur comme le fichier ayant les mauvaises autorisations.

D'autres syntaxes courantes (voir par exemple grep -r /etc/default /etc/init*sur les systèmes Debian pour les scripts d'initialisation qui n'ont pas encore été convertis systemd(où EnvironmentFile=-/etc/default/serviceest utilisé à la place pour spécifier un fichier d'environnement facultatif)) incluent:

  • [ -e "$file" ] && . "$file"

    Vérifiez le fichier qu'il est là, toujours source s'il est vide. Erreur toujours fatale si elle ne peut pas être ouverte (même si elle est là ou était là). Vous pouvez voir plus de variantes comme [ -f "$file" ](existe et est un fichier normal ), [ -r "$file" ](est lisible), ou des combinaisons de ceux-ci.

  • [ ! -e "$file" ] || . "$file"

    Une version légèrement meilleure. Il est plus clair que le fichier inexistant est un cas OK. Cela signifie également que $?reflétera l'état de sortie de la dernière commande exécutée $file(dans le cas précédent, si vous obtenez 1, vous ne savez pas si c'est parce qu'il $filen'existait pas ou si cette commande a échoué).

  • command . "$file"

    Attendez-vous à ce que le fichier soit là, mais ne quittez pas s'il ne peut pas être interprété.

  • [ ! -e "$file" ] || command . "$file"

    Combinaison de ce qui précède: c'est OK si le fichier n'est pas là, et pour les shells POSIX, les échecs d'ouverture (ou d'analyse) du fichier sont signalés mais ne sont pas fatals (ce qui peut être plus souhaitable pour ~/.profile).


¹ Remarque: Dans, zshcependant, vous ne pouvez pas utiliser commandcomme ça, sauf en shémulation; noter que dans le shell Korn, sourceest en fait un alias pour command ., une variante non spéciale de.

Stéphane Chazelas
la source
Intéressant! Je ne savais pas ça sur POSIX sh. Mais la question était sur le point .bash_profile. Je suppose qu'il vaut mieux prévenir que guérir, mais bash est-il toujours en mode POSIX quand .bash_profileest-il sourced?
Mikel
(Je réalise que vous pourriez interpréter cette question comme s'appliquant plus largement à tous les shells POSIX en fonction de la lecture du lien source nvm dans la question.)
Mikel
@Mikel, ma réponse s'applique toujours en dehors du bashmode POSIX. Vous voudriez [ -e /file ] && . /filesi vous ne le considérez pas comme une erreur lorsque le fichier n'existe pas. La source d'essai gère ensuite l'erreur, si aucune ne peut être effectuée ici.
Stéphane Chazelas
1
@Mikel, c'est contre-productif. 1) qui n'empêche pas la sortie en cas d'erreur avec des shells POSIX (ou bash en mode POSIX) 2), qui duplique le message d'erreur (le vôtre sur stdout), .signalera déjà une erreur (sur stderr). Et si l'intention est de ne pas le considérer comme une erreur lorsque le fichier n'existe pas, ce n'est pas correct (et il n'est pas possible, à partir de l'état de sortie, de dire s'il a .échoué parce que le fichier n'existait pas ou n'était pas lisible ou était pas analysable, ou la dernière commande a échoué) qui sont les points que je fais ici dans cette réponse.
Stéphane Chazelas
1
En ce qui concerne les conditions de concurrence - c'est beaucoup moins un problème à mon humble avis pour l'échec de la connexion une fois (alors que quelque chose de bizarre se produit sur le système) que pour l'échec de la connexion de manière cohérente (même s'il y a quelque chose de pas tout à fait correct dans la configuration de l'utilisateur -des dossiers). Donc, une condition de concurrence dans ce contrôle est toujours une amélioration par rapport à ne pas avoir le contrôle.
ruakh
5

Maintien de nvmla réponse de:

il est facile de désinstaller nvm en supprimant simplement le fichier; Forcer du travail supplémentaire (pour retrouver la ou les lignes de cette source nvm) ne semble pas particulièrement utile.

Mon interprétation (combinée à l'excellente explication de Stéphane et au commentaire de Kusalananda):

C'est plus simple et plus sûr.

Il se défend contre les shells POSIX se fermant au démarrage en raison d'un fichier manquant (pour diverses raisons). Ceux qui utilisent des shells non POSIX (par exemple bash) peuvent supprimer le conditionnel s'ils préfèrent.

JBallin
la source
1
Je m'oppose à votre interprétation que c'est "destiné aux débutants". C'est défensif. Vous ne voulez pas que le shell de connexion d'un utilisateur se termine de manière inattendue au démarrage simplement parce que ce fichier a disparu (pour une raison quelconque). Si cette ligne de code se trouve dans l'un des fichiers d'initialisation du shell sous /etc, cela permet à certains utilisateurs d'avoir le fichier, et d'autres non. À mon humble avis, la nvmréponse du responsable ne touche qu'à un seul aspect.
Kusalananda
1

Comme l' ont souligné JBallin et Stéphane Chazelas , dans les shells POSIX, le sourcing d'un fichier qui n'existe pas entraînerait l'échec de la connexion.

Mais l'ajout d'un test pour voir si le fichier existe et ensuite essayer de le source peut provoquer quelque chose appelé une condition de concurrence critique. Si quelque chose change nvm.shentre le [ -s nvm.sh ]et le . nvm.sh, cela causera exactement le bogue qu'ils essaient d'empêcher, quoique beaucoup plus rarement.

En général, le moyen d'empêcher les conditions de concurrence est simplement d'essayer la chose que vous voulez faire, puis de gérer l'erreur en cas d'échec, par exemple

. "$NVM_DIR/nvm.sh" || echo "Sourcing $NVM_DIR/nvm.sh failed" >&2

Il s'avère que cela ne fonctionne pas dans les shells POSIX, car, comme ci-dessus, l' .échec entraînera la fermeture immédiate du shell, avant que la gestion des erreurs puisse s'exécuter.

Ma réponse soutient que les shells POSIX ne sont pas pertinents pour cette question, car .bash_profileils ne devraient jamais fonctionner en mode POSIX. Nous pouvons donc simplement faire le code ci-dessus de toute façon.

Pour être plus sûr, nous pourrions nous assurer que le mode POSIX n'est pas en vigueur, ou que le mode POSIX est désactivé en utilisant la technique décrite dans /unix//a/383581/3169 .

La réponse de Stéphane contient quelques suggestions utiles sur la façon de gérer tous les shells POSIX, ce qui, je pense, était l'intention de l'auteur du nvm, mais était subtilement différent de ce que la question ici demandait, c'est pourquoi nous avons plusieurs approches possibles, en fonction de votre objectif. .

Mikel
la source