Valor de retorno en una función Bash

305

Estoy trabajando con un script bash y quiero ejecutar una función para imprimir un valor de retorno:

function fun1(){
  return 34
}
function fun2(){
  local res=$(fun1)
  echo $res
}

Cuando ejecuto fun2, no imprime "34". ¿Por qué es este el caso?

mindia
fuente
8
returnen su caso es esencialmente el mismo exit codeque el rango de 0 - 255. Use echosegún lo sugerido por @septi. Los códigos de salida se pueden capturar con $?.
devnull
1
En este caso, es mucho más flexible usar ya echo en fun1. Es la idea de la programación unix: echo envía los resultados a la salida estándar que luego pueden ser reutilizados por otras funciones con res = $ (fun1) - o directamente canalizadas a otras funciones:function a() { echo 34; } function b() { while read data; do echo $data ; done ;} a | b
Arne Babenhauserheide
La forma correcta de hacer esto es poner las cosas de nivel superior en una función y usar un local con la regla de alcance dinámico de bash. Crearé una respuesta para demostrar, no es una característica conocida, pero es totalmente compatible.
Oliver
Ver también: stackoverflow.com/a/8743103/12887
Jonathan Tran

Respuestas:

374

Aunque bash tiene una returndeclaración, lo único que puede especificar con ella es el exitestado propio de la función (un valor entre 0y 255, 0 significa "éxito"). Entonces returnno es lo que quieres.

Es posible que desee convertir su returndeclaración en una echodeclaración, de esa manera la salida de su función podría capturarse usando $()llaves, que parece ser exactamente lo que desea.

Aquí hay un ejemplo:

function fun1(){
  echo 34
}

function fun2(){
  local res=$(fun1)
  echo $res
}

Otra forma de obtener el valor de retorno (si solo desea devolver un entero 0-255) es $?.

function fun1(){
  return 34
}

function fun2(){
  fun1
  local res=$?
  echo $res
}

Además, tenga en cuenta que puede usar el valor de retorno para usar la lógica booleana, fun1 || fun2ya que solo se ejecutará fun2si fun1devuelve un 0valor. El valor de retorno predeterminado es el valor de salida de la última instrucción ejecutada dentro de la función.

tamasgal
fuente
2
Debe ejecutar fun1y luego el valor de retorno se almacena en $?. Aunque no recomendaría hacer eso ...
tamasgal
99
¿Por qué no usar $??
Pithikos
147
No, necesito el maldito valor de retorno . Al diablo con eco.
Tomáš Zato - Restablece a Mónica el
77
@Blauhirn en este entorno, con esta ||construcción, un código de salida de 0 se considera exitoso y, por lo tanto, "verdadero". No cero es error y, por lo tanto, falso. Piense fun1 || fun2en la abreviatura de "si fun1 devuelve el éxito o fun2 devuelve el éxito" en lugar de un operador en los valores de retorno reales.
davidA
66
Lo que es molesto es que una función que debe proporcionar datos no puede hacer eco de otras cosas para stdout, porque la persona que llama usando $ () también lo recibirá y se confundirá o tendrá que analizar la salida. Las variables globales no son geniales porque es solo cuestión de tiempo antes de usar la misma var global en dos lugares que están anidados y los datos podrían perderse. Debería haber canales separados para imprimir datos versus enviarlos de vuelta.
Oliver
68

$(...)captura el texto enviado a stdout por el comando que contiene. returnno sale a stdout. $?contiene el código de resultado del último comando.

fun1 (){
  return 34
}

fun2 (){
  fun1
  local res=$?
  echo $res
}
Ignacio Vazquez-Abrams
fuente
66
returnse utiliza para establecer $?cuál es el exit status. En el ejemplo anterior, fun1's exit statussería 34. Además, tenga en cuenta que $(...)también captura stderr además de stdout del comando especificado.
swoop81
59

Las funciones en Bash no son funciones como en otro idioma; en realidad son comandos. Por lo tanto, las funciones se usan como si fueran binarios o scripts extraídos de su ruta. Desde la perspectiva de la lógica de su programa, realmente no debería haber diferencia.

Los comandos de Shell están conectados por canalizaciones (también conocidos como flujos) y no por tipos de datos fundamentales o definidos por el usuario, como en los lenguajes de programación "reales". No existe un valor de retorno para un comando, tal vez principalmente porque no hay una forma real de declararlo. Podría ocurrir en la página de manual o en la --helpsalida del comando, pero ambos son legibles por humanos y, por lo tanto, están escritos al viento.

