2010-03-21 5 views
16

Existe-t-il des idiomes pour renvoyer plusieurs valeurs d'une fonction bash dans un script?idiomes pour renvoyer plusieurs valeurs dans le script shell

http://tldp.org/LDP/abs/html/assortedtips.html décrit comment écho plusieurs valeurs et traiter les résultats (par exemple, par exemple 35-17), mais cela devient délicat si certaines des valeurs retournées sont des chaînes avec des espaces.

Une façon de revenir plus structuré serait d'attribuer à des variables globales, comme

foo() { 
    FOO_RV1="bob" 
    FOO_RV2="bill" 
} 

foo 
echo "foo returned ${FOO_RV1} and ${FOO_RV2}" 

Je me rends compte que si je dois réentrance dans un script shell, je suis probablement le faire mal, mais je me sens encore très mal à l'aise de lancer des variables globales autour juste pour tenir renvoyer des valeurs.

Y at-il une meilleure façon? Je préférerais la portabilité, mais ce n'est probablement pas une vraie limitation si je dois spécifier #!/bin/bash.

Répondre

6

Bien que je l'aime shell, il est probablement le cas que dès que vous jetez des données structurées arbitraires autour, bourne Unix/posix shell n'est pas le bon choix.

S'il existe des caractères qui n'apparaissent pas dans les champs, séparez-les avec l'un de ceux-ci. L'exemple classique est /etc/passwd, /etc/group et divers autres fichiers qui utilisent un signe deux-points comme séparateur de champs.

Si vous utilisez un interpréteur de commandes qui peut gérer un caractère NUL à l'intérieur des chaînes, le fait de rejoindre le NUL et de le séparer (via $ IFS ou autre) peut bien fonctionner. Mais plusieurs coquilles communes, y compris bash, se cassent sur NUL. Un test serait un vieux sig de la mine:

foo=$'a\0b'; [ ${#foo} -eq 3 ] && echo "$0 rocks" 

Même si cela fonctionne pour vous, vous venez de joindre l'un des signes avant-coureurs qu'il est temps de passer à un langage plus structuré (Python, Perl , Ruby, Lua, Javascript ... choisissez votre poison préféré). Votre code est susceptible de devenir difficile à maintenir; Même si vous le pouvez, il y a un plus petit bassin de personnes qui le comprendront assez bien pour le maintenir.

0

Les fonctions de script Shell peuvent uniquement renvoyer l'état de sortie de la dernière commande exécutée ou le statut de sortie de cette fonction spécifié explicitement par une instruction return.

Pour revenir une chaîne de caractères d'une façon peut-être ceci:

function fun() 
{ 
    echo "a+b" 
} 

var=`fun` # Invoke the function in a new child shell and capture the results 
echo $var # use the stored result 

Cela peut réduire votre inconfort bien qu'il ajoute les frais généraux de la création d'une nouvelle coquille et serait donc légèrement plus lent.

1

vous pouvez utiliser des tableaux associatifs avec vous ont bash 4 par exemple

declare -A ARR 
function foo(){ 
    ... 
    ARR["foo_return_value_1"]="VAR1" 
    ARR["foo_return_value_2"]="VAR2" 
} 

vous pouvez les combiner sous forme de chaînes.

function foo(){ 
    ... 
    echo "$var1|$var2|$var3" 
} 

alors chaque fois que vous devez utiliser les valeurs de retour,

ret="$(foo)" 
IFS="|" 
set -- $ret 
echo "var1 one is: $1" 
echo "var2 one is: $2" 
echo "var3 one is: $3" 
1

Je voudrais aller à la solution I suggested here, mais en utilisant une variable de tableau à la place. Les anciens bash: es ne supportent pas les tableaux associatifs. E.g.,

function some_func() # ARRVAR args... 
{ 
    local _retvar=$1 # I use underscore to avoid clashes with return variable names 
    local -a _out 
    # ... some processing ... (_out[2]=xxx etc.) 
    eval $_retvar='("${_out[@]}")' 
} 

Site d'appel:

function caller() 
{ 
    local -a tuple_ret # Do not use leading '_' here. 
    # ... 
    some_func tuple_ret "arg1" 
    printf " %s\n" "${tuple_ret[@]}" # Print tuple members on separate lines 
} 
5

Cette question a été publiée il y a 5 ans, mais j'ai une réponse intéressante. J'ai moi-même commencé à apprendre bash, et je rencontre aussi le même problème que vous. Je pense que cette astuce pourrait être utile:

#!/bin/sh 

foo="" 
bar="" 

my_func(){ 
    echo 'foo="a"; bar="b"' 
} 

print_result(){ 
    echo $1 $2 
} 

eval $(my_func) 
print_result $foo $bar 
# result: a b 

Dans le cas où vous ne souhaitez pas utiliser les variables globales trop, essayez ceci:

#!/bin/sh 

my_func(){ 
    echo 'print_it "a" "b"' 
} 

print_result(){ 
    echo $1 $2 
} 

eval $(my_func) 
# result: a b 

Cette astuce est également utile pour résoudre un problème un processus enfant ne peut pas renvoyer de valeur à une variable globale d'un processus parent, par exemple

#!/bin/sh 

msg="" #global variable 
stat="" 

say_hello(){ 
    msg="hello" # doesn't work at all! 
    echo "success" 
} 

output=$(say_hello) # child process $(...) return "success" 

par résoudre:

#!/bin/sh 

msg="" #global variable 
stat="" 

say_hello(){ 
    echo 'msg="hello"; stat"success"' 
} 

eval $(say_hello) # this line evaluates to msg="hello"; stat"success" 
1

version ultérieure de Bash supporte nameref. Utilisez declare -n var_name pour donner var_name l'attribut nameref. nameref donne à votre fonction la possibilité de "passer par référence" qui est couramment utilisé dans les fonctions C++ pour renvoyer plusieurs valeurs. Selon la page man Bash:

Une variable peut être attribué le nameref attribut en utilisant l'option -n aux déclare ou commandes locales BUILTIN pour créer un nameref, 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 en réalité 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.

Voici quelques exemples de lignes de commande interactives.

Exemple 1:

$ unset xx yy 
$ xx=16 
$ yy=xx 
$ echo "[$xx] [$yy]" 
[16] [xx] 
$ declare -n yy 
$ echo "[$xx] [$yy]" 
[16] [16] 
$ xx=80 
$ echo "[$xx] [$yy]" 
[80] [80] 
$ yy=2016 
$ echo "[$xx] [$yy]" 
[2016] [2016] 
$ declare +n yy # Use -n to add and +n to remove nameref attribute. 
$ echo "[$xx] [$yy]" 
[2016] [xx] 

Exemple 2:

$ func() 
> { 
>  local arg1="$1" arg2="$2" 
>  local -n arg3ref="$3" arg4ref="$4" 
> 
>  echo '' 
>  echo 'Local variables:' 
>  echo " arg1='$arg1'" 
>  echo " arg2='$arg2'" 
>  echo " arg3ref='$arg3ref'" 
>  echo " arg4ref='$arg4ref'" 
>  echo '' 
> 
>  arg1='1st value of local assignment' 
>  arg2='2st value of local assignment' 
>  arg3ref='1st return value' 
>  arg4ref='2nd return value' 
> } 
$ 
$ unset foo bar baz qux 
$ 
$ foo='value of foo' 
$ bar='value of bar' 
$ baz='value of baz' 
$ qux='value of qux' 
$ 
$ func foo bar baz qux 

Local variables: 
    arg1='foo' 
    arg2='bar' 
    arg3ref='value of baz' 
    arg4ref='value of qux' 

$ 
$ { 
>  echo '' 
>  echo '2 values are returned after the function call:' 
>  echo " foo='$foo'" 
>  echo " bar='$bar'" 
>  echo " baz='$baz'" 
>  echo " qux='$qux'" 
> } 

2 values are returned after the function call: 
    foo='value of foo' 
    bar='value of bar' 
    baz='1st return value' 
    qux='2nd return value' 
1

Dans la version de la commande de Bash qui ne supporte pas nameref (introduit en bash 4,3-alpha) I peut définir une fonction d'assistance dans laquelle la valeur de retour est affectée à la variable donnée. C'est un peu comme utiliser eval pour faire le même type d'assignation de variable.

Exemple 1

## Add two complex numbers and returns it. 
## re: real part, im: imaginary part. 
## 
## Helper function named by the 5th positional parameter 
## have to have been defined before the function is called. 
complexAdd() 
{ 
    local re1="$1" im1="$2" re2="$3" im2="$4" fnName="$5" sumRe sumIm 

    sumRe=$(($re1 + $re2)) 
    sumIm=$(($im1 + $im2)) 

    ## Call the function and return 2 values. 
    "$fnName" "$sumRe" "$sumIm" 
} 

main() 
{ 
    local fooRe='101' fooIm='37' barRe='55' barIm='123' bazRe bazIm quxRe quxIm 

    ## Define the function to receive mutiple return values 
    ## before calling complexAdd(). 
    retValAssign() { bazRe="$1"; bazIm="$2"; } 
    ## Call comlexAdd() for the first time. 
    complexAdd "$fooRe" "$fooIm" "$barRe" "$barIm" 'retValAssign' 

    ## Redefine the function to receive mutiple return values. 
    retValAssign() { quxRe="$1"; quxIm="$2"; } 
    ## Call comlexAdd() for the second time. 
    complexAdd "$barRe" "$barIm" "$bazRe" "$bazIm" 'retValAssign' 

    echo "foo = $fooRe + $fooIm i" 
    echo "bar = $barRe + $barIm i" 
    echo "baz = foo + bar = $bazRe + $bazIm i" 
    echo "qux = bar + baz = $quxRe + $quxIm i" 
} 

main 

Exemple 2

## Add two complex numbers and returns it. 
## re: real part, im: imaginary part. 
## 
## Helper functions 
##  getRetRe(), getRetIm(), setRetRe() and setRetIm() 
## have to have been defined before the function is called. 
complexAdd() 
{ 
    local re1="$1" im1="$2" re2="$3" im2="$4" 

    setRetRe "$re1" 
    setRetRe $(($(getRetRe) + $re2)) 

    setRetIm $(($im1 + $im2)) 
} 

main() 
{ 
    local fooRe='101' fooIm='37' barRe='55' barIm='123' bazRe bazIm quxRe quxIm 

    ## Define getter and setter functions before calling complexAdd(). 
    getRetRe() { echo "$bazRe"; } 
    getRetIm() { echo "$bazIm"; } 
    setRetRe() { bazRe="$1"; } 
    setRetIm() { bazIm="$1"; } 
    ## Call comlexAdd() for the first time. 
    complexAdd "$fooRe" "$fooIm" "$barRe" "$barIm" 

    ## Redefine getter and setter functions. 
    getRetRe() { echo "$quxRe"; } 
    getRetIm() { echo "$quxIm"; } 
    setRetRe() { quxRe="$1"; } 
    setRetIm() { quxIm="$1"; } 
    ## Call comlexAdd() for the second time. 
    complexAdd "$barRe" "$barIm" "$bazRe" "$bazIm" 

    echo "foo = $fooRe + $fooIm i" 
    echo "bar = $barRe + $barIm i" 
    echo "baz = foo + bar = $bazRe + $bazIm i" 
    echo "qux = bar + baz = $quxRe + $quxIm i" 
} 

main 
5

Dans le cas particulier où vos valeurs ne contiennent jamais des espaces, cette astuce read peut être une solution simple:

get_vars() { 
    #... 
    echo "value1" "value2" 
} 

read var1 var2 < <(get_vars) 
echo "var1='$var1', var2='$var2'" 

Mais bien sûr, il se casse dès qu'il y a un espace dans l'une des valeurs. Vous pouvez modifier IFS et utiliser un séparateur spécial dans la fonction echo de votre fonction, mais le résultat n'est pas vraiment plus simple que les autres solutions suggérées.

1

Encore une autre façon:

function get_tuple() 
{ 
    echo -e "Value1\nValue2" 
} 

IFS=$'\n' read -d '' -ra VALUES < <(get_tuple) 
echo "${VALUES[0]}" # Value1 
echo "${VALUES[1]}" # Value2 
Questions connexes