Comment retourner une valeur de chaîne à partir d’une fonction Bash

Je voudrais retourner une chaîne à partir d’une fonction Bash.

Je vais écrire l’exemple en Java pour montrer ce que j’aimerais faire:

public Ssortingng getSomeSsortingng() { return "tadaa"; } Ssortingng variable = getSomeSsortingng(); 

L’exemple ci-dessous fonctionne en bash, mais existe-t-il un meilleur moyen de le faire?

 function getSomeSsortingng { echo "tadaa" } VARIABLE=$(getSomeSsortingng) 

Il n’y a pas de meilleur moyen que je connaisse. Bash ne connaît que les codes d’état (entiers) et les chaînes écrites sur la sortie standard.

Vous pourriez avoir la fonction prendre une variable comme premier argument et modifier la variable avec la chaîne que vous voulez retourner.

 #!/bin/bash set -x function pass_back_a_ssortingng() { eval "$1='foo bar rab oof'" } return_var='' pass_back_a_ssortingng return_var echo $return_var 

Estampes “foo bar rab oof”.

Edit : ajout des guillemets à l’endroit approprié pour permettre aux espaces dans la chaîne d’adresser le commentaire de @Luca Borrione.

Edit : En démonstration, voir le programme suivant. C’est une solution polyvalente: elle vous permet même de recevoir une chaîne dans une variable locale.

 #!/bin/bash set -x function pass_back_a_ssortingng() { eval "$1='foo bar rab oof'" } return_var='' pass_back_a_ssortingng return_var echo $return_var function call_a_ssortingng_func() { local lvar='' pass_back_a_ssortingng lvar echo "lvar='$lvar' locally" } call_a_ssortingng_func echo "lvar='$lvar' globally" 

Cela imprime:

 + return_var= + pass_back_a_ssortingng return_var + eval 'return_var='\''foo bar rab oof'\''' ++ return_var='foo bar rab oof' + echo foo bar rab oof foo bar rab oof + call_a_ssortingng_func + local lvar= + pass_back_a_ssortingng lvar + eval 'lvar='\''foo bar rab oof'\''' ++ lvar='foo bar rab oof' + echo 'lvar='\''foo bar rab oof'\'' locally' lvar='foo bar rab oof' locally + echo 'lvar='\'''\'' globally' lvar='' globally 

Edit : démontrer que la valeur de la variable d’origine est disponible dans la fonction, comme cela a été incorrectement critiqué par @Xichen Li dans un commentaire.

 #!/bin/bash set -x function pass_back_a_ssortingng() { eval "echo in pass_back_a_ssortingng, original $1 is \$$1" eval "$1='foo bar rab oof'" } return_var='original return_var' pass_back_a_ssortingng return_var echo $return_var function call_a_ssortingng_func() { local lvar='original lvar' pass_back_a_ssortingng lvar echo "lvar='$lvar' locally" } call_a_ssortingng_func echo "lvar='$lvar' globally" 

Cela donne une sortie:

 + return_var='original return_var' + pass_back_a_ssortingng return_var + eval 'echo in pass_back_a_ssortingng, original return_var is $return_var' ++ echo in pass_back_a_ssortingng, original return_var is original return_var in pass_back_a_ssortingng, original return_var is original return_var + eval 'return_var='\''foo bar rab oof'\''' ++ return_var='foo bar rab oof' + echo foo bar rab oof foo bar rab oof + call_a_ssortingng_func + local 'lvar=original lvar' + pass_back_a_ssortingng lvar + eval 'echo in pass_back_a_ssortingng, original lvar is $lvar' ++ echo in pass_back_a_ssortingng, original lvar is original lvar in pass_back_a_ssortingng, original lvar is original lvar + eval 'lvar='\''foo bar rab oof'\''' ++ lvar='foo bar rab oof' + echo 'lvar='\''foo bar rab oof'\'' locally' lvar='foo bar rab oof' locally + echo 'lvar='\'''\'' globally' lvar='' globally 

Toutes les réponses ci-dessus ignorent ce qui a été déclaré dans la page de manuel de bash.

  • Toutes les variables déclarées dans une fonction seront partagées avec l’environnement appelant.
  • Toutes les variables déclarées locales ne seront pas partagées.

Exemple de code

 #!/bin/bash f() { echo function starts local WillNotExists="It still does!" DoesNotExists="It still does!" echo function ends } echo $DoesNotExists #Should print empty line echo $WillNotExists #Should print empty line f #Call the function echo $DoesNotExists #Should print It still does! echo $WillNotExists #Should print empty line 

