Comment renvoyer une valeur de chaîne à partir d'une fonction Bash

462

Je voudrais renvoyer une chaîne d'une fonction Bash.

Je vais écrire l'exemple en java pour montrer ce que j'aimerais faire:

public String getSomeString() {
  return "tadaa";
}

String variable = getSomeString();

L'exemple ci-dessous fonctionne en bash, mais existe-t-il une meilleure façon de procéder?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)
Tomas F
la source
4
En aparté, la function funcName {syntaxe héritée pré-POSIX est héritée du début de ksh (où il y avait des différences sémantiques que bash n'honore pas). funcName() {, avec no function, doit être utilisé à la place; voir wiki.bash-hackers.org/scripting/obsolete
Charles Duffy
Ce lien indique d'utiliser NAME () COMPOUND-CMD ou la fonction NAME {CMDS; } Tout function myFunction { blah; }va bien; c'est function myFunction() { blah }que ce n'est pas bien, c'est à dire l'utilisation de parenthèses avec la fonction mot-clé.
Will

Réponses:

290

Il n'y a pas de meilleure façon que je sache. Bash ne connaît que les codes d'état (entiers) et les chaînes écrites sur la sortie standard.

Philipp
la source
15
+1 @ tomas-f: vous devez faire très attention à ce que vous avez dans cette fonction "getSomeString ()" car avoir un code qui finira par faire écho signifiera que vous obtenez une chaîne de retour incorrecte.
Mani
11
Ceci est tout simplement faux. Vous pouvez renvoyer des données arbitraires à l'intérieur d'une variable de retour. Ce qui est clairement une meilleure façon.
Evi1M4chine
36
@ Evi1M4chine, euh ... non, tu ne peux pas. Vous pouvez définir une variable globale et l'appeler "retour", comme je le vois dans vos scripts. Mais alors c'est par convention, PAS réellement lié par programme à l'exécution de votre code. "clairement une meilleure façon"? Um non. La substitution de commandes est beaucoup plus explicite et modulaire.
Wildcard
6
«La substitution de commandes est beaucoup plus explicite et modulaire» serait pertinent si la question portait sur les commandes; cette question est de savoir comment retourner une chaîne, à partir d'une fonction bash! Une manière intégrée de faire ce que le PO a demandé est disponible depuis Bash 4.3 (2014?) - voir ma réponse ci-dessous.
zenaan
4
La question d'origine contient le moyen le plus simple de le faire et fonctionne bien dans la plupart des cas. Les valeurs de retour bash devraient probablement être appelées "codes de retour" car elles ressemblent moins aux valeurs de retour standard dans les scripts, et plus aux codes de sortie des commandes du shell numérique (vous pouvez faire des choses comme somefunction && echo 'success'). Si vous pensez à une fonction comme juste une autre commande, cela a du sens; les commandes ne «renvoient» rien à la sortie autre qu'un code d'état, mais elles peuvent sortir des choses entre-temps que vous pouvez capturer.
Beejor
193

Vous pouvez demander à la fonction de prendre une variable comme premier argument et de modifier la variable avec la chaîne que vous souhaitez renvoyer.

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

return_var=''
pass_back_a_string return_var
echo $return_var

Imprime "foo bar rab oof".

Edit : ajout de citations à l'endroit approprié pour permettre aux espaces dans la chaîne de répondre au commentaire de @Luca Borrione.

Modifier : à titre de démonstration, voir le programme suivant. Il s'agit d'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_string() {
    eval "$1='foo bar rab oof'"
}

return_var=''
pass_back_a_string return_var
echo $return_var

function call_a_string_func() {
     local lvar=''
     pass_back_a_string lvar
     echo "lvar='$lvar' locally"
}

call_a_string_func
echo "lvar='$lvar' globally"

Cela imprime:

+ return_var=
+ pass_back_a_string 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_string_func
+ local lvar=
+ pass_back_a_string 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émontrant que la valeur de la variable d'origine est disponible dans la fonction, comme l'a incorrectement critiqué par @Xichen Li dans un commentaire.

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "echo in pass_back_a_string, original $1 is \$$1"
    eval "$1='foo bar rab oof'"
}

return_var='original return_var'
pass_back_a_string return_var
echo $return_var

function call_a_string_func() {
     local lvar='original lvar'
     pass_back_a_string lvar
     echo "lvar='$lvar' locally"
}

call_a_string_func
echo "lvar='$lvar' globally"

Cela donne une sortie:

+ return_var='original return_var'
+ pass_back_a_string return_var
+ eval 'echo in pass_back_a_string, original return_var is $return_var'
++ echo in pass_back_a_string, original return_var is original return_var
in pass_back_a_string, 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_string_func
+ local 'lvar=original lvar'
+ pass_back_a_string lvar
+ eval 'echo in pass_back_a_string, original lvar is $lvar'
++ echo in pass_back_a_string, original lvar is original lvar
in pass_back_a_string, 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
bstpierre
la source
1
Cette réponse est super! Les paramètres peuvent être passés par des références, similaires à l'idée en C ++.
Yun Huang
4
Ce serait bien de recevoir une réponse d'un expert à propos de cette réponse. Je n'ai jamais vu cela utilisé dans les scripts, peut-être pour une bonne raison. Quoi qu'il en soit: c'est +1 Il aurait dû être voté pour la bonne réponse
John
N'est-ce pas la même chose de fgmréponse écrite de manière simplifiée? Cela ne fonctionnera pas si la chaîne foocontient des espaces blancs, tandis que fgmcelle de .. sera comme il le montre.
Luca Borrione
4
@XichenLi: merci d'avoir laissé un commentaire avec votre downvote; s'il vous plaît voir mon montage. Vous pouvez obtenir la valeur initiale de la variable dans la fonction avec \$$1. Si vous cherchez quelque chose de différent, faites-le moi savoir.
bstpierre
1
@timiscoding Cela peut être corrigé avec un printf '%q' "$var". % q est une chaîne de format pour l'échappement du shell. Ensuite, passez-le simplement cru.
bb010g
99

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 à l'intérieur d'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

Sous pdksh et ksh également, ce script fait de même!

Vicky Ronnen
la source
10
Cette réponse a ses mérites. Je suis venu ici en pensant que je voulais retourner une chaîne d'une fonction. Cette réponse m'a fait réaliser que c'était juste mes conversations en C #. Je soupçonne que d'autres peuvent avoir la même expérience.
LOAS
4
@ElmarZander Vous vous trompez, c'est tout à fait pertinent. C'est un moyen simple d'entrer dans la portée globale une valeur de portée de fonction, et certains considéreraient cela mieux / plus simplement que l'approche eval pour redéfinir une variable globale comme indiqué par bstpierre.
KomodoDave
local n'est pas portable pour les scripts non bash, ce qui est une raison pour laquelle certaines personnes l'évitent.
don bright
Question: Qu'en est-il des variables dans les boucles?
anu
1
Sur un mac ($ bash --version GNU bash, version 3.2.57 (1) -release (x86_64-apple-darwin14) Copyright (C) 2007 Free Software Foundation, Inc.), il est correct qu'une variable globale correspondante soit initialisé, mais lorsque j'essaie de créer un effet secondaire sur la même variable dans une autre fonction f2, cet effet secondaire n'est pas persistant. Donc, cela semble très incohérent et donc pas bon pour mon utilisation.
AnneTheAgile
45

Bash, depuis la version 4.3, février 2014 (?), A un support explicite pour les variables de référence ou les références de nom (namerefs), au-delà de "eval", avec les mêmes performances bénéfiques et effet d'indirection, et qui peuvent être plus clairs dans vos scripts et aussi plus difficiles pour "oublier" eval "et devoir corriger cette erreur":

declare [-aAfFgilnrtux] [-p] [name[=value] ...]
typeset [-aAfFgilnrtux] [-p] [name[=value] ...]
  Declare variables and/or give them attributes
  ...
  -n Give each name the nameref attribute, 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 attribute itself, are performed on the variable
     referenced by name's value.  The -n attribute 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

Une variable peut être affectée à l'attribut nameref à l'aide de l'option -n aux commandes internes ou locales de déclaration (voir les descriptions de déclarer et local ci-dessous) pour créer un nom de référence ou une référence à une autre variable. Cela permet aux variables d'être manipulées indirectement. Chaque fois que la variable nameref est référencée ou affectée à, l'opération est réellement effectué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 transmis à une fonction shell comme premier argument, l'exécution

      declare -n ref=$1

à l'intérieur de la fonction crée une variable nameref ref dont la valeur est le nom de variable passé comme 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é passé à $ 1. Si la variable de contrôle dans une boucle for possède l'attribut 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'attribut -n. Cependant, les variables nameref peuvent référencer des variables de tableau et des variables de tableau en indice. Les références de nom peuvent être désactivées à l'aide de l'option -n de la commande intégrée non définie. Sinon, si unset est exécuté avec le nom d'une variable nameref comme argument,

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

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

et tester cet exemple:

$ return_a_string result; echo $result
The date is 20160817

Notez que la commande bash "declare", lorsqu'elle est utilisée dans une fonction, rend la variable déclarée "locale" par défaut, et "-n" peut également être utilisée avec "local".

Je préfère distinguer les variables "déclaration importante" des variables "locales ennuyeuses", donc l'utilisation de "déclarer" et "local" de cette manière fait office de documentation.

EDIT 1 - (Réponse au commentaire ci-dessous par Karsten) - Je ne peux plus ajouter de commentaires ci-dessous, mais le commentaire de Karsten m'a fait réfléchir, j'ai donc fait le test suivant qui FONCTIONNE BIEN, AFAICT - Karsten si vous lisez ceci, veuillez fournir un ensemble exact des étapes de test à partir de la ligne de commande, montrant le problème que vous supposez exister, car ces étapes suivantes fonctionnent très bien:

$ return_a_string ret; echo $ret
The date is 20170104

(Je l'ai exécuté tout à l'heure, après avoir collé la fonction ci-dessus dans un terme bash - comme vous pouvez le voir, le résultat fonctionne très bien.)

zenaan
la source
4
J'espère que cela s'infiltre au sommet. eval devrait être un dernier recours. Il convient de mentionner que les variables nameref ne sont disponibles que depuis bash 4.3 (selon le changelog ) (publié en février 2014 [?]). Ceci est important si la portabilité est un problème. Veuillez citer le manuel bash sur le fait que declaredes variables locales sont créées à l'intérieur des fonctions (cette information n'est pas donnée par help declare): "... Lorsqu'elles sont utilisées dans une fonction, déclarer et composer rendent chaque nom local, comme avec la commande locale, sauf si le - l'option g est fournie ... "
init_js
2
Cela a le même problème d'alias que la solution eval. Lorsque vous appelez une fonction et transmettez le nom de la variable de sortie, vous devez éviter de transmettre le nom d'une variable utilisée localement dans la fonction que vous appelez. C'est un problème majeur en termes d'encapsulation, car vous ne pouvez pas simplement ajouter ou renommer de nouvelles variables locales dans une fonction sans que l'une des fonctions des appelants veuille utiliser ce nom pour le paramètre de sortie.
Karsten
1
@Karsten a accepté. dans les deux cas (eval et namerefs), vous devrez peut-être choisir un nom différent. Un avantage de l'approche nameref par rapport à eval est qu'il n'est pas nécessaire de gérer les chaînes qui s'échappent. Bien sûr, vous pouvez toujours faire quelque chose comme K=$1; V=$2; eval "$A='$V'";, mais une erreur (par exemple un paramètre vide ou omis), et ce serait plus dangereux. @zenaan, le problème soulevé par @Karsten s'applique si vous choisissez "message" comme nom de variable de retour, au lieu de "ret".
init_js
3
Une fonction doit probablement être conçue dès le début pour accepter un argument nameref, donc l'auteur de la fonction doit être conscient de la possibilité d'une collision de nom et peut utiliser une convention typique pour éviter cela. Par exemple, à l'intérieur de la fonction X, nommez les variables locales avec la convention "X_LOCAL_name".
Ron Burk
34

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 de la citation du $. Cela évitera d'interpréter le contenu en $resulttant que caractères spéciaux du shell. J'ai trouvé que c'est un ordre de grandeur plus rapide que l' 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 fonction est presque catastrophique.

