L'exécution du exit
dans un sous-shell est un piège:
#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)
Le script imprime 42, quitte le sous - shell avec le code retour 1
et continue avec le script. Même remplacer l'appel par echo $(CALC) || exit 1
n'aide pas, car le code de retour echo
est 0 quel que soit le code de retour de calc
. Et calc
est exécuté avant echo
.
Encore plus déconcertant est de contrecarrer l’effet de exit
en l’enveloppant dans les fonctions local
intégrées, comme dans le script suivant. Je suis tombé sur le problème lorsque j'ai écrit une fonction pour vérifier une valeur d'entrée. Exemple:
Je veux créer un fichier nommé "année mois jour.log", c'est-à-dire 20141211.log
pour aujourd'hui. La date est saisie par un utilisateur qui peut ne pas fournir une valeur raisonnable. Par conséquent, dans ma fonction, fname
je vérifie la valeur de retour de date
pour vérifier la validité de l'entrée utilisateur:
#!/bin/bash
doit ()
{
local FNAME=$(fname "$1") || exit 1
touch "${FNAME}"
}
fname ()
{
date +"%Y%m%d.log" -d"$1" 2>/dev/null
if [ "$?" != 0 ] ; then
echo "fname reports \"Illegal Date\"" >&2
exit 1
fi
}
doit "$1"
Cela semble bon. Laissez le script être nommé s.sh
. Si l'utilisateur appelle le script avec ./s.sh "Thu Dec 11 20:45:49 CET 2014"
, le fichier 20141211.log
est créé. Cependant, si l'utilisateur tape ./s.sh "Thu hec 11 20:45:49 CET 2014"
, le script génère:
fname reports "Illegal Date"
touch: cannot touch ‘’: No such file or directory
La ligne fname…
indique que les données d'entrée incorrectes ont été détectées dans le sous-shell. Mais la exit 1
fin de la local …
ligne n'est jamais déclenchée car la local
directive est toujours renvoyée 0
. En effet, il local
est exécuté après $(fname)
et remplace donc son code de retour. Et à cause de cela, le script continue et appelle touch
avec un paramètre vide. Cet exemple est simple mais le comportement de bash peut être assez déroutant dans une application réelle. Je sais que les vrais programmeurs n'utilisent pas les locaux. "
Pour que ce soit bien clair: Sans le local
, le script s'interrompt comme prévu lorsqu'une date invalide est entrée.
La solution est de scinder la ligne comme
local FNAME
FNAME=$(fname "$1") || exit 1
Le comportement étrange est conforme à la documentation de local
la page de manuel de bash: "Le statut de retour est 0 sauf si local est utilisé en dehors d'une fonction, si un nom non valide est fourni ou si le nom est une variable en lecture seule."
Bien que n'étant pas un bug, je pense que le comportement de bash est contre-intuitif. Je suis conscient de la séquence d'exécution, local
ne doit pas masquer une affectation cassée, néanmoins.
Ma réponse initiale contenait des inexactitudes. Après une discussion révélatrice et approfondie avec mikeserv (merci pour cela), je suis allé les réparer.