Cómo devolver un valor de cadena desde una función Bash

462

Me gustaría devolver una cadena de una función Bash.

Escribiré el ejemplo en Java para mostrar lo que me gustaría hacer:

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

String variable = getSomeString();

El siguiente ejemplo funciona en bash, pero ¿hay una mejor manera de hacerlo?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)
Tomás F
fuente
44
Por otro lado, function funcName {es la sintaxis heredada pre-POSIX heredada de ksh temprano (donde tenía diferencias semánticas que bash no respeta). funcName() {, con no function, debe usarse en su lugar; ver wiki.bash-hackers.org/scripting/obsolete
Charles Duffy
Ese enlace dice usar NAME () COMPOUND-CMD o la función NAME {CMDS; } Entonces function myFunction { blah; }está bien; es function myFunction() { blah }que no está bien, es decir, el uso de paréntesis con la función de palabra clave.
Será el

Respuestas:

290

No hay mejor manera que yo sepa. Bash solo conoce códigos de estado (enteros) y cadenas escritas en el stdout.

Philipp
fuente
15
+1 @ tomas-f: debe tener mucho cuidado con lo que tiene en esta función "getSomeString ()", ya que tener cualquier código que eventualmente haga eco significará que obtendrá una cadena de retorno incorrecta.
Mani
11
Esto esta simplemente mal. Puede devolver datos arbitrarios dentro de una variable de retorno. Que claramente es una mejor manera.
Evi1M4chine
36
@ Evi1M4chine, um ... no, no puedes. Puedes establecer una variable global y llamarla "retorno", como veo que haces en tus scripts. Pero eso es, por convención, NO ligado programáticamente a la ejecución de su código. "claramente una mejor manera"? Mmm no. La sustitución de comandos es mucho más explícita y modular.
Comodín el
66
"La sustitución de comandos es mucho más explícita y modular" sería relevante si la pregunta fuera sobre comandos; ¡Esta pregunta es cómo devolver una cadena, desde una función bash! Una forma integrada de hacer lo que ha pedido el OP está disponible desde Bash 4.3 (2014?) - vea mi respuesta a continuación.
zenaan
44
La pregunta original contiene la forma más sencilla de hacerlo, y funciona bien en la mayoría de los casos. Los valores de retorno de Bash probablemente deberían llamarse "códigos de retorno" porque son menos parecidos a los valores de retorno estándar en las secuencias de comandos y más como códigos de salida de comandos de shell numéricos (puede hacer cosas como somefunction && echo 'success'). Si piensa en una función como un comando más, tiene sentido; los comandos no "devuelven" nada a la salida que no sea un código de estado, pero pueden generar cosas mientras tanto que puede capturar.
Beejor
193

Puede hacer que la función tome una variable como el primer argumento y modifique la variable con la cadena que desea devolver.

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

Editar : se agregaron citas en el lugar apropiado para permitir espacios en blanco en la cadena para abordar el comentario de @Luca Borrione.

Editar : Como demostración, vea el siguiente programa. Esta es una solución de uso general: incluso le permite recibir una cadena en una variable local.

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

Esto 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

Editar : demostrando que el valor de la variable original está disponible en la función, como fue criticado incorrectamente por @Xichen Li en un comentario.

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

Esto da salida:

+ 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
fuente
1
¡Esta respuesta es genial! Los parámetros se pueden pasar por referencias, similar a la idea en C ++.
Yun Huang
44
Sería bueno recibir una respuesta de un experto sobre esa respuesta. Nunca he visto eso usado en scripts, quizás por una buena razón. De todos modos: eso es +1 Debería haber sido votado por la respuesta correcta
John
¿No es la misma fgmrespuesta escrita de manera simplificada? Esto no funcionará si la cadena foocontiene espacios en blanco, mientras que la fgmde uno sí ... como lo muestra.
Luca Borrione
44
@XichenLi: gracias por dejar un comentario con tu voto negativo; por favor vea mi edición Puede obtener el valor inicial de la variable en la función con \$$1. Si está buscando algo diferente, hágamelo saber.
bstpierre
1
@timiscoding Eso se puede solucionar con a printf '%q' "$var". % q es una cadena de formato para el escape de shell. Entonces solo pásalo crudo.
bb010g
99

Todas las respuestas anteriores ignoran lo que se ha indicado en la página de manual de bash.

  • Todas las variables declaradas dentro de una función se compartirán con el entorno de llamada.
  • No se compartirán todas las variables declaradas locales.

Código de ejemplo

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

Y salida

$ 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

¡También bajo pdksh y ksh este script hace lo mismo!

Vicky Ronnen
fuente
10
Esta respuesta tiene sus méritos. Entré aquí pensando que quería devolver una cadena de una función. Esta respuesta me hizo darme cuenta de que esa era solo mi conversación en C #. Sospecho que otros pueden tener la misma experiencia.
LOAS
44
@ElmarZander Estás equivocado, esto es completamente relevante. Esta es una manera simple de obtener un valor de alcance de función en el alcance global, y algunos lo considerarían mejor / más simple que el enfoque eval para redefinir una variable global como se describe en bstpierre.
KomodoDave
local no es portable a scripts que no sean bash, que es una razón por la que algunas personas lo evitan.
don brillante
Pregunta: ¿Qué pasa con las variables en bucles?
Anu
1
En un mac ($ bash --version GNU bash, versión 3.2.57 (1) -release (x86_64-apple-darwin14) Copyright (C) 2007 Free Software Foundation, Inc.), es correcto que una variable global coincidente sea inicializado, pero cuando trato de hacer efecto secundario en la misma variable en otra función f2, ese efecto secundario no persiste. Entonces, parece muy inconsistente y, por lo tanto, no es bueno para mi uso.
AnneTheAgile
45

Bash, desde la versión 4.3, febrero de 2014 (?), Tiene soporte explícito para variables de referencia o referencias de nombre (namerefs), más allá de "eval", con el mismo rendimiento beneficioso y efecto indirecto, y que puede ser más claro en sus scripts y también más difícil para "olvidar 'evaluar' y corregir este error":

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...

y también:

PARÁMETROS

A la variable se le puede asignar el atributo nameref utilizando la opción -n para los comandos locales declare o local (consulte las descripciones de declare y local a continuación) para crear un nameref o una referencia a otra variable. Esto permite que las variables sean manipuladas indirectamente. Cada vez que se hace referencia a la variable nameref o se le asigna, la operación se realiza realmente en la variable especificada por el valor de la variable nameref. Un nameref se usa comúnmente dentro de las funciones de shell para referirse a una variable cuyo nombre se pasa como argumento a⋅ la función. Por ejemplo, si se pasa un nombre de variable a una función de shell como primer argumento, ejecutar

      declare -n ref=$1

dentro de la función crea una variable nameref ref cuyo valor es el nombre de la variable pasado como primer argumento. Las referencias y asignaciones a ref se tratan como referencias y asignaciones a la variable cuyo nombre se pasó como $ 1. Si la variable de control en un bucle for tiene el atributo nameref, la lista de palabras puede ser una lista de variables de shell, y se establecerá una referencia de nombre para cada palabra en la lista, a su vez, cuando se ejecute el bucle. Las variables de matriz no pueden recibir el atributo -n. Sin embargo, las variables nameref pueden hacer referencia a variables de matriz y variables de matriz suscritas. Namerefs se puede ⋅ desarmar usando la opción -n para el desarmado incorporado. De lo contrario, si unset se ejecuta con el nombre de una variable nameref como argumento,

Por ejemplo ( EDIT 2 : (gracias, Ron), el espacio de nombres (prefijado) del nombre de la variable interna de la función, para minimizar los conflictos de variables externas, que finalmente deberían responder adecuadamente, el problema planteado en los comentarios 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
}

y probando este ejemplo:

$ return_a_string result; echo $result
The date is 20160817

Tenga en cuenta que el bash "declare" incorporado, cuando se usa en una función, hace que la variable declarada sea "local" por defecto, y "-n" también se puede usar con "local".

Prefiero distinguir las variables de "declaración importante" de las variables "locales aburridas", por lo que usar "declarar" y "local" de esta manera actúa como documentación.

EDITAR 1 - (Respuesta al comentario de Karsten a continuación) - No puedo agregar más comentarios a continuación, pero el comentario de Karsten me hizo pensar, así que hice la siguiente prueba que FUNCIONA BIEN, AFACIO - Karsten si lees esto, proporciona un conjunto exacto de pasos de prueba desde la línea de comando, que muestra el problema que usted supone que existe, porque estos pasos siguientes funcionan bien:

$ return_a_string ret; echo $ret
The date is 20170104

(Ejecuté esto justo ahora, después de pegar la función anterior en un término bash, como puede ver, el resultado funciona bien).

zenaan
fuente
44
Espero que esto se filtre a la cima. eval debería ser el último recurso. Vale la pena mencionar que las variables nameref solo están disponibles desde bash 4.3 (de acuerdo con el registro de cambios ) (publicado en febrero de 2014 [?]). Esto es importante si la portabilidad es una preocupación. Cite el manual de bash sobre el hecho de que declarecrea variables locales dentro de las funciones (esa información no está dada por help declare): "... Cuando se usa en una función, declare y componga que cada nombre sea local, como con el comando local, a menos que el - se proporciona la opción g ... "
init_js
2
Esto tiene el mismo problema de alias que la solución eval. Cuando llama a una función y pasa el nombre de la variable de salida, debe evitar pasar el nombre de una variable que se usa localmente dentro de la función que llama. Ese es un problema importante en términos de encapsulación, ya que no puede simplemente agregar o cambiar el nombre de nuevas variables locales en una función sin que cualquiera de las personas que llaman puedan querer usar ese nombre para el parámetro de salida.
Karsten
1
@Karsten estuvo de acuerdo. En ambos casos (eval y namerefs), puede que tenga que elegir un nombre diferente. Una ventaja con el enfoque nameref sobre eval es que uno no tiene que lidiar con cadenas de escape. Por supuesto, siempre puede hacer algo como K=$1; V=$2; eval "$A='$V'";, pero un error (por ejemplo, un parámetro vacío u omitido), y sería más peligroso. @zenaan el problema planteado por @Karsten se aplica si elige "mensaje" como el nombre de la variable de retorno, en lugar de "ret".
init_js
3
Presumiblemente, una función debe diseñarse desde el principio para aceptar un argumento nameref, por lo que el autor de la función debe ser consciente de la posibilidad de una colisión de nombres y puede usar alguna convención típica para evitarlo. Por ejemplo, dentro de la función X, nombre las variables locales con la convención "X_LOCAL_name".
Ron Burk
34

Al igual que bstpierre arriba, uso y recomiendo el uso de nombrar explícitamente variables de salida:

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
}

Tenga en cuenta el uso de citar los $. Esto evitará interpretar el contenido $resultcomo caracteres especiales de shell. He descubierto que este es un orden de magnitud más rápido que el result=$(some_func "arg1")idioma de capturar un eco. La diferencia de velocidad parece aún más notable usando bash en MSYS, donde la captura estándar de llamadas a funciones es casi catastrófica.

Está bien enviar variables locales ya que los locales se definen dinámicamente en bash:

function another_func() # ARG
{
   local result
   some_func result "$1"
   echo result is $result
}
Markarian451
fuente
44
Esto me ayuda porque me gusta usar múltiples declaraciones de eco para propósitos de depuración / registro. El modismo de capturar el eco falla ya que los captura a todos. ¡Gracias!
AnneTheAgile
¡Esta es la (segunda mejor) solución adecuada! Limpio, rápido, elegante, sensible.
Evi1M4chine
+2 por mantenerlo real. Estaba a punto de decir. ¡Cómo pueden tantas personas ignorar la combinación echode una función dentro de una función, combinada con la sustitución de comandos!
Anthony Rutledge
23

También puede capturar la salida de la función:

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

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

Parece extraño, pero es mejor que usar variables globales en mi humilde opinión. Pasar parámetros funciona como de costumbre, solo colóquelos dentro de los corchetes o puntos de control.

chiborg
fuente
11
Aparte de la nota de sintaxis alternativa, ¿no es exactamente lo mismo que el operador ya escribió en su propia pregunta?
Luca Borrione
muy claro, gracias!
bcag2
12

La solución más directa y robusta es usar la sustitución de comandos, como escribieron otras personas:

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

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

La desventaja es el rendimiento ya que esto requiere un proceso separado.

La otra técnica sugerida en este tema, es decir, pasar el nombre de una variable para asignar como argumento, tiene efectos secundarios, y no la recomendaría en su forma básica. El problema es que probablemente necesitará algunas variables en la función para calcular el valor de retorno, y puede suceder que el nombre de la variable destinada a almacenar el valor de retorno interfiera con una de ellas:

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

Por supuesto, es posible que no declare las variables internas de la función como locales, pero realmente siempre debe hacerlo, de lo contrario, puede, por otro lado, sobrescribir accidentalmente una variable no relacionada del ámbito primario si hay una con el mismo nombre .

Una posible solución es una declaración explícita de la variable pasada como global:

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

Si se pasa el nombre "x" como argumento, la segunda fila del cuerpo de la función sobrescribirá la declaración local anterior. Pero los nombres mismos pueden interferir, por lo que si tiene la intención de usar el valor previamente almacenado en la variable pasada antes de escribir el valor de retorno allí, tenga en cuenta que debe copiarlo en otra variable local desde el principio; De lo contrario, el resultado será impredecible. Además, esto solo funcionará en la versión más reciente de BASH, es decir, 4.2. Un código más portátil podría utilizar construcciones condicionales explícitas con el mismo efecto:

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

Quizás la solución más elegante es simplemente reservar un nombre global para los valores de retorno de la función y usarlo de manera consistente en cada función que escriba.

Tomasz Żuk
fuente
3
Esto ^^^. El alias inadvertido que rompe la encapsulación es el gran problema con las soluciones evaly declare -n. La solución de tener un único nombre de variable dedicado como resultpara todos los parámetros de salida parece ser la única solución que no requiere una función para conocer a todas las personas que llaman para evitar conflictos.
Karsten
12

Como se mencionó anteriormente, la forma "correcta" de devolver una cadena de una función es mediante la sustitución de comandos. En el caso de que la función también necesite salir a la consola (como se menciona anteriormente en @Mani), cree un fd temporal al comienzo de la función y redirija a la consola. Cierre el fd temporal antes de devolver su cadena.

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

ejecutar script sin parámetros produce ...

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

espero que esto ayude a las personas

-Andy

Andy
fuente
66
Eso tiene sus usos, pero en general debe evitar hacer una redirección explícita a la consola; el resultado ya puede ser redirigido, o el script puede estar ejecutándose en un contexto donde no existe tty. Puede evitar eso duplicando 3>&1el encabezado del script, luego manipulando &1 &3y otro marcador de posición &4dentro de la función. Feo en general, sin embargo.
jmb
8

Podría usar una variable global:

declare globalvar='some string'

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

string globalvar

echo "'${globalvar}'"

Esto da

'some other string'
Fritz G. Mehner
fuente
6

Para ilustrar mi comentario sobre la respuesta de Andy, con manipulación adicional del descriptor de archivo para evitar el uso 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]"

