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


Answers

Puede hacer que la función tome una variable como la primera arg 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 agregó una cita en el lugar apropiado para permitir que los espacios en blanco de la cadena respondan al comentario de @Luca Borrione.

Editar : como demostración, vea el siguiente programa. Esta es una solución de propósito 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 : demuestra que el valor de la variable original está disponible en la función, como criticó incorrectamente @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
Question

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)



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=



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. Al pasar los parámetros funciona como de costumbre, simplemente colóquelos dentro de las llaves o palos de atrás.




La solución más directa y robusta es usar sustitución de comando, 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 lo 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

Puede, por supuesto, no declarar las variables internas de la función como locales, pero en realidad siempre debería hacerlo, de lo contrario, puede, por otro lado, sobrescribir accidentalmente una variable no relacionada desde el alcance principal si hay una con el mismo nombre .

Una solución posible 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 en sí mismos aún pueden interferir, por lo tanto, si tiene la intención de utilizar 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, a saber, 4.2. Más código 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 sea solo reservar un nombre global para los valores de retorno de función y usarlo consistentemente en cada función que escriba.




Para ilustrar mi comentario sobre la respuesta de Andy, con la 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]"

Aunque desagradable, sin embargo.




En mis programas, por convención, esto es para lo que es la variable $REPLY preexistente, que read usos para ese propósito exacto.

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

Este echo es

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 .




Dirigiéndose a la cabeza de Vicky Ronnen , teniendo en cuenta 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 función test_inside_a_func , por lo tanto 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 usted puede encontrar en otros idiomas, como señaló correctamente Vicky Ronnen .




El problema clave de cualquier esquema de 'variable de salida nombrada' donde la persona que llama puede pasar el nombre de la variable (ya sea utilizando eval o declare -n ) es un alias inadvertido, es decir, conflictos de nombres: desde un punto de vista de encapsulación, es horrible no poder para 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 como parámetro de salida. (O en la otra dirección, no quiero tener que leer el origen de la función que estoy llamando solo para asegurarme de que el parámetro de salida que pretendo usar no sea local en esa función).

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

Sin embargo, es posible que las funciones usen una variable de salida fija internamente , y luego agregue un poco de azúcar sobre la parte superior para ocultar este hecho a la persona que llama , como lo hice con la función de call en el siguiente ejemplo. Considere esto 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 (consulte el ejemplo de wrapper ). El código de salida de la función se transfiere, por lo que usarlos en, por ejemplo, un if o while o construcciones similares funciona como se espera.
  • Sintácticamente, la llamada a la función sigue siendo una simple declaración simple.

La razón por la que esto funciona es porque la función de call sí no tiene locales y no usa otras variables que no sean REPLY , evitando cualquier posibilidad de conflictos de nombres. En el punto donde se asigna el nombre de la variable de salida definida por el que llama, estamos efectivamente en el alcance del llamante (técnicamente en el ámbito idéntico de la función de call ), más que en el alcance de la función a la 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)



Como bstpierre arriba, uso y recomiendo el uso de nombrar explícitamente las 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 contenido en $result como caracteres especiales de shell. He encontrado que este es un orden de magnitud más rápido que el result=$(some_func "arg1") modismo de la captura de un eco. La diferencia de velocidad parece aún más notable utilizando bash en MSYS donde la captura de stdout de llamadas a funciones es casi catastrófica.

Está bien enviar variables locales ya que los locals tienen un alcance dinámico en bash:

function another_func() # ARG
{
   local result
   some_func result "$1"
   echo result is $result
}





Related