Cuando un comando quiere obtener una entrada, la lee de su flujo de entrada o de la lista de argumentos. En ambos casos, las cadenas de texto deben analizarse.

Cuando un comando quiere devolver algo, debe echohacerlo en su flujo de salida. Otra forma practicada a menudo es almacenar el valor de retorno en variables dedicadas y globales. Escribir en la secuencia de salida es más claro y más flexible, porque también puede tomar datos binarios. Por ejemplo, puede devolver un BLOB fácilmente:

encrypt() {
    gpg -c -o- $1 # encrypt data in filename to stdout (asks for a passphrase)
}

encrypt public.dat > private.dat # write function result to file

Como otros han escrito en este hilo, la persona que llama también puede usar la sustitución de comandos $()para capturar la salida.

Paralelamente, la función "devolvería" el código de salida de gpg(GnuPG). Piense en el código de salida como un bono que otros idiomas no tienen o, dependiendo de su temperamento, como un "Schmutzeffekt" de funciones de shell. Este estado es, por convención, 0 en caso de éxito o un número entero en el rango 1-255 para otra cosa. Para aclarar esto: return(like exit) solo puede tomar un valor de 0-255, y los valores distintos de 0 no son necesariamente errores, como se afirma a menudo.

Cuando no proporciona un valor explícito, returnel estado se toma del último comando en una declaración / función / comando Bash, etc. Por lo tanto, siempre hay un estado y returnes solo una forma fácil de proporcionarlo.

Andreas Spindler
fuente
44
+1 para explicar las funciones frente a los comandos y cómo esto afecta la noción de enviar datos de regreso a la persona que llama
Oliver
44
+1 para explicar que la programación de shell se trata de conectar comandos a través de tuberías. Otros lenguajes de programación componen funciones mediante tipos de retorno. Bash compone comandos a través de secuencias de texto.
jrahhali
29

La returninstrucción establece el código de salida de la función, muy similar a lo exitque hará para todo el script.

El código de salida para el último comando siempre está disponible en la $?variable.

function fun1(){
  return 34
}

function fun2(){
  local res=$(fun1)
  echo $? # <-- Always echos 0 since the 'local' command passes.

  res=$(fun1)
  echo $?  #<-- Outputs 34
}
Austin Phillips
fuente
21

El problema con otras respuestas es que usan un global, que puede sobrescribirse cuando hay varias funciones en una cadena de llamadas, o echoque su función no puede generar información de diagnóstico (olvidará que su función hace esto y el "resultado", es decir, devuelve valor, contendrá más información de la que espera la persona que llama, lo que provocará un error extraño), o evalque es demasiado pesado y hacky.

La forma correcta de hacer esto es poner las cosas de nivel superior en una función y usar una localregla de alcance dinámico de bash. Ejemplo:

func1() 
{
    ret_val=hi
}

func2()
{
    ret_val=bye
}

func3()
{
    local ret_val=nothing
    echo $ret_val
    func1
    echo $ret_val
    func2
    echo $ret_val
}

func3

Esto salidas

nothing
hi
bye

¡El alcance dinámico significa que ret_valapunta a un objeto diferente dependiendo de la persona que llama! Esto es diferente del alcance léxico, que es lo que usan la mayoría de los lenguajes de programación. Esta es en realidad una característica documentada , fácil de pasar por alto, y no muy bien explicada, aquí está la documentación para ello (el énfasis es mío):

Las variables locales a la función pueden declararse con el local incorporado. Estas variables son visibles solo para la función y los comandos que invoca .

Para alguien con un fondo C / C ++ / Python / Java / C # / javascript, este es probablemente el mayor obstáculo: las funciones en bash no son funciones, son comandos y se comportan como tales: pueden generar stdout/ stderrpueden conectarse / out, pueden devolver un código de salida. Básicamente no hay diferencia entre definir un comando en un script y crear un ejecutable al que se pueda llamar desde la línea de comando.

Entonces, en lugar de escribir su script de esta manera:

top-level code 
bunch of functions
more top-level code

escríbelo así:

# define your main, containing all top-level code
main() 
bunch of functions
# call main
main  