Todavía desagradable, sin embargo.

jmb
fuente
3

La forma en que lo tiene es la única forma de hacerlo sin romper el alcance. Bash no tiene un concepto de tipos de retorno, solo códigos de salida y descriptores de archivo (stdin / out / err, etc.)

Daenyth
fuente
3

Abordar la cabeza de Vicky Ronnen , considerando el siguiente código:

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



daré

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

Tal vez el escenario normal es usar la sintaxis utilizada en la test_inside_a_funcfunción, por lo que puede usar ambos métodos en la mayoría de los casos, aunque capturar la salida es el método más seguro que siempre funciona en cualquier situación, imitando el valor de retorno de una función que puede encontrar en otros idiomas, como Vicky Ronnense señaló correctamente.

Luca Borrione
fuente
2

Las opciones se han enumerado todas, creo. Elegir uno puede reducirse a una cuestión del mejor estilo para su aplicación particular, y en ese sentido, quiero ofrecer un estilo particular que he encontrado útil. En bash, las variables y las funciones no están en el mismo espacio de nombres. Entonces, tratar la variable del mismo nombre como el valor de la función es una convención que encuentro minimiza los conflictos de nombres y mejora la legibilidad, si la aplico rigurosamente. Un ejemplo de la vida real:

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"
}

Y, un ejemplo del uso de tales funciones:

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