Et sortie

 $ sh -x ./x.sh + echo + echo + f + echo function starts function starts + local 'WillNotExists=It still does!' + DoesNotExists='It still does!' + echo function ends function ends + echo It still 'does!' It still does! + echo 

Aussi sous pdksh et ksh ce script fait la même chose!

Comme bstpierre ci-dessus, j’utilise et recommande l’utilisation de nommer explicitement les variables de sortie:

 function some_func() # OUTVAR ARG1 { local _outvar=$1 local _result # Use some naming convention to avoid OUTVARs to clash ... some processing .... eval $_outvar=\$_result # Instead of just =$_result } 

Notez l’utilisation des guillemets. Cela évitera d’interpréter le contenu de $result comme des caractères spéciaux du shell. J’ai trouvé que c’est un ordre de grandeur plus rapide que le result=$(some_func "arg1") idiome de capture d’un écho. La différence de vitesse semble encore plus notable en utilisant bash sur MSYS où la capture stdout à partir d’appels de fonctions est presque catastrophique.

Il est acceptable d’envoyer une variable locale car les locaux sont dynamicment étendus dans bash:

 function another_func() # ARG { local result some_func result "$1" echo result is $result } 

Bash, depuis la version 4.3, février 2014 (?), Prend explicitement en charge les variables de référence ou les références de noms (namerefs), au-delà de “eval”, avec les mêmes effets bénéfiques et indirectionnels, plus clairs dans vos scripts “oublier de ‘évaluer’ et doit corriger cette erreur”:

 declare [-aAfFgilnrtux] [-p] [name[=value] ...] typeset [-aAfFgilnrtux] [-p] [name[=value] ...] Declare variables and/or give them atsortingbutes ... -n Give each name the nameref atsortingbute, making it a name reference to another variable. That other variable is defined by the value of name. All references and assignments to name, except for⋅ changing the -n atsortingbute itself, are performed on the variable referenced by name's value. The -n atsortingbute cannot be applied to array variables. ... When used in a function, declare and typeset make each name local, as with the local command, unless the -g option is supplied... 

et aussi:

PARAMÈTRES

L’atsortingbut nameref peut être atsortingbué à une variable à l’aide de l’option -n aux commandes internes declare ou local (voir les descriptions de declare et local ci-dessous) pour créer une nameref ou une référence à une autre variable. Cela permet de manipuler les variables indirectement. Chaque fois que la variable nameref est référencée ou affectée, l’opération est réellement exécutée sur la variable spécifiée par la valeur de la variable nameref. Un nameref est couramment utilisé dans les fonctions shell pour faire référence à une variable dont le nom est passé en argument à la fonction. Par exemple, si un nom de variable est passé à une fonction shell en tant que premier argument, en cours d’exécution

  declare -n ref=$1 

À l’intérieur de la fonction, une variable nameref ref a été créée dont la valeur correspond au nom de la variable passé en premier argument. Les références et les affectations à ref sont traitées comme des références et des affectations à la variable dont le nom a été transmis à $ 1. Si la variable de contrôle dans une boucle for possède l’atsortingbut nameref, la liste de mots peut être une liste de variables shell et une référence de nom sera établie pour chaque mot de la liste, à son tour, lorsque la boucle est exécutée. Les variables de tableau ne peuvent pas recevoir l’atsortingbut -n. Cependant, les variables nameref peuvent référencer des variables de tableau et des variables de tableau en indice. Namerefs peut être désactivé en utilisant l’option -n de la commande interne unset. Sinon, si unset est exécuté avec le nom d’une variable nameref en tant qu’argument, la variable référencée par la variable nameref ne sera pas définie.

Par exemple ( EDIT 2 : (merci Ron) nomme (préfixe) le nom de la variable interne à la fonction, afin de minimiser les conflits de variables externes, qui devraient finalement répondre correctement, problème soulevé dans les commentaires de Karsten):

 # $1 : ssortingng; your variable to contain the return value function return_a_ssortingng () { declare -n ret=$1 local MYLIB_return_a_ssortingng_message="The date is " MYLIB_return_a_ssortingng_message+=$(date) ret=$MYLIB_return_a_ssortingng_message } 

et tester cet exemple:

 $ return_a_ssortingng result; echo $result The date is 20160817 

Notez que le bash “declare” intégré, lorsqu’il est utilisé dans une fonction, rend la variable déclarée “locale” par défaut, et “-n” peut également être utilisé avec “local”.

