2010-03-21 21 views
16

¿Existen expresiones idiomáticas para devolver múltiples valores de una función bash dentro de un script?modismos para devolver múltiples valores en el shell scripting

http://tldp.org/LDP/abs/html/assortedtips.html describe cómo echo múltiples valores y procesar los resultados (por ejemplo, el ejemplo 35-17), pero que se complica si algunos de los valores devueltos son cadenas con espacios en.

una manera más estructurada para volver sería asignar a las variables globales, como

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

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

soy consciente de que si necesito re-entrada en un script de shell probablemente estoy haciendo mal, pero todavía me siento muy incómodo tirar variables globales alrededor sólo para mantener valores de retorno.

¿Hay una manera mejor? Preferiría la portabilidad, pero probablemente no sea una limitación real si tengo que especificar #!/bin/bash.

Respuesta

6

Por mucho que me encanta la cáscara, que es probablemente el caso de que tan pronto como usted está lanzando datos estructurados arbitrarias alrededor, Bourne de Unix/Shell de posix no es la elección correcta.

Si hay caracteres que no aparecen dentro de los campos, sepárelos con uno de ellos. El ejemplo clásico es /etc/passwd, /etc/group y varios otros archivos que usan dos puntos como separador de campo.

Si usa un shell que pueda manejar un carácter NUL dentro de cadenas, entonces unir el NUL y separarlo (a través de $ IFS o lo que sea) puede funcionar bien. Pero varios proyectiles comunes, incluido bash, se rompen en NUL. Una prueba sería un viejo .sig mío:

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

Incluso si eso fuera a trabajar para usted, usted acaba de llegar a una de las señales de advertencia de que es hora de cambiar a un lenguaje más estructurado (Python, Perl , Ruby, Lua, Javascript ... elige tu veneno preferido). Es probable que su código sea difícil de mantener; incluso si puede, hay un grupo más pequeño de personas que lo entenderá lo suficientemente bien como para mantenerlo.

0

Las funciones de script de shell solo pueden devolver el estado de salida del último comando ejecutado o el estado de salida de esa función especificado explícitamente por una instrucción return.

Para regresar un poco de hilo de una forma puede ser la siguiente:

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 

Esto puede reducir la molestia aunque añade la sobrecarga de la creación de un nuevo shell y por lo tanto sería marginalmente más lento.

1

se puede hacer uso de matrices asociativas con el que tienen fiesta de 4 por ejemplo

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

puede combinarlos como cadenas.

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

entonces cada vez que necesita usar esos valores de retorno,

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

Iría por la solución I suggested here, pero usando una variable de matriz en su lugar. Viejos bash: es no son compatibles con matrices asociativas. P. ej.,

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[@]}")' 
} 

sitio de llamada:

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

Esta pregunta fue publicada hace 5 años, pero tienen alguna respuesta interesante. Yo mismo acabo de empezar a aprender bash, y también me encuentro con el mismo problema que tú. Creo que este truco podría ser útil:

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

En caso de que usted no desea utilizar demasiado variables globales, intente esto:

#!/bin/sh 

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

print_result(){ 
    echo $1 $2 
} 

eval $(my_func) 
# result: a b 

Este truco también es útil para resolver un problema que un proceso hijo no puede devolver el valor a una variable global de un proceso principal, por ejemplo

#!/bin/sh 

msg="" #global variable 
stat="" 

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

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

resolver por:

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

versión posterior de Bash apoya nameref. Utilice declare -n var_name para dar var_name la nameref atributo. nameref da a su función de la capacidad de "pasar por referencia" que se utiliza comúnmente en las funciones de C++ para devolver múltiples valores. De acuerdo con la página de manual de bash:

Una variable se puede asignar la nameref atributo mediante la opción -n a los declaran o órdenes internas locales para crear un nameref, o una referencia a otra variable. Esto permite que las variables sean manipuladas indirectamente. Siempre que la variable nameref se hace referencia o se asimilan en la operación se realiza realmente en la variable indicada por el valor nameref de variable. Un nameref se usa comúnmente dentro de las funciones de shell para hacer referencia a una variable cuyo nombre se pasa como argumento a la función.

Los siguientes son algunos ejemplos de línea de comando interactivo.

Ejemplo 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] 

Ejemplo 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

En la versión orden de Bash que no soporta nameref (introducido en Bash 4,3-alfa) I puede definir la función auxiliar en la que el valor de retorno se asigna a la variable dada. Es como usar eval para hacer el mismo tipo de asignación de variable.

Ejemplo 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 

Ejemplo 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

En el caso especial donde sus valores nunca contienen espacios, este read truco puede ser una solución simple:

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

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

Pero, por supuesto, se rompe tan pronto como hay un espacio en uno de los valores. Puede modificar IFS y usar un separador especial en el echo de su función, pero luego el resultado no es realmente más simple que las otras soluciones sugeridas.

1

embargo, otra forma:

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

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