Como puede ver, el estado de devolución está ahí para que lo use cuando lo necesite, o ignore si no lo necesita. La variable "devuelta" también se puede usar o ignorar, pero, por supuesto, solo después de invocar la función.

Por supuesto, esto es solo una convención. Usted es libre de no establecer el valor asociado antes de regresar (de ahí mi convención de anularlo siempre al comienzo de la función) o de pisotear su valor llamando a la función nuevamente (posiblemente indirectamente). Aún así, es una convención que encuentro muy útil si me encuentro haciendo un uso intensivo de las funciones bash.

A diferencia del sentimiento de que esta es una señal, uno debería, por ejemplo, "pasar a Perl", mi filosofía es que las convenciones siempre son importantes para manejar la complejidad de cualquier idioma.

Ron Burk
fuente
2

Puede hacer echouna cadena, pero atraparla canalizando ( |) la función a otra cosa.

Puede hacerlo expr, aunque ShellCheck informa que este uso está en desuso.

Apennebaker
fuente
El problema es que lo que está a la derecha de la tubería es un subshell. Entonces myfunc | read OUTPUT ; echo $OUTPUTno produce nada. myfunc | ( read OUTPUT; echo $OUTPUT )obtiene el valor esperado y aclara lo que está sucediendo en el lado derecho. Pero, por supuesto, OUTPUT no está disponible donde lo necesitas ...
Ed Randall
2