donde main()declara ret_valcomo localy todas las demás funciones devuelven valores a través de ret_val.

Consulte también la siguiente pregunta de Unix y Linux: Alcance de las variables locales en las funciones de Shell .

Otra solución, quizás incluso mejor según la situación, es la publicada por ya.teck que utiliza local -n.

Oliver
fuente
17

Otra forma de lograr esto son las referencias de nombre (requiere Bash 4.3+).

function example {
  local -n VAR=$1
  VAR=foo
}

example RESULT
echo $RESULT
ya.teck
fuente
3
cualquiera que se pregunte qué -n <name>=<reference>hace: convierte la variable recién creada en una referencia a otra señalada por <reference>. Se <name>realizan tareas adicionales en la variable referenciada.
Valerio
7

Me gusta hacer lo siguiente si se ejecuta en un script donde se define la función:

POINTER= # used for function return values

my_function() {
    # do stuff
    POINTER="my_function_return"
}

my_other_function() {
    # do stuff
    POINTER="my_other_function_return"
}

my_function
RESULT="$POINTER"

my_other_function
RESULT="$POINTER"

Me gusta esto, porque puedo incluir declaraciones de eco en mis funciones si quiero

my_function() {
    echo "-> my_function()"
    # do stuff
    POINTER="my_function_return"
    echo "<- my_function. $POINTER"
}
Doc
fuente
5

Como complemento de las excelentes publicaciones de otros, aquí hay un artículo que resume estas técnicas:

  • establecer una variable global
  • establecer una variable global, cuyo nombre le pasó a la función
  • establece el código de retorno (y recógelo con $?)
  • 'echo' algunos datos (y recógelos con MYVAR = $ (myfunction))

Valores devueltos de funciones bash

Tom Hundt
fuente
Esta es la mejor respuesta, ya que el artículo discute limpiamente todas las opciones.
mzimmermann
-2

Git Bash en Windows usando matrices para múltiples valores de retorno

CODIGO BASH:

#!/bin/bash

##A 6-element array used for returning
##values from functions:
declare -a RET_ARR
RET_ARR[0]="A"
RET_ARR[1]="B"
RET_ARR[2]="C"
RET_ARR[3]="D"
RET_ARR[4]="E"
RET_ARR[5]="F"


function FN_MULTIPLE_RETURN_VALUES(){

   ##give the positional arguments/inputs
   ##$1 and $2 some sensible names:
   local out_dex_1="$1" ##output index
   local out_dex_2="$2" ##output index

   ##Echo for debugging:
   echo "running: FN_MULTIPLE_RETURN_VALUES"

   ##Here: Calculate output values:
   local op_var_1="Hello"
   local op_var_2="World"

   ##set the return values:
   RET_ARR[ $out_dex_1 ]=$op_var_1
   RET_ARR[ $out_dex_2 ]=$op_var_2
}


echo "FN_MULTIPLE_RETURN_VALUES EXAMPLES:"
echo "-------------------------------------------"
fn="FN_MULTIPLE_RETURN_VALUES"
out_dex_a=0
out_dex_b=1
eval $fn $out_dex_a $out_dex_b  ##<--Call function
a=${RET_ARR[0]} && echo "RET_ARR[0]: $a "
b=${RET_ARR[1]} && echo "RET_ARR[1]: $b "
echo
##----------------------------------------------##
c="2"
d="3"
FN_MULTIPLE_RETURN_VALUES $c $d ##<--Call function
c_res=${RET_ARR[2]} && echo "RET_ARR[2]: $c_res "
d_res=${RET_ARR[3]} && echo "RET_ARR[3]: $d_res "
echo
##----------------------------------------------##
FN_MULTIPLE_RETURN_VALUES 4 5  ##<---Call function
e=${RET_ARR[4]} && echo "RET_ARR[4]: $e "
f=${RET_ARR[5]} && echo "RET_ARR[5]: $f "
echo
##----------------------------------------------##


read -p "Press Enter To Exit:"

RENDIMIENTO ESPERADO:

FN_MULTIPLE_RETURN_VALUES EXAMPLES:
-------------------------------------------
running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[0]: Hello
RET_ARR[1]: World

running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[2]: Hello
RET_ARR[3]: World

running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[4]: Hello
RET_ARR[5]: World

Press Enter To Exit:
JMI MADISON
fuente