set -e et condition Le sujet est résolu

Tout ce qui concerne la programmation.
Répondre
Avatar du membre
Dunatotatos
Membre
Membre
Messages : 411
Enregistré le : 11 mai 2016, 20:56
Localisation : Arabie Saoudite
Status : Hors ligne

13 sept. 2017, 08:42

Salut à tous,

Je souhaite que les erreurs rencontrées lors de l'exécution d'un script sh soient propagées. `set -e` est parfait pour ça, sauf dans certains cas : lorsque les commandes sont dans une condition. Un extrait de man bash:

Code : Tout sélectionner

              -e      Exit  immediately  if a pipeline (which may consist of a single simple command), a list, or a compound command (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 !.  If a compound command other than a subshell returns
                      a non-zero status because a command failed while -e was being ignored, the shell does not exit.  A trap on ERR,  if  set,
                      is  executed  before  the  shell exits.  This option applies to the shell environment and each subshell environment sepa‐
                      rately (see COMMAND EXECUTION ENVIRONMENT above), and may cause subshells to exit before executing all  the  commands  in
                      the subshell.


Et un MVE pour comprendre le souci :

Code : Tout sélectionner

#!/bin/sh

set -e

(
    false
    echo "passed"
) || echo "failed"

Ce script renvoie "passed", alors qu'on pourrait s'attendre à voir "failed". Comment modifier ce script pour obtenir le comportement attendu ? Ajouter un `set -e` à l'intérieur du sub-shell suit le même comportement :

Code : Tout sélectionner

#!/bin/sh
set -e
(
    set -e
    false
    echo "passed"
) || echo "failed"
EDIT : J'ai posé la question sur SO, mais pas grand chose n'en est sorti : https://stackoverflow.com/questions/461 ... -inside-or
Never trust Windows output.
MicP
Modérateur
Modérateur
Messages : 551
Enregistré le : 16 avr. 2016, 22:14
Status : Hors ligne

13 sept. 2017, 12:21

…The shell does not exit if the command that fails is part of …
… any command executed in a && or || list except the command following the final && or ||
Avatar du membre
Dunatotatos
Membre
Membre
Messages : 411
Enregistré le : 11 mai 2016, 20:56
Localisation : Arabie Saoudite
Status : Hors ligne

13 sept. 2017, 12:46

Comme indiqué sur SO (et dans mon message ici), j'ai bien compris pourquoi. Mais comment produire le résultat voulu ?
Never trust Windows output.
MicP
Modérateur
Modérateur
Messages : 551
Enregistré le : 16 avr. 2016, 22:14
Status : Hors ligne

13 sept. 2017, 14:50

Code : Tout sélectionner

#!/bin/sh
set -e
{
    ls "non-existing-dir"
    printf "End of block\n"
} || {
    printf "It is broken\n"
}

Le code d'erreur retourné par le bloc
est celui de la dernière commande exécutée dans le bloc :

Code : Tout sélectionner

    printf "End of block\n"
Qui s'exécute sans erreur

=======
Mais quelqu'un viens juste de donner une réponse sur Stack Overflow
Avatar du membre
Dunatotatos
Membre
Membre
Messages : 411
Enregistré le : 11 mai 2016, 20:56
Localisation : Arabie Saoudite
Status : Hors ligne

13 sept. 2017, 14:58

Ça, j'ai bien compris. Je ne cherche plus à comprendre le problème (puisque je l'ai compris), mais à trouver un moyen d'écrire mon code proprement pour obtenir le comportement initialement prévu.

Le comportement que je recherche, c'est :
  • lorsqu'une erreur survient dans le bloc avant ||, arrêt de l'exécution de ce bloc juste après l'erreur, puis exécution du bloc après ||
  • si aucune erreur ne survient, exécution complète du bloc avant ||, puis poursuite du script sans exécution du bloc après ||
J'ai pour le moment résolu ce problème à base de trap :

Code : Tout sélectionner

#!/bin/sh
abort () {
    printf "It is broken\n"
}
trap 'abort' ERR
(
    set -e
    false
    printf "End of block\n"
)
trap ERR
Ce n'est pas du grand art, mais ça reste à peu près propre, et je crois limiter les effets de bord.
Never trust Windows output.
Répondre