El problema clave de cualquier esquema de 'variable de salida con nombre' donde la persona que llama puede pasar el nombre de la variable (ya sea usando evalo declare -n) es un alias involuntario, es decir, conflictos de nombres: desde el punto de vista de la encapsulación, es horrible no poder agregar o renombrar una variable local en una función sin verificar TODAS las llamadas de la función primero para asegurarse de que no quieran pasar el mismo nombre que el parámetro de salida. (O en la otra dirección, no quiero tener que leer la fuente de la función que estoy llamando solo para asegurarme de que el parámetro de salida que pretendo usar no sea un local en esa función).

La única forma REPLYde evitarlo es usar una única variable de salida dedicada como (como lo sugiere Evi1M4chine ) o una convención como la sugerida por Ron Burk .

Sin embargo, es posible que las funciones utilicen una variable de salida fija internamente y luego agreguen algo de azúcar por encima para ocultar este hecho a la persona que llama , como he hecho con la callfunción en el siguiente ejemplo. Considere esto como una prueba de concepto, pero los puntos clave son

  • La función siempre asigna el valor de retorno a REPLY, y también puede devolver un código de salida como de costumbre
  • Desde la perspectiva de la persona que llama, el valor de retorno puede asignarse a cualquier variable (local o global) incluida REPLY(ver el wrapperejemplo). El código de salida de la función se pasa a través, por lo que su uso en, por ejemplo una ifo whileo construcciones similares funciona como se espera.
  • Sintácticamente, la llamada a la función sigue siendo una sola declaración simple.

La razón por la que esto funciona es porque la callfunción en sí no tiene locales y no utiliza otras variables que no sean REPLY, evitando posibles conflictos de nombres. En el punto donde se asigna el nombre de la variable de salida definida por el llamador, estamos efectivamente en el alcance del llamador (técnicamente en el alcance idéntico de la callfunción), en lugar del alcance de la función que se llama.

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

Salida:

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

patrón bash para devolver objetos de valor escalar y de matriz :

definición

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
}

invocación

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
fuente
0

En mis programas, por convención, esto es para lo que sirve la $REPLYvariable preexistente , que se readusa para ese propósito exacto.

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

Esto echoes

tadaa

Pero para evitar conflictos, cualquier otra variable global servirá.

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

Si eso no es suficiente, recomiendo la solución de Markarian451 .

Evi1M4chine
fuente
-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
fuente