web-dev-qa-db-fra.com

Renvoie une valeur dans une fonction Bash

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, le code "34" n'est pas imprimé. pourquoi est-ce le cas?

247
mindia

Bien que bash ait une instruction return, vous ne pouvez spécifier que le statut propre de la fonction exit (valeur comprise entre 0 et 255, 0 signifiant "succès" ). Donc, return n’est pas ce que vous voulez.

Vous voudrez peut-être convertir votre instruction return en une instruction echo. Ainsi, la sortie de votre fonction pourra ê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
}

Un autre moyen d'obtenir la valeur de retour (si vous souhaitez simplement renvoyer un entier compris entre 0 et 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 une logique booléenne, comme fun1 || fun2 ne sera exécuté que fun2 si fun1 renvoie une valeur 0. La valeur de retour par défaut est la valeur de sortie de la dernière instruction exécutée dans la fonction.

318
tamasgal

$(...) capture le texte envoyé à stdout par la commande qu'il contient. return ne sort pas sur la sortie standard. $? contient le code de résultat de la dernière commande.

fun1 (){
  return 34
}

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

Les fonctions dans Bash ne sont pas des fonctions comme dans une autre langue; ce sont en réalité des commandes. Les fonctions sont donc utilisées comme s'il s'agissait de fichiers binaires ou de scripts extraits de votre chemin. Du point de vue de la logique de votre programme, il ne devrait y avoir 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’existe pas de valeur de retour pour une commande, peut-être surtout parce qu’il n’existe aucun moyen réel de la déclarer. Cela pourrait se produire sur la page de manuel, ou sur la sortie --help de la commande, mais les deux ne sont lisibles que par l'homme et sont donc écrits au vent.

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

Quand une commande veut renvoyer quelque chose, elle doit echo le dans son flux de sortie. Une autre méthode souvent utilisé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 facilement retourner 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 commande $() pour capturer la sortie.

Parallèlement, la fonction "renverrait" le code de sortie de gpg (GnuPG). Pensez au code de sortie comme un bonus que n’ont pas les autres langues ou, selon votre tempérament, comme un "Schmutzeffekt" de fonctions Shell. Cet état est, par convention, égal à 0 en cas de succès ou à un nombre entier compris entre 1 et 255 pour un autre résultat. 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 on l'affirme souvent.

Lorsque vous ne fournissez pas de valeur explicite avec return, le statut provient de la dernière commande dans une instruction/fonction/commande Bash, etc. Il existe donc toujours un statut et return n’est qu’un moyen simple de le fournir.

49
Andreas Spindler

L'instruction return définit le code de sortie de la fonction, de la même manière que exit pour le script entier.

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
}
25
Austin Phillips

Le problème avec d’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 echo qui signifie que votre fonction ne peut pas émettre d’informations de diagnostic (vous oublierez que votre fonction le fait et " result ", c'est-à-dire que la valeur renvoyée contiendra plus d'informations que votre appelant n'en attend, conduisant à un bug étrange), ou eval qui est beaucoup trop lourd et hacky.

Pour ce faire, la méthode appropriée consiste à placer les éléments de niveau supérieur dans une fonction et à utiliser un local avec la règle de portée dynamique de 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 sortie

nothing
hi
bye

La portée dynamique signifie que ret_val pointe sur un objet différent en fonction de l'appelant! Cela diffère de la portée lexicale, qui est celle utilisée par la plupart des langages de programmation. C’est en fait ne fonctionnalité documentée , tout simplement facile à manquer, et pas très bien expliqué, voici la documentation à ce sujet (c’est moi qui souligne):

Les variables locales à la fonction peuvent être déclarées avec la commande locale intégrée. Ces variables ne sont visibles que par la fonction et les commandes qu'elle appelle .

Pour quelqu'un avec un arrière-plan C/C++/Python/Java/C #/javascript, c'est probablement le plus gros obstacle: les fonctions de bash ne sont pas des fonctions, ce sont des commandes et se comportent comme telles: elles peuvent générer stdout/stderr, ils peuvent effectuer un pipe in/out, ils peuvent renvoyer un code de sortie. En gros, il n'y a pas de différence entre définir une commande dans un script et créer un exécutable pouvant ê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

écris-le comme ceci:

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

main() déclare ret_val comme local et toutes les autres fonctions renvoient des valeurs via ret_val.

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

Une autre solution, peut-être même meilleure en fonction de la situation, est celle publiée par ya.teck qui utilise local -n.

9
Oliver

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

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

example RESULT
echo $RESULT
7
ya.teck

J'aime effectuer les opérations suivantes si vous utilisez un script dans lequel 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, parce que je peux ensuite inclure des instructions d'écho dans mes fonctions si je le souhaite.

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

En plus des excellents articles d’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éfinir le code de retour (et le récupérer avec $?)
  • 'echo' certaines données (et les récupérer avec MYVAR = $ (myfunction))

valeurs de retour des fonctions Bash

3
Tom Hundt