web-dev-qa-db-fra.com

Que fait «set -e» et pourquoi pourrait-il être considéré comme dangereux?

Cette question est apparue sur un quiz pré-interview et ça me rend fou. Quelqu'un peut-il répondre à cela et me mettre à l'aise? Le quiz n'a aucune référence à un Shell particulier mais la description du poste est pour un sa unix. encore une fois, la question est simplement ...

Que fait "set -e" et pourquoi pourrait-il être considéré comme dangereux?

148
egorgry

set -e provoque la fermeture du shell si une sous-commande ou un pipeline renvoie un état différent de zéro.

La réponse que l'intervieweur cherchait probablement est:

Il serait dangereux d'utiliser "set -e" lors de la création de scripts init.d:

De http://www.debian.org/doc/debian-policy/ch-opersys.html 9.3.2 -

Faites attention à utiliser set -e dans les scripts init.d. L'écriture de scripts init.d corrects nécessite l'acceptation de divers états de sortie d'erreur lorsque les démons sont déjà en cours d'exécution ou déjà arrêtés sans abandonner le script init.d, et les bibliothèques de fonctions init.d courantes ne sont pas sûres d'appeler avec set -e en vigueur. Pour les scripts init.d, il est souvent plus facile de ne pas utiliser set -e et de vérifier à la place le résultat de chaque commande séparément.

Il s'agit d'une question valable du point de vue de l'intervieweur, car elle évalue les connaissances pratiques des candidats en matière de script et d'automatisation au niveau du serveur.

156
Rich

De bash(1) :

          -e      Exit immediately if a pipeline (which may consist  of  a
                  single  simple command),  a subshell command enclosed in
                  parentheses, or one of the commands executed as part  of
                  a  command  list  enclosed  by braces (see Shell GRAMMAR
                  above) exits with a non-zero status.  The Shell does not
                  exit  if  the  command that fails is part of the command
                  list immediately following a  while  or  until  keyword,
                  part  of  the  test  following  the  if or Elif reserved
                  words, part of any command executed in a && or  ││  list
                  except  the  command  following  the final && or ││, any
                  command in a pipeline but the last, or if the  command’s
                  return  value  is being inverted with !.  A trap on ERR,
                  if set, is executed before the Shell exits.  This option
                  applies to the Shell environment and each subshell envi-
                  ronment separately (see  COMMAND  EXECUTION  ENVIRONMENT
                  above), and may cause subshells to exit before executing
                  all the commands in the subshell.

Malheureusement, je ne suis pas assez créatif pour penser à pourquoi ce serait dangereux, à part "le reste du script ne sera pas exécuté" ou "cela pourrait peut-être masquer de vrais problèmes".

34

Il convient de noter que set -e peut être activé et désactivé pour différentes sections d'un script. Il ne doit pas être activé pour l'exécution du script entier. Il pourrait même être activé sous condition. Cela dit, je ne l'utilise jamais car je fais ma propre gestion des erreurs (ou non).

some code
set -e     # same as set -o errexit
more code  # exit on non-zero status
set +e     # same as set +o errexit
more code  # no exit on non-zero status

À noter également dans la section de la page de manuel Bash de la commande trap qui décrit également comment set -e fonctionne dans certaines circonstances.

L'interruption ERR n'est pas exécutée si la commande échouée fait partie de la liste de commandes immédiatement après un mot clé while ou until, une partie du test dans une instruction if, une partie d'une commande exécutée dans une liste && ou ⎪⎪, ou si la commande est la valeur de retour est inversée via!. Ce sont les mêmes conditions auxquelles obéit l'option errexit.

Il existe donc certaines conditions dans lesquelles un état différent de zéro ne provoquera pas de sortie.

Je pense que le danger est de ne pas comprendre quand set -e entre en jeu et quand il ne le fait pas et en s'appuyant incorrectement sur lui sous une hypothèse invalide.

Veuillez aussi voir BashFAQ/105 Pourquoi set -e (ou set -o errexit, ou trap ERR) ne fait-il pas ce que j'attendais?

Gardez à l'esprit qu'il s'agit d'un quiz pour un entretien d'embauche. Les questions peuvent avoir été rédigées par le personnel actuel et elles peuvent être erronées. Ce n'est pas nécessairement mauvais, et tout le monde fait des erreurs, et les questions d'entrevue se trouvent souvent dans un coin sombre sans examen, et ne sortent que lors d'une entrevue.

