Valeur de retour dans une fonction Bash

305

Je travaille avec un script bash et je veux exécuter une fonction pour imprimer une valeur de retour:

function fun1(){
  return 34
}
function fun2(){
  local res=$(fun1)
  echo $res
}

Lorsque j'exécute fun2, il n'imprime pas "34". pourquoi est-ce le cas?

Mindia
la source
8
returndans votre cas est essentiellement le même que celui exit codequi va de 0 - 255. Utilisez echocomme suggéré par @septi. Les codes de sortie peuvent être capturés avec $?.
devnull
1
Dans ce cas, il est beaucoup plus flexible d'utiliser déjà l'écho dans fun1. C'est l'idée de la programmation unix: echo envoie les résultats vers une sortie standard qui peut ensuite être réutilisée par d'autres fonctions avec res = $ (fun1) - ou directement dirigée vers d'autres fonctions:function a() { echo 34; } function b() { while read data; do echo $data ; done ;} a | b
Arne Babenhauserheide
La bonne façon de le faire est de placer les éléments de niveau supérieur dans une fonction et d'utiliser une règle de portée dynamique locale avec bash. Je vais créer une réponse pour démontrer, ce n'est pas une fonctionnalité bien connue mais entièrement prise en charge.
Oliver
Voir aussi: stackoverflow.com/a/8743103/12887
Jonathan Tran

Réponses:

374

Bien que bash ait une returninstruction, la seule chose que vous pouvez spécifier avec elle est le propre exitstatut de la fonction (une valeur entre 0et 255, 0 signifiant "succès"). Ce returnn'est donc pas ce que vous voulez.

Vous voudrez peut-être convertir votre returninstruction en echoinstruction - de cette façon, la sortie de votre fonction pourrait être capturée à l'aide d' $()accolades, ce qui semble être exactement ce que vous voulez.

Voici un exemple:

function fun1(){
  echo 34
}

function fun2(){
  local res=$(fun1)
  echo $res
}

Une autre façon d'obtenir la valeur de retour (si vous voulez simplement renvoyer un entier 0-255) est $?.

function fun1(){
  return 34
}

function fun2(){
  fun1
  local res=$?
  echo $res
}

Notez également que vous pouvez utiliser la valeur de retour pour utiliser la logique booléenne comme fun1 || fun2ne s'exécutera que fun2si fun1renvoie une 0valeur. La valeur de retour par défaut est la valeur de sortie de la dernière instruction exécutée dans la fonction.

tamasgal
la source
2
Vous devez exécuter fun1, puis la valeur de retour est stockée dans $?. Bien que je ne recommanderais pas de faire ça ...
tamasgal
9
Pourquoi ne pas utiliser $??
Pithikos
147
Non, j'ai besoin de cette putain de valeur de retour . Au diable l'écho.
Tomáš Zato - Reinstate Monica
7
@Blauhirn dans cet environnement, avec cette ||construction, un code de sortie de 0 est considéré comme un succès et donc "vrai". Non nul est une erreur et donc faux. Considérez fun1 || fun2comme un raccourci pour "si fun1 retourne le succès ou fun2 retourne le succès" plutôt qu'un opérateur sur les valeurs de retour réelles elles-mêmes.
davidA
6
Ce qui est ennuyeux, c'est qu'une fonction qui devrait fournir des données ne peut pas faire écho à Stdout, car l'appelant utilisant $ () le recevra également et sera confus ou devra analyser la sortie. Les variables globales ne sont pas géniales car ce n'est qu'une question de temps avant d'utiliser la même variable globale à deux endroits qui se trouvent imbriqués et les données pourraient être perdues. Il devrait y avoir des canaux distincts pour l'impression des données par rapport à l'envoi des données.
Oliver
68

$(...)capture le texte envoyé à stdout par la commande contenue dans. returnne sort pas sur stdout. $?contient le code de résultat de la dernière commande.

fun1 (){
  return 34
}

fun2 (){
  fun1
  local res=$?
  echo $res
}
Ignacio Vazquez-Abrams
la source
6
Oui returnest utilisé pour définir $?qui est le exit status. Dans l'exemple ci - dessus, fun1« s exit statusseraient 34. Notez $(...)également que capture également stderr en plus de stdout à partir de la commande spécifiée.
swoop81
59

Les fonctions dans Bash ne sont pas des fonctions comme dans une autre langue; ce sont en fait des commandes. Les fonctions sont donc utilisées comme s'il s'agissait de binaires ou de scripts extraits de votre chemin. Du point de vue de la logique de votre programme, il ne devrait y avoir vraiment aucune différence.