Il est correct d'envoyer des variables locales car les sections locales sont étendues dynamiquement dans bash:

function another_func() # ARG
{
   local result
   some_func result "$1"
   echo result is $result
}
Markarian451
la source
4
Cela m'aide car j'aime utiliser plusieurs instructions d'écho à des fins de débogage / journalisation. L'idiome de capture d'écho échoue car il les capture tous. Je vous remercie!
AnneTheAgile
C'est la (deuxième meilleure) solution appropriée! Propre, rapide, élégant, sensible.
Evi1M4chine
+2 pour le garder réel. Je voulais dire. Comment tant de gens peuvent-ils ignorer la combinaison d'un echointérieur d'une fonction, combinée à la substitution de commandes!
Anthony Rutledge
23

Vous pouvez également capturer la sortie de la fonction:

#!/bin/bash
function getSomeString() {
     echo "tadaa!"
}

return_var=$(getSomeString)
echo $return_var
# Alternative syntax:
return_var=`getSomeString`
echo $return_var

Ça a l'air bizarre, mais c'est mieux que d'utiliser des variables globales à mon humble avis. Le passage des paramètres fonctionne comme d'habitude, il suffit de les mettre entre accolades ou backticks.

chiborg
la source
11
à part la note de syntaxe alternative, n'est-ce pas exactement la même chose que l'op a déjà écrit dans sa propre question?
Luca Borrione
très clair, merci!
bcag2 Il y a
12

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