Je préfère distinguer les variables “déclarer important” des variables “ennuyeuses locales”, donc utiliser “déclarer” et “local” agit comme une documentation.

EDIT 1 – (Réponse au commentaire ci-dessous par Karsten) – Je ne peux plus append de commentaires ci-dessous, mais le commentaire de Karsten m’a fait réfléchir, alors j’ai fait le test suivant WORKS FINE, AFAICT – Karsten. des étapes de test à partir de la ligne de commande, montrant le problème que vous supposez existe, car ces étapes suivantes fonctionnent très bien:

 $ return_a_ssortingng ret; echo $ret The date is 20170104 

(Je l’ai exécuté tout de suite, après avoir collé la fonction ci-dessus dans un terme bash – comme vous pouvez le voir, le résultat fonctionne correctement.)

Vous pouvez également capturer la sortie de la fonction:

 #!/bin/bash function getSomeSsortingng() { echo "tadaa!" } return_var=$(getSomeSsortingng) echo $return_var # Alternative syntax: return_var=`getSomeSsortingng` echo $return_var 

Semble étrange, mais c’est mieux que d’utiliser les variables globales IMHO. Passer les parameters fonctionne comme d’habitude, il suffit de les placer entre les accolades ou les backticks.

Comme mentionné précédemment, le moyen “correct” de retourner une chaîne à partir d’une fonction est la substitution de commande. Dans le cas où la fonction doit également sortir sur la console (comme @Mani le mentionne ci-dessus), créez une fd temporaire au début de la fonction et redirigez-la vers la console. Fermez le fd temporaire avant de renvoyer votre chaîne.

 #!/bin/bash # file: func_return_test.sh returnSsortingng() { exec 3>&1 >/dev/tty local s=$1 s=${s:="some default ssortingng"} echo "writing directly to console" exec 3>&- echo "$s" } my_ssortingng=$(returnSsortingng "$*") echo "my_ssortingng: [$my_ssortingng]" 

exécuter un script sans parameters produit …

 # ./func_return_test.sh writing directly to console my_ssortingng: [some default ssortingng] 

espère que cela aide les gens

-Andy

La solution la plus simple et la plus robuste consiste à utiliser la substitution de commandes, comme d’autres personnes l’ont écrit:

 assign() { local x x="Test" echo "$x" } x=$(assign) # This assigns ssortingng "Test" to x 

L’inconvénient est la performance car cela nécessite un processus distinct.

L’autre technique proposée dans cette rubrique, à savoir le passage du nom d’une variable à atsortingbuer en tant qu’argument, a des effets secondaires, et je ne le recommanderais pas dans sa forme de base. Le problème est que vous aurez probablement besoin de certaines variables dans la fonction pour calculer la valeur de retour, et il se peut que le nom de la variable destinée à stocker la valeur de retour interfère avec l’un d’eux:

 assign() { local x x="Test" eval "$1=\$x" } assign y # This assigns ssortingng "Test" to y, as expected assign x # This will NOT assign anything to x in this scope # because the name "x" is declared as local inside the function 

Vous pourriez, bien sûr, ne pas déclarer les variables internes de la fonction comme locales, mais vous devriez vraiment le faire, sinon vous pourriez, par contre, écraser accidentellement une variable non associée de la scope parent s’il en existe une du même nom. .

Une solution de contournement possible est une déclaration explicite de la variable passée en tant que global:

 assign() { local x eval declare -g $1 x="Test" eval "$1=\$x" } 

Si le nom “x” est passé en tant qu’argument, la deuxième ligne du corps de la fonction remplacera la déclaration locale précédente. Mais les noms eux-mêmes peuvent toujours interférer, donc si vous avez l’intention d’utiliser la valeur précédemment stockée dans la variable passée avant d’écrire la valeur de retour, sachez que vous devez la copier dans une autre variable locale au tout début; sinon le résultat sera imprévisible! En outre, cela ne fonctionnera que dans la version la plus récente de BASH, à savoir 4.2. Un code plus portable pourrait utiliser des constructions conditionnelles explicites avec le même effet:

 assign() { if [[ $1 != x ]]; then local x fi x="Test" eval "$1=\$x" } 

La solution la plus élégante est peut-être de réserver un nom global pour les valeurs de retour de fonction et de l’utiliser systématiquement dans toutes les fonctions que vous écrivez.