Il est tout à fait possible que "set -e" ne fasse rien que nous considérions comme "dangereux". Mais l'auteur de cette question peut croire à tort que "set -e" est dangereux, en raison de sa propre ignorance ou de ses propres préjugés. Peut-être qu'ils ont écrit un script Shell buggé, qu'il a bombardé horriblement, et ils ont pensé à tort que 'set -e' était à blâmer, alors qu'en fait ils ont négligé d'écrire la vérification des erreurs appropriée.

J'ai participé à probablement 40 entretiens d'embauche au cours des 2 dernières années, et les enquêteurs posent parfois des questions incorrectes ou ont des réponses erronées.

Ou peut-être que c'est une question piège, qui serait boiteuse, mais pas entièrement inattendue.

Ou peut-être que c'est une bonne explication: http://www.mail-archive.com/[email protected]/msg473314.html

13
Stefan Lasiewski

set -e indique à bash, dans un script, de quitter chaque fois que quelque chose retourne une valeur de retour non nulle.

je pouvais voir comment cela serait ennuyeux et buggé, pas sûr du danger, à moins que vous n'ayez ouvert des autorisations sur quelque chose, et avant de pouvoir les restreindre à nouveau, votre script est mort.

9
cpbills

set -e Termine le script si un code de sortie différent de zéro est rencontré, sauf sous certaines conditions. Pour résumer les dangers de son utilisation en quelques mots: il ne se comporte pas comme les gens le pensent.

À mon avis, il doit être considéré comme un hack dangereux qui continue d'exister à des fins de compatibilité uniquement. L'instruction set -e Ne transforme pas Shell d'un langage qui utilise des codes d'erreur en un langage qui utilise un flux de contrôle de type exception, il tente simplement mal d'émuler ce comportement.

Greg Wooledge a beaucoup à dire sur les dangers de set -e:

Dans le deuxième lien, il existe divers exemples du comportement non intuitif et imprévisible de set -e.