assign()
{
    local x
    x="Test"
    echo "$x"
}

x=$(assign) # This assigns string "Test" to x

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

L'autre technique suggérée dans cette rubrique, à savoir le passage du nom d'une variable à affecter 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 peut arriver que le nom de la variable destinée à stocker la valeur de retour interfère avec l'une d'entre elles:

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

assign y # This assigns string "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

Bien sûr, vous ne pouvez pas déclarer les variables internes de la fonction comme locales, mais vous devez toujours le faire car sinon vous pouvez, par contre, écraser accidentellement une variable non liée de la portée parent s'il y en a une avec le même nom .

Une solution de contournement possible est une déclaration explicite de la variable passée comme globale:

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

Si le nom "x" est passé en 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 encore interférer, donc si vous avez l'intention d'utiliser la valeur précédemment stockée dans la variable passée avant d'y é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 simplement de réserver un nom global pour les valeurs de retour de fonction et de l'utiliser de manière cohérente dans chaque fonction que vous écrivez.

Tomasz Żuk
la source
3
Ce ^^^. Le repliement involontaire qui rompt l'encapsulation est le gros problème avec les solutions evalet declare -n. La solution de contournement d'avoir un seul nom de variable dédié comme resultpour tous les paramètres de sortie semble la seule solution qui ne nécessite pas de fonctions pour connaître tous ses appelants pour éviter les conflits.
Karsten
12