Vous pouvez utiliser une variable globale:

 declare globalvar='some ssortingng' ssortingng () { eval "$1='some other ssortingng'" } # ---------- end of function ssortingng ---------- ssortingng globalvar echo "'${globalvar}'" 

Cela donne

 'some other ssortingng' 

Pour illustrer mon commentaire sur la réponse d’Andy, avec une manipulation supplémentaire du descripteur de fichier pour éviter d’utiliser /dev/tty :

 #!/bin/bash exec 3>&1 returnSsortingng() { exec 4>&1 >&3 local s=$1 s=${s:="some default ssortingng"} echo "writing to stdout" echo "writing to stderr" >&2 exec >&4- echo "$s" } my_ssortingng=$(returnSsortingng "$*") echo "my_ssortingng: [$my_ssortingng]" 

Encore méchant, cependant.

La façon dont vous l’avez est la seule façon de le faire sans casser la scope. Bash n’a pas de concept de type de retour, juste des codes de sortie et des descripteurs de fichiers (stdin / out / err, etc.)

S’adressant à la tête de Vicky Ronnen , considérant le code suivant:

 function use_global { eval "$1='changed using a global var'" } function capture_output { echo "always changed" } function test_inside_a_func { local _myvar='local starting value' echo "3. $_myvar" use_global '_myvar' echo "4. $_myvar" _myvar=$( capture_output ) echo "5. $_myvar" } function only_difference { local _myvar='local starting value' echo "7. $_myvar" local use_global '_myvar' echo "8. $_myvar" local _myvar=$( capture_output ) echo "9. $_myvar" } declare myvar='global starting value' echo "0. $myvar" use_global 'myvar' echo "1. $myvar" myvar=$( capture_output ) echo "2. $myvar" test_inside_a_func echo "6. $_myvar" # this was local inside the above function only_difference 

va donner

 0. global starting value 1. changed using a global var 2. always changed 3. local starting value 4. changed using a global var 5. always changed 6. 7. local starting value 8. local starting value 9. always changed 

Le scénario normal consiste peut-être à utiliser la syntaxe utilisée dans la fonction test_inside_a_func , vous pouvez donc utiliser les deux méthodes dans la majorité des cas, bien que la capture soit la méthode la plus sûre, quelle que soit la situation. peut trouver dans d’autres langues, comme Vicky Ronnen correctement souligné.

