Comment renvoyer 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 je voudrais faire:

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

String variable = getSomeString();

L'exemple ci-dessous fonctionne en bash, mais est-il une meilleure façon de le faire?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)
381
demandé sur Jeff Puckett 2010-07-13 15:55:37

18 réponses

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 dans la sortie standard.

237
répondu Philipp 2010-07-13 12:03:54

Vous pouvez demander à la fonction de prendre une variable comme premier arg et de modifier la variable avec la chaîne que vous voulez 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 d'absence du bureau".

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

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

#!/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"

Cette impression:

+ 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é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_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 la 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
177
répondu bstpierre 2012-12-04 16:45:35

Toutes les réponses ci-dessus ignorent ce qui a été indiqué 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!

91
répondu Vicky Ronnen 2012-01-24 15:20:18

Comme bstpierre ci-dessus, j'utilise et je 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 citer le $. Cela évitera d'interpréter le contenu dans $result en tant que caractères spéciaux du shell. J'ai trouvé que c'est un ordre de grandeur plus rapide que l'idiome result=$(some_func "arg1") de capturer 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.

C'est ok pour envoyer dans une variable locale puisque les locaux sont dynamiquement étendus dans bash:

function another_func() # ARG
{
   local result
   some_func result "$1"
   echo result is $result
}
30
répondu Markarian451 2013-01-26 21:36:00

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 noms (namerefs), au-delà de "eval", avec le même effet bénéfique de performance et d'indirection, et qui peut être plus clair dans vos scripts et aussi plus difficile à "oublier de" eval "et à 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 se voir attribuer l'attribut nameref à l'aide de l'option-n declare ou local builtin commandes (voir les descriptions de déclarer et locales ci-dessous) pour créer un nameref, ou une référence à une autre variable. Cela permet variables à manipuler indirectement. Chaque fois que la variable nameref est⋅ référencé ou assigné à, l'opération est réellement effectuée sur la variable spécifié par la valeur de la variable nameref. Un nameref est couramment utilisé dans shell fonctions pour faire référence à une variable dont le nom est passé en argument⋅ fonction. Par exemple, si une variable le nom est passé à une fonction shell comme premier argument, en cours d'exécution

      declare -n ref=$1

À l'intérieur de la fonction crée une variable nameref ref dont la valeur est la variable nom passé comme premier argument. Les références et les affectations à ref sont traités comme des références et des affectations à la variable dont le nom a été passé comme⋅ $1. Si la variable de contrôle dans une boucle for a l'attribut nameref, la liste de mots peut être une liste de variables shell, et un nom de référence sera⋅ établi pour chaque mot dans 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 peut référencer des variables de tableau et des variables de tableau sous-inscrites. Namerefs peut être⋅ désactiver à l'aide de l'option-n à la fonction intégrée désactiver. Sinon, si unset est exécuté avec le nom d'une variable nameref comme argument, la variable référencée par⋅ la variable nameref sera désactivée.

Par exemple ( EDIT 2 : (Merci Ron) namespaced (préfixé) la fonction-nom de la variable interne, pour minimiser les conflits de variables externes, ce 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 le bash" declare " intégré, lorsqu'il est utilisé dans une fonction, rend la variable déclarée "local" par défaut, et "-n" peut également être utilisé avec "local".

Je préfère distinguer les variables" déclaration importante" à partir de variables "locales ennuyeuses", l'utilisation de "declare" et "local" de cette manière agit comme 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, alors j'ai fait le test suivant qui fonctionne bien, AFAICT-Karsten si vous lisez ceci, veuillez fournir un ensemble exact d'étapes de test à partir de la ligne de commande, montrant le problème]}

$ return_a_string ret; echo $ret
The date is 20170104

(I a couru cela 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.)

30
répondu zenaan 2017-04-10 10:37:24

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

Semble bizarre, mais c'est mieux que d'utiliser des variables globales à mon humble avis. Passer les paramètres fonctionne comme d'habitude, il suffit de les mettre à l'intérieur des accolades ou des backticks.

17
répondu chiborg 2010-08-17 09:02:12

Comme mentionné précédemment, la manière "correcte" de renvoyer une chaîne à partir d'une fonction est avec la substitution de commande. Dans le cas où la fonction doit également sortir vers la console (comme le mentionne @Mani 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 retourner 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]"

L'exécution d'un script sans params produit...

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

J'espère que cela aide les gens

-Andy

11
répondu Andy 2016-11-25 23:04:11

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 string "Test" to x

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

L'autre technique suggérée dans ce sujet, à savoir passer le nom d'une variable à assigner en 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 il arrive 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

Vous pouvez, bien sûr, ne pas déclarer les variables internes de la fonction comme locales, mais vous devriez vraiment toujours le faire car sinon vous pouvez, d'un autre côté, é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 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 écrasera 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'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. Plus portable le code peut utiliser des constructions conditionnelles explicites avec le même effet:

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

Peut-être que la solution la plus élégante est juste de réserver un nom global pour les valeurs de retour de fonction et utilisez-le de manière cohérente dans chaque fonction que vous écrivez.

9
répondu Tomasz Żuk 2013-08-12 23:34:09

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'
8
répondu Fritz G. Mehner 2010-07-13 12:58:52

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.

6
répondu jmb 2014-03-13 12:02:46

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)