Comme mentionné précédemment, la manière "correcte" de renvoyer une chaîne à partir d'une fonction consiste à substituer une commande. Dans le cas où la fonction doit également sortir sur la console (comme @Mani le mentionne ci-dessus), créez un fd temporaire au début de la fonction et redirigez-le vers la console. Fermez le fd temporaire avant de renvoyer votre chaîne.

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

my_string=$(returnString "$*")
echo "my_string:  [$my_string]"

exécuter un script sans paramètres produit ...

# ./func_return_test.sh
writing directly to console
my_string:  [some default string]

j'espère que cela aide les gens

-Andy

Andy
la source
6
Cela a ses utilités, mais dans l'ensemble, vous devez éviter de faire une redirection explicite vers la console; la sortie peut déjà être redirigée ou le script s'exécute dans un contexte où aucun tty n'existe. Vous pouvez contourner cela en dupliquant 3>&1à la tête du script, puis en manipulant &1 &3et un autre espace réservé &4au sein de la fonction. Moche tout autour, cependant.
jmb
8

Vous pouvez utiliser une variable globale:

declare globalvar='some string'

string ()
{
  eval  "$1='some other string'"
} # ----------  end of function string  ----------

string globalvar

echo "'${globalvar}'"

Cela donne

'some other string'
Fritz G. Mehner
la source
6

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

#!/bin/bash

exec 3>&1