Les options ont toutes été énumérées, je pense. Le choix de l’un peut se résumer à une question du meilleur style pour votre application particulière, et dans cet ordre d’idées, je veux offrir un style particulier que j’ai trouvé utile. En bash, les variables et les fonctions ne sont pas dans le même espace de noms. Donc, traiter la variable du même nom comme la valeur de la fonction est une convention que je trouve minimise les conflits de noms et améliore la lisibilité, si je l’applique rigoureusement. Un exemple de la vie réelle:

 UnGetChar= function GetChar() { # assume failure GetChar= # if someone previously "ungot" a char if ! [ -z "$UnGetChar" ]; then GetChar="$UnGetChar" UnGetChar= return 0 # success # else, if not at EOF elif IFS= read -N1 GetChar ; then return 0 # success else return 1 # EOF fi } function UnGetChar(){ UnGetChar="$1" } 

Et, un exemple d’utilisation de ces fonctions:

 function GetToken() { # assume failure GetToken= # if at end of file if ! GetChar; then return 1 # EOF # if start of comment elif [[ "$GetChar" == "#" ]]; then while [[ "$GetChar" != $'\n' ]]; do GetToken+="$GetChar" GetChar done UnGetChar "$GetChar" # if start of quoted ssortingng elif [ "$GetChar" == '"' ]; then # ... et cetera 

Comme vous pouvez le constater, le statut de retour est à votre disposition lorsque vous en avez besoin ou ignorez-le si vous n’en avez pas besoin. La variable “retourné” peut également être utilisée ou ignorée, mais bien sûr uniquement après l’appel de la fonction.

Bien sûr, ce n’est qu’une convention. Vous êtes libre de ne pas définir la valeur associée avant de retourner (d’où ma convention de toujours l’annuler au début de la fonction) ou de piétiner sa valeur en appelant à nouveau la fonction (éventuellement indirectement). C’est quand même une convention que je trouve très utile si je me retrouve à faire un usage intensif des fonctions bash.

Par opposition au sentiment que ceci est un signe, par exemple, “passer à perl”, ma philosophie est que les conventions sont toujours importantes pour gérer la complexité de n’importe quelle langue.

Le problème principal de tout schéma de ‘variable de sortie nommée’ où l’appelant peut transmettre le nom de la variable (qu’il utilise eval ou declare -n ) est un aliasing involontaire, c’est-à-dire des conflits de noms: du sharepoint vue de l’encapsulation append ou renommer une variable locale dans une fonction sans vérifier TOUS les appelants de la fonction pour s’assurer qu’ils ne veulent pas transmettre ce même nom comme paramètre de sortie. (Ou dans l’autre sens, je ne veux pas avoir à lire la source de la fonction que j’appelle juste pour m’assurer que le paramètre de sortie que j’ai l’intention d’utiliser n’est pas un local dans cette fonction.)

La seule solution consiste à utiliser une seule variable de sortie dédiée comme REPLY (comme suggéré par Evi1M4chine ) ou une convention comme celle proposée par Ron Burk .

Cependant, il est possible que des fonctions utilisent une variable de sortie fixe en interne , puis ajoutent du sucre par-dessus pour cacher ce fait à l’appelant , comme je l’ai fait avec la fonction d’ call dans l’exemple suivant. Considérez ceci comme une preuve de concept, mais les points clés sont

  • La fonction assigne toujours la valeur de retour à REPLY , et peut également retourner un code de sortie comme d’habitude
  • Du sharepoint vue de l’appelant, la valeur de retour peut être affectée à n’importe quelle variable (locale ou globale), y compris REPLY (voir l’exemple de wrapper ). Le code de sortie de la fonction est transmis, donc leur utilisation, par exemple dans une construction if ou while ou similaire, fonctionne comme prévu.
  • Syntaxiquement, l’appel de fonction est toujours une simple déclaration simple.

La raison pour laquelle cela fonctionne est que la fonction d’ call elle-même n’a pas de locus et n’utilise aucune variable autre que REPLY , évitant tout risque de conflit de noms. Au point où le nom de la variable de sortie définie par l’appelant est atsortingbué, nous sums effectivement dans la scope de l’appelant (techniquement dans la scope identique de la fonction d’ call ), plutôt que dans la scope de la fonction appelée.

 #!/bin/bash function call() { # var=func [args ...] REPLY=; "${1#*=}" "${@:2}"; eval "${1%%=*}=\$REPLY; return $?" } function greet() { case "$1" in us) REPLY="hello";; nz) REPLY="kia ora";; *) return 123;; esac } function wrapper() { call REPLY=greet "$@" } function main() { local abcd call a=greet us echo "a='$a' ($?)" call b=greet nz echo "b='$b' ($?)" call c=greet de echo "c='$c' ($?)" call d=wrapper us echo "d='$d' ($?)" } main 

Sortie:

 a='hello' (0) b='kia ora' (0) c='' (123) d='hello' (0) 

Dans mes programmes, par convention, c’est la variable pré-existante de $REPLY , qui est utilisée à cette fin.

 function getSomeSsortingng { REPLY="tadaa" } getSomeSsortingng echo $REPLY 

Cela fait echo

 tadaa 

Mais pour éviter les conflits, toute autre variable globale fera l’affaire.

 declare result function getSomeSsortingng { result="tadaa" } getSomeSsortingng echo $result 

Si cela ne suffit pas, je recommande la solution de Markarian451 .

Vous pouvez echo une chaîne, mais attrapez-la en canalisant ( | ) la fonction vers autre chose.

Vous pouvez le faire avec expr , bien que ShellCheck rapporte cette utilisation comme étant obsolète.

modèle bash pour retourner à la fois les objects scalaires et les objects de valeur de tableau :

définition

 url_parse() { # parse 'url' into: 'url_host', 'url_port', ... local "$@" # inject caller 'url' argument in local scope local url_host="..." url_path="..." # calculate 'url_*' components declare -p ${!url_*} # return only 'url_*' object fields to the caller } 

invocation

 main() { # invoke url parser and inject 'url_*' results in local scope eval "$(url_parse url=http://host/path)" # parse 'url' echo "host=$url_host path=$url_path" # use 'url_*' components } 
 agt@agtsoft:~/temp$ cat ./fc #!/bin/sh fcall='function fcall { local res p=$1; shift; fname $*; eval "$p=$res"; }; fcall' function f1 { res=$[($1+$2)*2]; } function f2 { local a; eval ${fcall//fname/f1} a 2 3; echo f2:$a; } a=3; f2; echo after:a=$a, res=$res agt@agtsoft:~/temp$ ./fc f2:10 after:a=3, res=