Comment obtenir la valeur de sortie et de sortie d'un sous-shell lorsque j'utilise «bash -e»?

70

Considérons le code suivant

outer-scope.sh

#!/bin/bash
set -e
source inner-scope.sh
echo $(inner)
echo "I thought I would've died :("

introspection.sh

#!/bin/bash
function inner() { echo "winner"; return 1; }

J'essaie de outer-scope.shsortir quand un appel à inner()échouer. Depuis $()invoque un sous-shell, cela ne se produit pas.

Sinon, comment puis-je obtenir le résultat d'une fonction tout en préservant le fait que la fonction peut se fermer avec un code de sortie non nul?

jabalsad
la source

Réponses:

102

$()préserve le statut de sortie; il vous suffit de l'utiliser dans une instruction qui n'a pas de statut propre, comme une affectation.

sortie = $ (interne)

Après cela, $?contiendrait le statut de sortie de inner, et vous pouvez utiliser toutes sortes de contrôles pour cela:

output=$(inner) || exit $?
echo $output

Ou:

if ! output=$(inner); then
    exit $?
fi
echo $output

Ou:

if output=$(inner); then
    echo $output
else
    exit $?
fi

(Remarque: Un exitargument sans argument équivaut à exit $?- c'est-à-dire qu'il se ferme avec le statut de sortie de la dernière commande. J'ai utilisé le second formulaire uniquement pour plus de clarté.)


En outre, pour mémoire: sourceest totalement indépendant dans ce cas. Vous pouvez simplement définir inner()dans le outer-scope.shfichier, avec les mêmes résultats.

Grawity
la source
Pourquoi est-ce que, même si $? contient le statut de sortie de $ () que le script ne quitte pas automatiquement (étant donné que -e est défini)? EDIT: tant pis, je pense que vous avez répondu à mes questions, merci!
Jabalsad
Je ne suis pas sûr. (Je n'ai testé aucun des éléments ci-dessus.) Mais il y a quelques restrictions sur -e, toutes expliquées dans la page de manuel de bash; de plus, si vous posez la question à propos de echo $(), cela peut être dû au fait que les codes de sortie des sous-coquilles sont ignorés lorsque la ligne - la echocommande - a son propre code de sortie (généralement 0).
Grawity
1
Hmm, quand je tape if ! $(exit 1) ; then echo $?; fi, je comprends 0. Vous ne savez pas trop ifsi vous devez conserver cette valeur de sortie.
Ron Burk
@grawity - Cela fonctionne-t-il avec Dash? J'essaie de contourner un comportement gênant d'AutoConf (à savoir, le succès de la création de rapports Autoconf lorsque le compilateur de Sun ou du compilateur IBM imprime une option illégale au terminal).
jww
3
if ! output=$(inner); then exit $?; fiquittera avec un code retour de 0 car $?donnera le code retour de !au lieu du code retour de inner. Vous pouvez obtenir le comportement souhaité avec if output=$(inner); then : ; else exit $?; fimais c'est évidemment plus verbeux
SJL
26

Voir BashFAQ / 002 :

Si vous voulez les deux (statut de sortie et de sortie):

output=$(command)
status=$? 

Un cas particulier

Remarque sur un cas délicat avec des variables locales de fonction, comparez le code suivant:

f() { local    v=$(echo data; false); echo output:$v, status:$?; }
g() { local v; v=$(echo data; false); echo output:$v, status:$?; }

Nous aurons:

$ f     # fooled by 'local' with inline initialization
output:data, status:0

$ g     # a good one
output:data, status:1

Pourquoi?

Lorsque la sortie d'un sous-shell est utilisée pour initialiser une localvariable, l'état de sortie n'est plus celui du sous-shell, mais celui de la commande , ce qui est le plus susceptible de l'être .local 0

Voir aussi https://stackoverflow.com/a/4421282/537554

Ryenus
la source
3
Bien que cela ne réponde pas vraiment à la question, cela m’a été utile aujourd’hui, donc +1.
Fourpastmidnight
1
Le statut de sortie des commandes bash est toujours celui de la dernière commande exécutée. Lorsque nous passons tant de temps dans des langages fortement typés, il est facile d’oublier que "local" n’est pas un spécificateur de type, mais simplement une autre commande. Merci d'avoir réitéré ce point ici, m'a aidé aujourd'hui.
Markeissler
2
Wow, je suis tombé sur ce problème précis tout à l'heure et vous l'avez éclairci. Merci!
krb686
4
#!/bin/bash
set -e
source inner-scope.sh
foo=$(inner)
echo $foo
echo "I thought I would've died :("

En ajoutant echo, le sous-shell n'est pas autonome (n'est pas vérifié séparément) et n'abandonne pas. L'affectation contourne ce problème.

Vous pouvez également le faire et rediriger la sortie vers un fichier pour le traiter ultérieurement.

tmpfile=$( mktemp )
inner > $tmpfile
cat $tmpfile
rm $tmpfile
Daniel Beck
la source
Bien sûr, le fichier $ tmp continue d'exister dans la deuxième variante ...
Daniel Beck