Les commandes shell sont connectées par des canaux (ou flux), et non par des types de données fondamentaux ou définis par l'utilisateur, comme dans les "vrais" langages de programmation. Il n'y a rien de tel qu'une valeur de retour pour une commande, peut-être principalement parce qu'il n'y a pas de véritable moyen de la déclarer. Cela peut se produire sur la page de manuel ou sur la --helpsortie de la commande, mais les deux ne sont lisibles que par l'homme et sont donc écrits au vent.

Lorsqu'une commande veut obtenir une entrée, elle la lit à partir de son flux d'entrée ou de la liste d'arguments. Dans les deux cas, les chaînes de texte doivent être analysées.

Lorsqu'une commande veut renvoyer quelque chose, elle doit la retrouver echodans son flux de sortie. Une autre façon souvent pratiquée consiste à stocker la valeur de retour dans des variables globales dédiées. L'écriture dans le flux de sortie est plus claire et plus flexible, car elle peut également prendre des données binaires. Par exemple, vous pouvez retourner facilement un BLOB:

encrypt() {
    gpg -c -o- $1 # encrypt data in filename to stdout (asks for a passphrase)
}

encrypt public.dat > private.dat # write function result to file

Comme d'autres l'ont écrit dans ce fil, l'appelant peut également utiliser la substitution de commandes $()pour capturer la sortie.

Parallèlement, la fonction "retournerait" le code de sortie de gpg(GnuPG). Considérez le code de sortie comme un bonus que les autres langues n'ont pas, ou, selon votre tempérament, comme un "Schmutzeffekt" de fonctions shell. Ce statut est, par convention, 0 en cas de succès ou un entier compris entre 1 et 255 pour autre chose. Pour que cela soit clair: return(comme exit) ne peut prendre qu'une valeur comprise entre 0 et 255, et des valeurs autres que 0 ne sont pas nécessairement des erreurs, comme cela est souvent affirmé.

Lorsque vous ne fournissez pas de valeur explicite, returnle statut provient de la dernière commande dans une instruction / fonction / commande Bash, etc. Il y a donc toujours un statut et returnc'est juste un moyen facile de le fournir.

Andreas Spindler
la source
4
+1 pour expliquer les fonctions par rapport aux commandes et comment cela affecte la notion de renvoi de données à l'appelant
Oliver
4
+1 pour expliquer que la programmation shell consiste à connecter des commandes via des canaux. D'autres langages de programmation composent des fonctions via des types de retour. Bash compose des commandes via des flux de texte.
jrahhali
29

L' returninstruction définit le code de sortie de la fonction, de la même manière exitque pour tout le script.

Le code de sortie de la dernière commande est toujours disponible dans la $?variable.

function fun1(){
  return 34
}

function fun2(){
  local res=$(fun1)
  echo $? # <-- Always echos 0 since the 'local' command passes.

  res=$(fun1)
  echo $?  #<-- Outputs 34
}
Austin Phillips
la source
21

Le problème avec les autres réponses est qu'elles utilisent soit un global, qui peut être écrasé lorsque plusieurs fonctions sont dans une chaîne d'appel, soit echoque votre fonction ne peut pas générer d'informations de diagnostic (vous oublierez que votre fonction fait cela et le "résultat", c'est-à-dire retour valeur, contiendra plus d'informations que ce que votre appelant attend, ce qui conduit à un bug étrange), ou evalqui est beaucoup trop lourd et hacky.

La bonne façon de le faire est de placer les éléments de niveau supérieur dans une fonction et d'utiliser une localrègle de portée dynamique avec bash. Exemple:

func1() 
{
    ret_val=hi
}

func2()
{
    ret_val=bye
}

func3()
{
    local ret_val=nothing
    echo $ret_val
    func1
    echo $ret_val
    func2
    echo $ret_val
}

func3

Cette sorties

nothing
hi
bye

La portée dynamique signifie que cela ret_valpointe vers un objet différent selon l'appelant! Ceci est différent de la portée lexicale, qui est utilisée par la plupart des langages de programmation. Il s'agit en fait d' une fonctionnalité documentée , facile à manquer, et pas très bien expliquée, voici la documentation pour elle (c'est moi qui souligne):

Les variables locales à la fonction peuvent être déclarées avec le builtin local. Ces variables ne sont visibles que par la fonction et les commandes qu'elle appelle .

Pour quelqu'un avec un fond C / C ++ / Python / Java / C # / javascript, c'est probablement le plus grand obstacle: les fonctions en bash ne sont pas des fonctions, ce sont des commandes et se comportent comme telles: elles peuvent sortir vers stdout/ stderr, elles peuvent diriger / out, ils peuvent retourner un code de sortie. Fondamentalement, il n'y a aucune différence entre la définition d'une commande dans un script et la création d'un exécutable qui peut être appelé à partir de la ligne de commande.