3
répondu Daenyth 2010-07-14 02:49:31

Adressage tête haute de Vicky Ronnen , compte tenu du 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



donnera

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 est d'utiliser la syntaxe utilisée dans la fonction test_inside_a_func, donc vous pouvez 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 qui fonctionne toujours dans n'importe quelle situation, imitant la valeur de retour d'une fonction que vous pouvez trouver dans

3
répondu Luca Borrione 2017-05-23 11:55:05

Les options ont toutes été énumérées, je pense. Choisir 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. Dans 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 réel vie:

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 telles 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, l'état de retour est là pour vous d'utiliser quand vous en avez besoin, ou ignorer si vous ne le faites pas. la variable" returned " peut également être utilisée ou ignorée, mais bien sûr seulement après la fonction est appelée.

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.

Contrairement 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 quelle langue.

2
répondu Ron Burk 2015-05-05 00:19:23

Le problème clé de tout schéma 'named output variable' où l'appelant peut passer le nom de la variable (que ce soit en utilisant eval ou declare -n) est un aliasing involontaire, c'est-à-dire des conflits de noms: D'un point de vue d'encapsulation, il est horrible de ne pas pouvoir ajouter ou renommer une variable locale dans une fonction sans vérifier tous les 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 s'assurer que le paramètre de sortie, j'ai l'intention d'utiliser n'est pas un local dans cette fonction.)

Le seul moyen de contourner cela est d'utiliser une seule variable de sortie comme REPLY (comme suggéré par Evi1M4chine) ou d'une convention comme celle proposé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 cacher ce fait de l'appelant , comme je l'ai fait avec la fonction call dans l'exemple suivant. Considérez ceci comme une preuve de concept, mais les points clés sont

  • La fonction attribue toujours la valeur de retour de REPLY, et peut également renvoyer un code de sortie comme d'habitude
  • du point De vue de l'appelant, la valeur de retour peut être affectée à une variable (locale ou globale), y compris REPLY (voir la wrapper exemple). Le code de sortie de la fonction est passé à travers, donc en les utilisant par exemple dans un if ou while ou des constructions similaires fonctionnent comme prévu.
  • syntaxiquement, L'appel de fonction est toujours une seule instruction simple.

La raison pour laquelle cela fonctionne est que la fonction call elle-même n'a pas d'habitants et n'utilise Aucune variable autre que REPLY, évitant tout risque de conflit 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 fonction call), 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

Sortie:

a='hello' (0)
b='kia ora' (0)
c='' (123)
d='hello' (0)
2
répondu Karsten 2017-05-23 10:31:14

Dans mes programmes, par convention, c'est à quoi sert la variable $REPLY préexistante, que read utilise 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.

1
répondu Evi1M4chine 2016-01-05 16:33:57

Vous pouvez echo une chaîne, mais l'attraper en sifflant (|) la fonction à autre chose.

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

1
répondu apennebaker 2016-01-23 22:35:57

Bash modèle pour la retourner scalaires et matrice des objets de valeur:

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
}
0
répondu Andrei Pozolotin 2017-10-22 02:27:24
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=
-2
répondu agtsoft 2012-11-16 22:40:39