Le langage exact utilisé dans la spécification Single UNIX pour décrire la signification deset -e
est:
Lorsque cette option est activée, si une commande simple échoue pour l'une des raisons répertoriées dans Conséquences des erreurs de shell ou renvoie une valeur de statut de sortie> 0, et n'est pas une [commande conditionnelle ou inversée], le shell doit immédiatement quitter.
Il y a une ambiguïté sur ce qui se passe lorsqu'une telle commande est exécutée dans un sous - shell . D'un point de vue pratique, tout ce que le sous-shell peut faire est de quitter et de renvoyer un statut différent de zéro au shell parent. La sortie du shell parent dépend de la transformation de cet état non nul en une simple commande échouant dans le shell parent.
Un de ces problèmes est celui que vous avez rencontré: un état de retour différent de zéro à partir d'une substitution de commande . Étant donné que cet état est ignoré, le shell parent ne se ferme pas. Comme vous l'avez déjà découvert , un moyen de prendre en compte le statut de sortie consiste à utiliser la substitution de commande dans une affectation simple : le statut de sortie de l'affectation est alors le statut de sortie de la dernière substitution de commande dans la ou les assignations .
Notez que cela fonctionnera comme prévu uniquement s'il y a une substitution de commande unique, car seul le statut de la dernière substitution est pris en compte. Par exemple, la commande suivante réussit (à la fois selon le standard et dans chaque implémentation que j'ai vue):
a=$(false)$(echo foo)
Un autre cas à surveiller est sous - couches explicites : (somecommand)
. Selon l'interprétation ci-dessus, le sous-shell peut renvoyer un statut différent de zéro, mais comme il ne s'agit pas d'une simple commande dans le shell parent, le shell parent doit continuer. En fait, tous les obus que je connais font revenir le parent à ce stade. Bien que cela soit utile dans de nombreux cas, par exemple (cd /some/dir && somecommand)
lorsque les parenthèses sont utilisées pour conserver une opération telle qu'un changement d'annuaire en cours, il enfreint la spécification si elle set -e
est désactivée dans le sous-shell ou si le sous-shell renvoie un statut différent de zéro de manière à ce que ne le terminerait pas, par exemple en utilisant !
une vraie commande. Par exemple, ash, bash, pdksh, ksh93 et zsh se ferment sans afficher foo
les exemples suivants:
set -e; (set +e; false); echo "This should be displayed"
set -e; (! true); echo "This should be displayed"
Pourtant, aucune commande simple n'a échoué tant qu'elle set -e
était en vigueur!
Un troisième cas problématique concerne les éléments d'un pipeline non trivial . En pratique, tous les shells ignorent les défaillances des éléments du pipeline autres que le dernier et présentent l'un des deux comportements suivants en ce qui concerne le dernier élément de pipeline:
- ATT ksh et zsh, qui exécutent le dernier élément du pipeline dans le shell parent, fonctionnent comme d'habitude: si une commande simple échoue dans le dernier élément du pipeline, le shell qui l'exécute, qui se trouve être le shell parent, sorties.
- D'autres shells approchent le comportement en quittant si le dernier élément du pipeline renvoie un statut différent de zéro.
Comme auparavant, le fait de désactiver set -e
ou d'utiliser une négation dans le dernier élément du pipeline entraîne le renvoi d'un statut différent de zéro de manière à ne pas terminer le shell; les obus autres que ATT ksh et zsh vont alors sortir.
L' pipefail
option de Bash entraîne la fermeture immédiate d'un pipeline set -e
si l'un de ses éléments renvoie un statut différent de zéro.
Notez que, complication supplémentaire, bash est désactivé set -e
dans les sous-shell, sauf s’il est en mode POSIX ( set -o posix
ou POSIXLY_CORRECT
dans l’environnement au démarrage de bash).
Tout cela montre que la spécification POSIX fait malheureusement un travail médiocre en spécifiant l' -e
option. Heureusement, les obus existants sont généralement cohérents dans leur comportement.
set -e; (cd /nonexisting)
.(Répondre à la mienne parce que j'ai trouvé une solution) Une solution consiste à toujours l'affecter à une variable intermédiaire. De cette façon, le code de retour (
$?
) est défini.Alors
Est-ce que la sortie
1
(ou la sortie si elleset -e
est présente), cependant:Sortira
0
après une ligne vide. Le code retour de l'écho (ou une autre commande exécutée avec la sortie backtick) remplacera le code retour 0.Je suis toujours ouvert aux solutions qui n'exigent pas la variable intermédiaire, mais cela me permet de faire quelques progrès.
la source
Comme le PO l'a souligné dans sa propre réponse, l'affectation du résultat de la sous-commande à une variable résout le problème. le
$?
reste indemne.Cependant, un cas de bord peut toujours vous intercepter avec de faux négatifs (c’est-à-dire que la commande échoue mais que l’erreur ne bouillonne pas),
local
déclaration de variable:local myvar=$(subcommand)
sera toujours revenir0
!bash(1)
souligne ceci:Voici un cas de test simple:
Le résultat:
la source
VAR=...
etlocal VAR=...
m'a vraiment déconcerté!Comme d'autres l'ont déjà dit,
local
le résultat retournera toujours 0. La solution consiste à déclarer d'abord la variable:Sortie:
la source
Pour sortir d'un échec de substitution de commande, vous pouvez définir explicitement
-e
dans un sous-shell, comme ceci:la source
Point intéressant!
Je ne suis jamais tombé sur ça, parce que je ne suis pas un ami de
set -e
(mais que je préfèretrap ... ERR
), mais j'ai déjà testé cela:trap ... ERR
ne$(...)
capturez pas non plus les erreurs internes (ou les backticks à l'ancienne).Je pense que le problème est (comme si souvent) qu’un sous-shell est appelé ici et
-e
signifie explicitement le shell actuel .Seule une autre solution qui m'est venue à l’esprit à ce moment-là serait d’utiliser la lecture
Cela jette
ERR
et avec-e
le shell sera terminé. Seul problème: cela ne fonctionne que pour les commandes avec une seule ligne de sortie (ou vous passez au travers de quelque chose qui joint des lignes).la source