returnString() {
    exec 4>&1 >&3
    local s=$1
    s=${s:="some default string"}
    echo "writing to stdout"
    echo "writing to stderr" >&2
    exec >&4-
    echo "$s"
}

my_string=$(returnString "$*")
echo "my_string:  [$my_string]"

Toujours méchant, cependant.

jmb
la source
3

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

Daenyth
la source
3

S'adresser à Vicky Ronnen tête haute, en 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

Peut-être que le scénario normal consiste à utiliser la syntaxe utilisée dans la test_inside_a_funcfonction, vous pouvez donc utiliser les deux méthodes dans la majorité des cas, bien que la capture de la sortie soit la méthode la plus sûre fonctionnant toujours dans toutes les situations, imitant la valeur de retour d'une fonction que vous pouvez trouver dans d'autres langues, comme Vicky Ronnenindiqué correctement.

Luca Borrione
la source
2

Je pense que les options ont toutes été énumérées. Le choix d'un peut se résumer à une question du meilleur style pour votre application particulière, et dans cette veine, 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. Ainsi, le traitement de 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 string
    elif [ "$GetChar" == '"' ]; then
# ... et cetera

Comme vous pouvez le voir, le statut de retour est là pour que vous puissiez l'utiliser lorsque vous en avez besoin, ou ignorer si vous ne le faites pas. La variable "retournée" peut également être utilisée ou ignorée, mais bien sûr seulement 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). Pourtant, c'est une convention que je trouve très utile si je me retrouve à faire un usage intensif des fonctions bash.

Par opposition au sentiment que c'est un signe que l'on devrait par exemple "passer à perl", ma philosophie est que les conventions sont toujours importantes pour gérer la complexité de n'importe quel langage.

Ron Burk
la source
2

Vous pouvez echoune chaîne, mais attrapez-la par piping (| ) la fonction vers autre chose.

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

apennebaker
la source
Le problème est que la chose à droite du tuyau est une sous-coque. Donc myfunc | read OUTPUT ; echo $OUTPUTne rapporte rien. myfunc | ( read OUTPUT; echo $OUTPUT )obtient la valeur attendue et clarifie ce qui se passe sur le côté droit. Mais bien sûr, OUTPUT n'est pas disponible là où vous en avez besoin ...
Ed Randall
2

Ils posent un problème majeur de tout schéma de «variable de sortie nommée» où l'appelant peut transmettre le nom de la variable (que ce soit en utilisant evalou declare -n) est un alias par inadvertance, c'est-à-dire des conflits de noms: du point de vue de l'encapsulation, il est horrible de ne pas pouvoir ajouter ou renommer une variable locale dans une fonction sans vérifier d'abord TOUS les appelants de la fonction pour s'assurer qu'ils ne veulent pas passer le même nom que le 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 suggérée par Ron Burk .

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

  • La fonction affecte toujours la valeur de retour à REPLY, et peut également retourner un code de sortie comme d'habitude
  • Du point de vue de l'appelant, la valeur de retour peut être affectée à n'importe quelle variable (locale ou globale), y compris REPLY(voir l' wrapperexemple). Le code de sortie de la fonction est transmis, donc en les utilisant par exemple dans un ifouwhile ou de travaux de constructions similaires comme prévu.
  • Syntaxiquement, l'appel de fonction est toujours une simple instruction simple.

La raison pour laquelle cela fonctionne est que la callfonction elle-même n'a pas de sections locales et n'utilise aucune variable autre que REPLY, évitant tout risque de conflits de noms. Au moment où le nom de la variable de sortie définie par l'appelant est attribué, nous sommes effectivement dans la portée de l'appelant (techniquement dans la portée identique de la callfonction), plutôt que dans la portée 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 a b c d
  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

Production:

a='hello' (0)
b='kia ora' (0)
c='' (123)
d='hello' (0)
Karsten
la source
1

modèle bash pour renvoyer les objets de valeur scalaire et 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
}
Andrei Pozolotin
la source
0

Dans mes programmes, par convention, c'est à cela que sert la $REPLYvariable préexistante , qui l' readutilise dans ce but précis.

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

Ce echoes

tadaa

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

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

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

Evi1M4chine
la source
-2
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=
agtsoft
la source