Quelques exemples du comportement peu intuitif de set -e (Certains tirés du lien wiki ci-dessus):

  • set -e 
     x = 0 
     let x ++ 
     echo "x is $ x"
    Ce qui précède entraînera la fermeture prématurée du script Shell, car let x++ Renvoie 0, qui est traité par le mot clé let comme une valeur falsifiée et transformé en un code de sortie différent de zéro. set -e Le remarque et termine silencieusement le script.
  •  set -e 
     [-d/opt/foo] && echo "Avertissement: foo est déjà installé. Remplacera." > & 2 
     Echo "Installation de foo ..." 
    

    Ce qui précède fonctionne comme prévu, affichant un avertissement si /opt/foo Existe déjà.

     set -e 
     check_previous_install () {
     [-d/opt/foo] && echo "Avertissement: foo est déjà installé. Il écrasera." > & 2 
    } 
     
     Check_previous_install 
     Echo "Installation de foo ..." 
    

    Ce qui précède, malgré la seule différence étant qu'une seule ligne a été refactorisée en fonction, se terminera si /opt/foo N'existe pas. En effet, le fait qu'il ait fonctionné à l'origine est une exception spéciale au comportement de set -e. Lorsque a && b Renvoie une valeur non nulle, il est ignoré par set -e. Cependant, maintenant qu'il s'agit d'une fonction, le code de sortie de la fonction est égal au code de sortie de cette commande, et la fonction renvoyant une valeur non nulle terminera silencieusement le script.

  •  set -e 
     IFS = $ '\ n' read -d '' -r -a config_vars <config 
    

    Ce qui précède lira le tableau config_vars Du fichier config. Comme l'auteur pourrait le vouloir, il se termine par une erreur si config est manquant. Comme l'auteur pourrait ne pas vouloir, il se termine silencieusement si config ne se termine pas par une nouvelle ligne. Si set -e N'était pas utilisé ici, alors config_vars Contiendrait toutes les lignes du fichier, qu'il se termine ou non par une nouvelle ligne.

    Utilisateurs de Sublime Text (et d'autres éditeurs de texte qui ne gèrent pas correctement les sauts de ligne), méfiez-vous.

  •  set -e 
     
     should_audit_user () {
     local group groups = "$ (groups" $ 1 ")" 
     pour le groupe dans $ groups ; faire 
     si ["$ group" = audit]; puis retournez 0; fi 
     done 
     return 1 
    } 
     
     if should_audit_user "$ user"; puis 
     enregistreur 'Blah' 
     fi 
    

    L'auteur peut raisonnablement s'attendre à ce que si, pour une raison quelconque, l'utilisateur $user N'existe pas, la commande groups échouera et le script se terminera au lieu de laisser l'utilisateur effectuer une tâche non vérifiée. Cependant, dans ce cas, la terminaison set -e Ne prend jamais effet. Si $user Est introuvable pour une raison quelconque, au lieu de terminer le script, la fonction should_audit_user Renverra simplement des données incorrectes comme si set -e N'était pas en vigueur.

    Cela s'applique à toute fonction invoquée à partir de la partie condition d'une instruction if, quelle que soit sa profondeur d'imbrication, peu importe où elle est définie, même si vous exécutez à nouveau set -e À l'intérieur. L'utilisation de if à tout moment désactive complètement l'effet de set -e Jusqu'à ce que le bloc de condition soit entièrement exécuté. Si l'auteur n'est pas au courant de cet écueil ou ne connaît pas l'intégralité de sa pile d'appels dans toutes les situations possibles dans lesquelles une fonction peut être appelée, il écrira du code bogué et le faux sentiment de sécurité fourni par set -e sera au moins partiellement à blâmer.

    Même si l'auteur est pleinement conscient de cet écueil, la solution consiste à écrire du code de la même manière que l'on l'écrirait sans set -e, Ce qui rendrait ce commutateur moins qu'inutile; non seulement l'auteur doit écrire du code de gestion d'erreur manuelle comme si set -e n'était pas en vigueur, mais la présence de set -e peut les avoir fait croire qu'il n'était pas obligé de le faire.

Quelques autres inconvénients de set -e:

  • Il encourage le code bâclé. Les gestionnaires d'erreurs sont complètement oubliés, dans l'espoir que tout échec signalera l'erreur de manière raisonnable. Cependant, avec des exemples comme let x++ Ci-dessus, ce n'est pas le cas. Si le script meurt de façon inattendue, il est généralement silencieux, ce qui empêche le débogage. Si le script ne meurt pas et que vous vous y attendiez (voir puce précédente), vous avez un bug plus subtil et insidieux entre vos mains.
  • Cela conduit les gens à un faux sentiment de sécurité. Voir à nouveau la puce if- condition.
  • Les endroits où le shell se termine ne sont pas cohérents entre les shells ou les versions du shell. Il est possible d'écrire accidentellement un script qui se comporte différemment sur une ancienne version de bash en raison du comportement de set -e Ayant été modifié entre ces versions.

set -e Est un problème litigieux, et certaines personnes conscientes des problèmes qui l'entourent le déconseillent, tandis que d'autres recommandent simplement de faire attention pendant qu'il est actif pour connaître les pièges. Il existe de nombreux débutants de scripts Shell qui recommandent set -e Sur tous les scripts comme fourre-tout pour les conditions d'erreur, mais dans la vraie vie, cela ne fonctionne pas de cette façon.

set -e Ne remplace pas l'éducation.

8
Score_Under

Je dirais que c'est dangereux parce que vous ne contrôlez plus le flux de votre script. Le script peut se terminer tant que l'une des commandes qu'il appelle renvoie un non différent de zéro. Donc, tout ce que vous avez à faire est de faire quelque chose qui modifie le comportement ou la sortie de l'un des composants, et vous arrêtez le script principal. Cela pourrait être plus un problème de style, mais cela a certainement des conséquences. Et si votre script principal était censé mettre un drapeau, et il ne l'a pas fait parce qu'il s'est terminé tôt? Vous finiriez par mettre en défaut le reste du système s'il suppose que l'indicateur devrait être là, ou travailler avec une valeur par défaut ou ancienne inattendue.

2
Marcin

Comme exemple plus concret de la réponse de @ Marcin qui m'a personnellement mordue, imaginez qu'il y avait un rm foo/bar.txt ligne quelque part dans votre script. Habituellement, ce n'est pas grave si foo/bar.txt n'existe pas réellement. Cependant avec set -e présent maintenant votre script se terminera tôt là-bas! Oops.

0
Suan