Donc au lieu d'écrire votre script comme ceci:

top-level code 
bunch of functions
more top-level code

écrivez-le comme ceci:

# define your main, containing all top-level code
main() 
bunch of functions
# call main
main  

main()déclare ret_valas localet toutes les autres fonctions renvoient des valeurs via ret_val.

Voir aussi la question Unix et Linux suivante: Portée des variables locales dans les fonctions shell .

Une autre solution, peut-être encore meilleure selon la situation, est celle publiée par ya.teck qui utilise local -n.

Oliver
la source
17

Une autre façon d'y parvenir est les références de nom (nécessite Bash 4.3+).

function example {
  local -n VAR=$1
  VAR=foo
}

example RESULT
echo $RESULT
ya.teck
la source
3
quiconque se demande ce qui -n <name>=<reference>fait: fait de la variable nouvellement créée une référence à une autre pointée par <reference>. D'autres affectations <name>sont effectuées sur la variable référencée.
Valerio
7

J'aime faire ce qui suit si je cours dans un script où la fonction est définie:

POINTER= # used for function return values

my_function() {
    # do stuff
    POINTER="my_function_return"
}

my_other_function() {
    # do stuff
    POINTER="my_other_function_return"
}

my_function
RESULT="$POINTER"

my_other_function
RESULT="$POINTER"

J'aime ça, car je peux ensuite inclure des déclarations d'écho dans mes fonctions si je veux

my_function() {
    echo "-> my_function()"
    # do stuff
    POINTER="my_function_return"
    echo "<- my_function. $POINTER"
}
doc
la source
5

En complément des excellents articles des autres, voici un article résumant ces techniques:

  • définir une variable globale
  • définir une variable globale, dont vous avez transmis le nom à la fonction
  • définissez le code retour (et récupérez-le avec $?)
  • 'écho' de certaines données (et récupérez-les avec MYVAR = $ (mafonction))

Renvoyer des valeurs à partir de fonctions Bash

Tom Hundt
la source
C'est la meilleure réponse, car l'article traite proprement de toutes les options.
mzimmermann
-2

Git Bash sur Windows à l' aide de tableaux pour plusieurs valeurs de retour

CODE DE BASE:

#!/bin/bash

##A 6-element array used for returning
##values from functions:
declare -a RET_ARR
RET_ARR[0]="A"
RET_ARR[1]="B"
RET_ARR[2]="C"
RET_ARR[3]="D"
RET_ARR[4]="E"
RET_ARR[5]="F"


function FN_MULTIPLE_RETURN_VALUES(){

   ##give the positional arguments/inputs
   ##$1 and $2 some sensible names:
   local out_dex_1="$1" ##output index
   local out_dex_2="$2" ##output index

   ##Echo for debugging:
   echo "running: FN_MULTIPLE_RETURN_VALUES"

   ##Here: Calculate output values:
   local op_var_1="Hello"
   local op_var_2="World"

   ##set the return values:
   RET_ARR[ $out_dex_1 ]=$op_var_1
   RET_ARR[ $out_dex_2 ]=$op_var_2
}


echo "FN_MULTIPLE_RETURN_VALUES EXAMPLES:"
echo "-------------------------------------------"
fn="FN_MULTIPLE_RETURN_VALUES"
out_dex_a=0
out_dex_b=1
eval $fn $out_dex_a $out_dex_b  ##<--Call function
a=${RET_ARR[0]} && echo "RET_ARR[0]: $a "
b=${RET_ARR[1]} && echo "RET_ARR[1]: $b "
echo
##----------------------------------------------##
c="2"
d="3"
FN_MULTIPLE_RETURN_VALUES $c $d ##<--Call function
c_res=${RET_ARR[2]} && echo "RET_ARR[2]: $c_res "
d_res=${RET_ARR[3]} && echo "RET_ARR[3]: $d_res "
echo
##----------------------------------------------##
FN_MULTIPLE_RETURN_VALUES 4 5  ##<---Call function
e=${RET_ARR[4]} && echo "RET_ARR[4]: $e "
f=${RET_ARR[5]} && echo "RET_ARR[5]: $f "
echo
##----------------------------------------------##


read -p "Press Enter To Exit:"

PRODUCTION ATTENDUE:

FN_MULTIPLE_RETURN_VALUES EXAMPLES:
-------------------------------------------
running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[0]: Hello
RET_ARR[1]: World

running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[2]: Hello
RET_ARR[3]: World

running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[4]: Hello
RET_ARR[5]: World

Press Enter To Exit:
JMI MADISON
la source